从零开始
更新:
现已支持添加多个配置信息, 自动化部署时支持选择配置信息运行
效果展示
1. 待部署工程本地完成打包构建
本地打包构建目录
2. 确定远端部署目录及发布文件夹
远端部署目录
3. 修改配置
修改配置文件
4. 运行自动化部署
选择配置信息
自动化部署
5. 查看远端效果
远端部署目录
6. 再次部署 原目录已备份 (开启远端备份生效)
远端部署目录
前言
前端项目部署时, nginx 配置完成后, 只需将打包后的文件上传至服务器指定目录下即可.
一般使用以下方式完成:
xshell 等命令行工具上传
ftp 等可视化工具上传
jenkins 等自动化部署服务
对于简单前端项目, 频繁部署时, xshell,ftp 两种方式较为繁琐, 而 jenkins 等自动化部署服务需要提前安装软件, 并熟悉配置流程.
因此希望借助本地 node 服务实现对前端打包后文件的上传工作, 既不需要服务器额外安装程序, 还可以帮助我们实现快速上传部署, 更能帮助我们深入了解 node .
开始
1. 明确需求
进行开发前需要首先明确需求, 根据常见的前端部署流程总结为以下过程:
前端部署流程
根据部署流程明确自动化部署的需求:
明确需求
2. 开发前准备
2.1 导入依赖模块
由于需要实现文件压缩, 及连接远程服务器, 实现远程命令调用, 因此至少需要以下模块:
SSH 模块 (可实现连接服务器, 命令调用等常见操作)
文件压缩 模块 (可实现 .zip 等常见压缩文件的本地打包)
查找资料, 最终选择 node-SSH 和 archiver 分别实现上述功能
- # 安装依赖
- NPM i node-SSH --save
- NPM i archiver --save
2.2 如何实现规范
为实现需求中的 解耦合理 与 逻辑清晰 / 灵活 , 需要关注整体程序逻辑, 这里选择 封装 相关功能实现, 并在 主程序 中自由调度 (可灵活 调用 / 关闭 / 修改 相关功能), 并对于当前所执行功能给与提示, 以保证功能实现的 完整性 和 异常提示 .
因为 文件压缩 , 文件上传 , 执行远端命令 等存在异步过程, 因此需要明确功能完成的顺序, 即 应在前置任务完成的回调中开启当前任务, 为实现控制异步过程和代码逻辑清晰, 这里选择使用 ES6 的 Promise 结合 ES7 的语法糖 async awiat 实现逻辑流程控制.
到这里就完成了对程序功能构建的梳理工作, 下面进入项目实现.
3. 功能实现
工程目录预览
这里先展示最终结果的工程目录, 以供参考:
工程目录
- node_modules
- utils
compressFile.JS[压缩本地文件]
handleCommand.JS[调用远端命令]
helper.JS[]
SSH.JS[连接远端务器]
uploadFile.JS[上传本地文件]
App.JS[主程序]
config.JS[配置文件]
dist.zip[压缩后的文件] (当前版本不会自动删除)
package.JSON
README.md[项目介绍]
3.1 压缩本地文件
compressFile 接收 需要压缩的目录 和 打包生成文件, 传入后实现本地文件压缩.
compressFile.JS 参考代码
- // compressFile.JS
- var fs = require('fs')
- var archiver = require('archiver')
- function compressFile (targetDir, localFile) {
- return new Promise((resolve, reject)=>{
- console.log('1 - 正在压缩文件...')
- let output = fs.createWriteStream(localFile) // 创建文件写入流
- const archive = archiver('zip', {
- zlib: { level: 9 } // 设置压缩等级
- })
- output.on('close', () => {
- resolve(
- console.log('2 - 压缩完成! 共计' + (archive.pointer() / 1024 /1024).toFixed(3) + 'MB')
- )
- }).on('error', (err) => {
- reject(console.error('压缩失败', err))
- })
- archive.pipe(output) // 管道存档数据到文件
- archive.directory(targetDir, 'dist') // 存储目标文件并重命名
- archive.finalize() // 完成文件追加 确保写入流完成
- })
- }
- module.exports = compressFile
3.2 连接远端服务器
connectServe 接收远端 ip, 用户名, 密码等信息, 完成远端服务器连接, 具体配置参考 config.JS .
SSH.JS 参考代码
- // SSH.JS
- node_ssh = require('node-ssh')
- SSH = new node_ssh()
- function connectServe (sshInfo) {
- return new Promise((resolve, reject) => {
- SSH.connect({ ...sshInfo }).then(() => {
- resolve(console.log('3-' + sshInfo.host + '连接成功'))
- }).catch((err) => {
- reject(console.error('3-' + sshInfo.host + '连接失败', err))
- })
- })
- }
- module.exports = connectServe
3.3 远端执行命令
runCommand 接收 需执行的命令 和 执行命令的远端路径, 这里将其单独拆分, 既方便 主程序 的单独调用, 也方便 文件上传 等功能的模块封装, 达到 解耦 的效果.
handleCommand.JS 参考代码
- // handleCommand.JS
- node_ssh = require('node-ssh')
- SSH = new node_ssh()
- // run Linux shell
- function runCommand (command, path) {
- return new Promise((resolve, reject) => {
- SSH.execCommand(command, {
- cwd: path
- }).then((res) => {
- if (res.stderr) {
- reject(console.error('发生错误:' + res.stderr))
- } else {
- resolve(console.log(command + '执行完成!'))
- }
- })
- })
- }
- module.exports = runCommand
3.4 文件上传
uploadFile 接收 系统配置参数, 待上传的本地文件 , 完成本地文件上传至指定服务器目录, 这里还引入 handleCommand.JS , 根据 openBackUp 是否开启远端备份, 完成对存在解压后同名目录的处理. 具体配置参考 config.JS .
uploadFile.JS 参考代码
- // uploadFile.JS
- node_ssh = require('node-ssh')
- SSH = new node_ssh()
- runCommand = require ('./handleCommand')
- async function uploadFile (config, localFile) {
- return new Promise((resolve, reject) => {
- console.log('4 - 开始文件上传')
- handleSourceFile()
- SSH.putFile(localFile, config.deployDir + config.targetFile).then(async () => {
- resolve(console.log('5 - 文件上传完成'))
- }, (err) => {
- reject(console.error('5 - 上传失败!', err))
- })
- })
- }
- // 处理源文件
- async function handleSourceFile () {
- if (config.openBackUp) {
- console.log('已开启远端备份!')
- await runCommand(
- `
- if [ -d ${config.releaseDir} ];
- then mv ${config.releaseDir} ${config.releaseDir}_${new Date().getTime()}
- fi
- `,
- config.deployDir)
- } else {
- console.log('提醒: 未开启远端备份!')
- await runCommand(
- `
- if [ -d ${config.releaseDir} ];
- then mv ${config.releaseDir} /tmp/${config.releaseDir}_${new Date().getTime()}
- fi
- `,
- config.deployDir)
- }
- }
- module.exports = uploadFile
3.5 主程序
当所有功能模块封装完成后, 其中 异步流程 均使用 Promise 处理, 这时结合 async awiat 实现, 既保证了功能实现的顺序, 也使得功能组合变得更加简洁, 优雅.
main 函数中通关功能组合实现自动化部署的流程, 后续增加其他功能实现, 主程序 中引入, 组合即可完成升级.
App.JS 参考代码
- // App.JS
- config = require ('./config')
- compressFile = require ('./utils/compressFile')
- connectServe = require ('./utils/ssh')
- uploadFile = require ('./utils/uploadFile')
- runCommand = require ('./utils/handleCommand')
- // 可单独执行
- async function main () {
- const localFile = __dirname + '/' + config.targetFile
- config.openCompress ? await compressFile(config.targetDir, localFile) : '' // 压缩
- await connectServe(config.SSH) // 连接
- await uploadFile(config, localFile) // 上传
- await runCommand('unzip' + config.targetFile, config.deployDir) // 解压
- await runCommand('mv dist' + config.releaseDir, config.deployDir) // 修改文件名称
- await runCommand('rm -f' + config.targetFile, config.deployDir) // 删除
- console.log('所有操作完成!')
- process.exit()
- }
- // run main
- main()
3.5 配置文件
为方便前端自动化部署, 这里抽离关键信息, 生成配置文件.
用户只需修改配置文件即可实现 自动化部署 .
config.JS 参考代码
- // config.JS
- /*
- 说明:
- 请确保解压后的文件目录为 dist
- SSH: 连接服务器用户信息
- targetDir: 需要压缩的文件目录 (需开启压缩)
- targetFile: 指定上传文件名称 (该文件同级目录)
- openCompress: 关闭后, 将跳过目标目录压缩步骤, 直接上传指定文件
- openBackUp: 开启后, 若远端存在相同目录, 则会修改原始目录名称, 不会直接覆盖
- deployDir: 指定远端部署地址
- releaseDir: 指定远端部署地址下的发布目录名称
- */
- const config = {
- SSH: {
- host: '192.168.0.110',
- username: 'root',
- password: 'root'
- },
- targetDir: 'E:/private/my-vue-cli/dist', // 目标压缩目录 (可使用相对地址)
- targetFile: 'dist.zip', // 目标文件
- openCompress: true, // 是否开启压缩
- openBackUp: true, // 是否开启远端备份
- deployDir: '/home/node_test' + '/', // 远端目录
- releaseDir: 'web' // 发布目录
- }
- module.exports = config
使用
拉取源码, 安装依赖, 修改配置文件, 运行即可
- NPM install
- NPM run deploy
该项目已开源至 GitHub 欢迎下载使用 后续会完善更多功能
源码及项目说明
Tip: 喜欢的话别忘记 star 哦, 有疑问欢迎提出 issues , 积极交流.
来源: http://www.jianshu.com/p/312f3be018de