💔 每个前端都经历过的痛:上线之殇
"哥,能帮我部署下项目吗?就更新个按钮样式..."
"姐,服务器怎么重启nginx啊?能不能..."
"大佬,我代码传好了但是白屏,能帮我看看吗..."
是不是很熟悉?作为前端,我们经常遇到这样的困境:
🆘 传统部署的三大噩梦
-
卑微求人模式
- 每次改个CSS都要找后端帮忙
- 后端大佬在开会/在忙/在度假...
- "这个简单,你自己搞搞呗"(但根本不会啊!)
-
FTP上古神器
- 传着传着断线了
- 传完了发现漏了几个文件
- 覆盖了不该覆盖的配置
-
服务器黑魔法
diff
- Linux命令像天书
diff
- 权限问题永远搞不定
- "sudo rm -rf /" 警告!(千万别试!)
🔑 解放方案:NodeSSH ------ 前端的部署自由卡
为什么SSH是终极解决方案?
SSH就像给你的电脑和服务器之间架了座加密大桥:
- 直接操作服务器:再也不用求人帮忙敲命令
- 自动化脚本:一次编写,终身受用
- 文件传输稳定:比FTP可靠100倍
- 全前端技术栈:用你最熟悉的JavaScript搞定
🌈 自主部署带来的美好生活
- 随时上线:半夜灵感来了?自己搞定!
- 快速迭代:小改动分分钟上线,不用排队
- 故障自修:页面挂了?自己就能回滚版本
- 技能升级:从此简历可以写上"全栈部署能力"
🌟 认识我们的"魔法杖":NodeSSH
首先隆重介绍今天的主角------node-ssh
,它就像是给你的Node.js装上了一根SSH魔法杖!这个神奇的库可以让你:
- 📡 远程连接服务器(就像在服务器门口变出个任意门)
- 💻 执行各种Linux命令(动动键盘就能让服务器乖乖听话)
- 📁 上传下载文件(比FTP还要方便的文件快递服务) 安装它只需要一句咒语:
js
npm install node-ssh
🧙♂️ 部署脚本解析:分步拆解魔法咒语
1️⃣ 准备阶段:导入法宝
js
import { NodeSSH } from 'node-ssh'; // 主角登场
import { deployConfig } from './config.js'; // 服务器钥匙
import path from 'path'; // 路径导航仪
// 获取当前法术书位置(兼容ES模块的现代魔法)
const __dirname = path.dirname(new URL(import.meta.url).pathname);
2️⃣ 连接服务器:打开魔法传送门
js
const ssh = new NodeSSH(); // 掏出我们的魔法杖
try {
// 念出连接咒语(使用config.js中的配置)
await ssh.connect(deployConfig);
console.log('🎉 SSH连接成功!服务器说:"欢迎光临~"');
// ...后续操作
} catch (err) {
console.error('💥 啊哦!连接失败:', err);
}
3️⃣ 定位dist目录:寻找"宝藏地图"
js
// 像海盗寻宝一样找到dist目录
let localDistPath = path.join(path.dirname(__dirname), 'dist');
// 处理Windows路径的小脾气(把反斜杠变成正斜杠)
localDistPath = localDistPath.replace(/^\\+/, '');
console.log(`🔍 找到宝藏位置:${localDistPath}`);
🗺️ 路径定位原理 :
假设你的项目结构是这样的:
js
my-project/
├── deploy/ ← 我们的脚本在这里
│ └── deploy.js
└── dist/ ← 我们要找的"宝藏"
4️⃣ 上传文件:施展文件传送术
js
const remoteDistPath = '/usr/share/nginx/html'; // 服务器上的"宝箱"
console.log(`🚀 开始传送:${localDistPath} → 服务器:${remoteDistPath}`);
// 最强大的咒语------putDirectory!
const uploadResult = await ssh.putDirectory(localDistPath, remoteDistPath, {
recursive: true, // 连子文件夹一起传送
concurrency: 10, // 同时传送10个文件(效率MAX!)
tick: (localPath) => { // 进度播报员
console.log(`📤 正在传送:${path.basename(localPath)}`);
}
});
console.log(uploadResult ? '🎊 传送成功!' : '😱 传送失败!');
5️⃣ 收尾工作:打扫魔法现场
js
// 可选:重启nginx让新版本生效
const restartResult = await ssh.execCommand('sudo systemctl restart nginx');
console.log(restartResult.stderr || '🔄 Nginx重启成功!');
// 最后记得关闭传送门哦!
ssh.dispose();
console.log('🏁 部署完成!可以打开浏览器验收啦~');
最后贴一份已经写完的基础代码,如果有其他需求增加可以自己编辑
目录结构
js
my-project/
├── script/ ← 我们的脚本在这里
│ └── node_ssh.js
│ └──config.js
└── dist/ ← 我们要找的"宝藏"
└──src/
└──ssh_tools.jsonc
node_ssh文件
js
//scripts/node_ssh
import { NodeSSH } from 'node-ssh';
import { deployConfig } from './config.js';
import path from 'path';
const __dirname = path.dirname(new URL(import.meta.url).pathname);
async function deploy() {
const ssh = new NodeSSH();
try {
await ssh.connect(deployConfig);
console.log('SSH 连接成功!');
const result = await ssh.execCommand('ls');
console.log('命令执行结果:', result);
const deployScript = `
cd /usr/share/nginx/html;
`;
const deployResult = await ssh.execCommand(deployScript);
console.log('部署脚本执行结果:', deployResult);
let localDistPath = path.join(path.dirname(__dirname), 'dist');
localDistPath = localDistPath.replace(/^\\+/, '');
const remoteDistPath = '/usr/share/nginx/html';
// 上传 dist 文件夹
const uploadResult = await ssh.putDirectory(localDistPath, remoteDistPath, {
recursive: true,
concurrency: 10,
});
console.log('上传结果:', uploadResult);
// const restartResult = await ssh.execCommand('sudo systemctl restart nginx');
// console.log('Nginx 重启结果:', restartResult);
} catch (err) {
console.error('SSH 连接或命令执行出错:', err);
}
}
deploy();
config文件
js
//srcipt/config.js
export const deployConfig = {
host: '服务器公网地址',
username: '账号',
password: '服务器密码'
}
ssh_tools.jsonc配置
js
{
"test": {
"host": "",
"port": ,
"username": "",
"password": "",
"proxy": false,
"upload_on_save": false,
"watch": true,
"submit_git_before_upload": false,
"submit_git_msg": "",
"compress": false,
"remote_unpacked": true,
"delete_remote_compress": true,
"delete_local_compress": true,
"upload_to_root": false,
"deleteRemote": false,
"distPath": [],
"remotePath": "/www/wwwtest/test",
"excludePath": []
}
//参考配置
//环境名称,支持自定义名称
// "test": { //测试环境
// "host": "0.0.0.0", // (必填)服务器地址
// "port": 22, // (非必填) 端口号 ,默认22
// "username": "username", // (必填)登录用户名
// "password": "password", // 登录密码 (和私钥路径,二选一)
// // "privateKeyPath": "/your_path/id_rsa", // 私钥路径 (和登录密码,二选一),注意:最好不要将密匙,放代码根目录
// "proxy": false, // 是否使用代理,默认false
// "upload_on_save": false, // 保存后实时提交,建议单人开发使用,upload_on_save设置为true时,watch、submit_git_before_upload、compress、deleteRemote无效,默认false
// "watch": true, // 监听上传目录文件变动,默认true,如果upload_on_save为true,则此项无效。如果配置了distPath目录,则只监听distPath目录下文件变动
// "submit_git_before_upload": true, // 团队开发使用,上传代码前提交本地git,防止覆盖远程代码,默认false
// "submit_git_msg": "", // 提交git的message配置,默认空。submit_git_before_upload为true时,不填写会弹出提示框手动填写
// // "build": "yarn build:test", // (非必填) 构建执行的命令 如果是前端项目则打开此项
// "compress": true, // 是否压缩上传,并远程解压(账号需要支持ssh登录,系统会自动检测是否支持,不支持,则不会压缩上传),默认false
//"remote_unpacked": true, // 压缩上传后是否远程解压,默认true
//"delete_remote_compress": true, // 压缩文件上传后是否删除远程压缩文件,默认true
//"delete_local_compress": true, // 压缩文件上传后是否删除本地压缩文件,默认true
// "upload_to_root": false, // 如果distPath配置目录只有一个,则上传到remotePath根目录,一般用于部署前端代码, 默认false
// "deleteRemote": false, // 上传前是否删除远程distPath配置目录,一般用于清理前端部署代码, 默认false
// "distPath": [], // (非必填) 本地需要上传的目录,支持字符串或数组,默认上传根目录
// "remotePath": "/www/wwwtest/test", // (必填)上传服务器地址
// "excludePath": [] // (非必填) 当前环境排除的上传文件及目录,会和插件配置excludePath合并,插件配置使用gitignore的时候,会和.gitignore配置文件合并
// }
}
最后再在package.json加上运行语句
js
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "npm run build && node scripts/node_ssh.js" //->这个是运行语句
},
🎯 总结:你的部署魔法
现在你拥有了:
- ✅ 一键连接服务器的能力
- ✅ 自动定位dist目录的智慧
- ✅ 批量上传文件的效率
- ✅ 安全部署的最佳实践 下次部署时,只需轻轻一句:
js
npm run deploy
然后就可以喝着咖啡☕,看着文件自动飞上服务器啦!是不是比手动FTP一个个传文件要优雅多了?快去试试吧!