現在,先後端分離愈來愈流行,前端項目的各類打包部署工具也愈來愈多,能夠經過jenkins,pipline等等一鍵部署。本篇記錄使用node的ssh2來進行自動化打包上傳html
首先須要明白自動化上傳的思路,本篇以本人的導航項目我的導航爲藍本。前端
該項目經過vue-cli來生成,默認使用的webpack打包,按照之前的部署方法,應該是npm run build
進行打包,而後手動上傳至服務器,如今使用ssh2來進行自動化上傳。vue
npm install ssh2
這是官方的,詳細使用查看相關文檔,注意的是,我的使用仍是加上dev參數比較好node
config/prod.env.js
進行了配置,包括了服務器名稱,帳號,密碼,項目名稱,路徑等等,這些配置都不必定非要提取出來,能夠自由配置設置經過shell交互進行輸入。'use strict' // const DEFAULT_SERVER = '"localhost:8080"' const REMOTE_SERVER = '0.0.0.0' const DEFAULT_HOST = {host: REMOTE_SERVER, user: '******', password: '******', key: '', name: 'navigation', path: '/opt/lampp/htdocs'} module.exports = { NODE_ENV: '"production"', REMOTE_HOST: REMOTE_SERVER, DEFAULT_HOST: DEFAULT_HOST, }
... "scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "lint": "eslint --ext .js,.vue src", "build": "node build/build.js", "publish": "node build/build.js -p" }, ...
我加了一行publish,他和build類似,區別就是多了一個默認參數-p
, 個人想法是經過監聽這個參數,來判斷是隻進行打包仍是打包上傳。webpack
'use strict' require('./check-versions')() process.env.NODE_ENV = 'production' const program = require('commander') const ora = require('ora') const rm = require('rimraf') const path = require('path') const chalk = require('chalk') const webpack = require('webpack') const config = require('../config') const webpackConfig = require('./webpack.prod.conf') const spinner = ora('building for production...') spinner.start() program .version('0.0.1') .option('-p, --publish', 'Publish Remote') .parse(process.argv) rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, (err, stats) => { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. chunks: false, chunkModules: false }) + '\n\n') if (stats.hasErrors()) { console.log(chalk.red(' Build failed with errors.\n')) process.exit(1) } console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) if (program.publish) { require('../publish/publish-zip')() } }) })
這一步引入了一個commander
,並在最後對program.publish
進行了判斷,若存在則引入publish/publish-zip
文件。到這一步爲止,若是輸入了npm run publish
,這會完成相應的打包工做,而且引入了publish/publish-zip
文件。web
const fs = require('fs') const archiver = require('archiver') const env = require('../config/prod.env') // const chalk = require('chalk') module.exports = function () { // console.log(chalk.cyan(' Zip files.\n')) // console.time('key') var output = fs.createWriteStream(`publish/${env.DEFAULT_HOST.name}.zip`) var archive = archiver('zip') output.on('close', function () { // console.log(chalk.cyan(' Zip files.\n')) // console.timeEnd('key') console.log('compress completed...ready upload') require('./publish')() }) output.on('end', function () { }) archive.on('error', function (err) { throw err }) archive.pipe(output) archive.glob('./dist' + '/**') archive.finalize() }
該文件的主要做用就是,將打包後的文件進行壓縮,壓縮名爲配置中的navigation.zip,壓縮完成後引入publish.js
文件vue-cli
const env = require('../config/prod.env') const chalk = require('chalk') var Client = require('ssh2').Client var conn = new Client() var fs = require('fs') const user = { host: env.DEFAULT_HOST.host, port: 22, username: env.DEFAULT_HOST.user, password: env.DEFAULT_HOST.password } /** * 1.進入目錄 * 2.刪除舊的備份項目 * 3.將原項目名稱加上bak標誌爲備份文件 * 4.解壓縮上傳的zip文件並將名稱改成項目名稱 * 5.刪除zip文件 * 6.退出 * @type {string[]} */ const uploadShellList = [ `cd ${env.DEFAULT_HOST.path}\n`, `rm -rf ${env.DEFAULT_HOST.name}.bak\n`, `mv ${env.DEFAULT_HOST.name} ${env.DEFAULT_HOST.name}.bak\n`, `unzip ${env.DEFAULT_HOST.name}.zip\n`, `mv dist ${env.DEFAULT_HOST.name}\n`, `rm -rf ${env.DEFAULT_HOST.name}.zip\n`, `exit\n` ] const params = {file: `./publish/${env.DEFAULT_HOST.name}.zip`, target: `${env.DEFAULT_HOST.path}/${env.DEFAULT_HOST.name}.zip`} /** * 上傳文件 * @param conn * @param params * @constructor */ function UploadFile (conn, params) { const file = params.file const target = params.target if (!conn) { return } conn.sftp(function (err, sftp) { if (err) { throw err } sftp.fastPut(file, target, {}, function (err, result) { if (err) { console.log(chalk.red(err.message)) throw err } Shell(conn) }) }) } function Ready () { conn.on('ready', function () { console.log('Client :: ready') UploadFile(conn, params) }).connect(user) } /** * 上傳完成後服務器須要執行的內容 * 刪除本地壓縮文件 * @param conn * @constructor */ function Shell (conn) { conn.shell(function (err, stream) { if (err) throw err stream.on('close', function () { console.log('Stream :: close') conn.end() fs.unlinkSync(`./publish/${env.DEFAULT_HOST.name}.zip`) }).on('data', function (data) { console.log('STDOUT: ' + data) }).stderr.on('data', function (data) { console.log('STDERR: ' + data) }) stream.end(uploadShellList.join('')) }) } module.exports = function () { try { Ready() } catch (err) { console.log(err) } }
思路就是:連接服務器->調用uploadFile方法->調用Shell方法(命令自行調整,詳情看註釋)->刪除本地壓縮文件shell
npm run publish
進行自動化的部署了注:npm
const env = require('../config/prod.env') const chalk = require('chalk') var Client = require('ssh2').Client var fs = require('fs') const readline = require('readline') const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) /** * 1.進入目錄 * 2.刪除舊的備份項目 * 3.將原項目名稱加上bak標誌爲備份文件 * 4.解壓縮上傳的zip文件並將名稱改成項目名稱 * 5.刪除zip文件 * 6.退出 * @type {string[]} */ const uploadShellList = [ `cd ${env.DEFAULT_HOST.path}\n`, `rm -rf ${env.DEFAULT_HOST.name}.bak\n`, `mv ${env.DEFAULT_HOST.name} ${env.DEFAULT_HOST.name}.bak\n`, `unzip ${env.DEFAULT_HOST.name}.zip\n`, `mv dist ${env.DEFAULT_HOST.name}\n`, `rm -rf ${env.DEFAULT_HOST.name}.zip\n`, `exit\n` ] const params = {file: `./publish/${env.DEFAULT_HOST.name}.zip`, target: `${env.DEFAULT_HOST.path}/${env.DEFAULT_HOST.name}.zip`} /** * 上傳文件 * @param conn * @param params * @constructor */ function UploadFile (conn, params) { const file = params.file const target = params.target if (!conn) { return } conn.sftp(function (err, sftp) { if (err) { throw err } sftp.fastPut(file, target, {}, function (err, result) { if (err) { console.log(chalk.red(err.message)) throw err } Shell(conn) }) }) } function Ready () { var conn = new Client() const user = { host: env.DEFAULT_HOST.host, port: 22, username: env.DEFAULT_HOST.user, password: env.DEFAULT_HOST.password } if (user.password) { Publish(conn, user) } else { rl.question(chalk.green(`發佈至服務器 ${env.DEFAULT_HOST.host} 請輸入服務器密碼:`), (answer) => { // console.log(chalk.green(`發佈至服務器 ${host.host} 請輸入服務器密碼:`)) if (answer !== null) { user.password = answer.replace(/\r\n$/, '') Publish(conn, user) } }) } } function Publish (conn, user) { conn.on('ready', function () { console.log('Client :: ready') UploadFile(conn, params) }).connect(user) } /** * 上傳完成後服務器須要執行的內容 * 刪除本地壓縮文件 * @param conn * @constructor */ function Shell (conn) { conn.shell(function (err, stream) { if (err) throw err stream.on('close', function () { console.log('Stream :: close') conn.end() fs.unlinkSync(`./publish/${env.DEFAULT_HOST.name}.zip`) }).on('data', function (data) { console.log('STDOUT: ' + data) }).stderr.on('data', function (data) { console.log('STDERR: ' + data) }) stream.end(uploadShellList.join('')) }) } module.exports = function () { try { Ready() } catch (err) { console.log(err) } }
此時,在打包完成後,它會提示輸入服務器密碼:json
compress completed...ready upload 發佈至服務器 0.0.0.0 請輸入服務器密碼: