傳統的前端代碼手工部署流程以下:html
傳統的手工部署須要經歷:前端
npm run build
打包生成dist文件夾。傳統的手工部署存在如下缺點:vue
全自動化的部署其實能夠採用jenkins實現,jenkins能夠根據gitlab push或者merge事件自動打包代碼到web目錄,能夠參考:node
採用jenkins部署是很方便,可是也存在安裝配置麻煩、打包占用服務器資源等缺點。github
因爲咱們的服務器常年高負載運行,曾出現jenkeins打包把服務器打崩的狀況,所以只能逼着博主採用輕量部署的方案來實現自動化部署了(果真技術方案都是被逼出來的,哈哈)。web
思考:
能不能運行相似npm run deploy
一個腳本就直接將咱們的代碼打包、部署到服務器上的web目錄?shell
通過一番調研:發現node-ssh
、archiver
能夠知足咱們的需求。npm
node-ssh是一個基於ssh2的輕量級npm包,主要用於ssh鏈接服務器、上傳文件、執行命令。json
使用指南:
const node_ssh = require('node-ssh')
const ssh = new node_ssh()
複製代碼
用到的api:
ssh.connect({
host: 'localhost',
username: 'steel',
privateKey: '/home/steel/.ssh/id_rsa'
})
複製代碼
ssh.putFile('/home/steel/Lab/localPath', '/home/steel/Lab/remotePath').then(function() {
console.log("The File thing is done")
}, function(error) {
console.log("Something's wrong")
console.log(error)
})
複製代碼
ssh.execCommand('hh_client --json', { cwd:'/var/www' }).then(function(result) {
console.log('STDOUT: ' + result.stdout)
console.log('STDERR: ' + result.stderr)
})
複製代碼
archiver是一個用於生成存檔的npm包,主要用於打包生成zip、rar等。
使用指南:
const archiver = require('archiver');
// 設置壓縮類型及級別
const archive = archiver('zip', {
zlib: { level: 9 },
}).on('error', err => {
throw err;
});
// 建立文件輸出流
const output = fs.createWriteStream(__dirname + '/dist.zip');
// 經過管道方法將輸出流存檔到文件
archive.pipe(output);
// 從subdir子目錄追加內容並重命名
archive.directory('subdir/', 'new-subdir');
// 完成打包歸檔
archive.finalize();
複製代碼
部署方案設計以下:
流程以下:
npm run build
生成dist包node-ssh
讀取配置鏈接服務器ssh.putFile
上傳dist.zipssh.execCommand
解壓dist.zipfs.unlink
刪除本地dist.zip具體代碼:
// deploy.js
const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const node_ssh = require('node-ssh');
const archiver = require('archiver');
const { successLog, errorLog, underlineLog } = require('../utils/index');
const projectDir = process.cwd();
let ssh = new node_ssh(); // 生成ssh實例
// 部署流程入口
function deploy(config) {
const { script } = config;
try {
console.log(`\n(1)${script}`);
childProcess.execSync(`${script}`);
successLog(' 打包成功');
startZip(config);
} catch (err) {
errorLog(err);
process.exit(1);
}
}
// 開始打包
function startZip(config) {
let { distPath, host } = config;
distPath = path.resolve(projectDir, distPath);
console.log('(2)打包成zip');
const archive = archiver('zip', {
zlib: { level: 9 },
}).on('error', err => {
throw err;
});
const output = fs.createWriteStream(`${projectDir}/dist.zip`).on('close', err => {
if (err) {
console.log(' 關閉archiver異常:', err);
return;
}
successLog(' zip打包成功');
console.log(`(3)鏈接${underlineLog(host)}`);
uploadFile(config);
});
archive.pipe(output);
archive.directory(distPath, '/');
archive.finalize();
}
// 上傳文件
function uploadFile(config) {
const { host, port, username, password, privateKey, passphrase, } = config;
const sshConfig = {
host,
port,
username,
password,
privateKey,
passphrase
};
ssh.connect(sshConfig)
.then(() => {
successLog(` SSH鏈接成功`);
console.log(`(4)上傳zip至目錄${underlineLog(config.webDir)}`);
ssh.putFile(`${projectDir}/dist.zip`, `${config.webDir}/dist.zip`)
.then(() => {
successLog(` zip包上傳成功`);
console.log('(5)解壓zip包');
statrRemoteShell(config);
})
.catch(err => {
errorLog(' 文件傳輸異常', err);
process.exit(0);
});
})
.catch(err => {
errorLog(' 鏈接失敗', err);
process.exit(0);
});
}
// 執行Linux命令
function runCommand(command, webDir) {
return new Promise((resolve, reject) => {
ssh.execCommand(command, { cwd: webDir })
.then(result => {
resolve();
// if (result.stdout) {
// successLog(result.stdout);
// }
if (result.stderr) {
errorLog(result.stderr);
process.exit(1);
}
})
.catch(err => {
reject(err);
});
});
}
// 開始執行遠程命令
function statrRemoteShell(config) {
const { webDir } = config;
const commands = [`cd ${webDir}`, 'pwd', 'unzip -o dist.zip && rm -f dist.zip'];
const promises = [];
for (let i = 0; i < commands.length; i += 1) {
promises.push(runCommand(commands[i], webDir));
}
Promise.all(promises)
.then(() => {
successLog(' 解壓成功');
console.log('(6)開始刪除本地dist.zip');
deleteLocalZip(config);
})
.catch(err => {
errorLog(' 文件解壓失敗', err);
process.exit(0);
});
}
// 刪除本地dist.zip包
function deleteLocalZip(config) {
const { projectName, name } = config;
fs.unlink(`${projectDir}/dist.zip`, err => {
if (err) {
errorLog(' 本地dist.zip刪除失敗', err);
}
successLog(' 本地dist.zip刪除成功\n');
successLog(`\n 恭喜您,${underlineLog(projectName)}項目${underlineLog(name)}部署成功了^_^\n`);
process.exit(0);
});
}
module.exports = deploy;
複製代碼
問題:
上面的方案已經能夠完成一個項目的自動化部署,可是再有一個新的項目要接入自動化部署,是否是又得把整個文件拷貝過去,是否是很是麻煩?
所以能夠將自動化部署作成一個腳手架fe-deploy-cli
,支持生成部署配置模板、腳本部署,只需一條命令便可部署到對應環境中。
與腳手架相關的npm包:
package.json
中的打包script初始化須要在github上新建一個部署配置git倉庫,執行deploy init
經過download-git-repo
從git上拉取配置模板。
// init.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const download = require('download-git-repo');
const ora = require('ora');
const { successLog, infoLog, errorLog } = require('../utils/index');
let tmp = 'deploy';
const deployPath = path.join(process.cwd(), './deploy');
const deployConfigPath = `${deployPath}/deploy.config.js`;
const deployGit = 'dadaiwei/fe-deploy-cli-template';
// 檢查部署目錄及部署配置文件是否存在
const checkDeployExists = () => {
if (fs.existsSync(deployPath) && fs.existsSync(deployConfigPath)) {
infoLog('deploy目錄下的deploy.config.js配置文件已經存在,請勿從新下載');
process.exit(1);
return;
}
downloadAndGenerate(deployGit);
};
// 下載部署腳本配置
const downloadAndGenerate = templateUrl => {
const spinner = ora('開始生成部署模板');
spinner.start();
download(templateUrl, tmp, { clone: false }, err => {
if (err) {
console.log();
errorLog(err);
process.exit(1);
}
spinner.stop();
successLog('模板下載成功,模板位置:deploy/deploy.config.js');
infoLog('請配置deploy目錄下的deploy.config.js配置文件');
process.exit(0);
});
};
module.exports = () => {
checkDeployExists();
};
複製代碼
經過修改deploy.config.js
,設定dev(測試環境)和prod(線上環境)的配置。
// deploy.config.js
module.exports = {
privateKey: '', // 本地私鑰地址,位置通常在C:/Users/xxx/.ssh/id_rsa,非必填,有私鑰則配置
passphrase: '', // 本地私鑰密碼,非必填,有私鑰則配置
projectName: '', // 項目名稱
dev: { // 測試環境
name: '測試環境',
script: "npm run build", // 測試環境打包腳本
host: '', // 測試服務器地址
port: 22, // ssh port,通常默認22
username: '', // 登陸服務器用戶名
password: '', // 登陸服務器密碼
distPath: 'dist', // 本地打包dist目錄
webDir: '', // // 測試環境服務器地址
},
prod: { // 線上環境
name: '線上環境',
script: "npm run build", // 線上環境打包腳本
host: '', // 線上服務器地址
port: 22, // ssh port,通常默認22
username: '', // 登陸服務器用戶名
password: '', // 登陸服務器密碼
distPath: 'dist', // 本地打包dist目錄
webDir: '' // 線上環境web目錄
}
// 再還有多餘的環境按照這個格式寫便可
}
複製代碼
註冊部署命令就是從deploy.config.js
中讀取dev和prod配置,而後經過program.command
註冊dev和prod command,運行deploy dev
或者deploy prod
即進入1.3節的部署流程。
// 部署流程
function deploy() {
// 檢測部署配置是否合理
const deployConfigs = checkDeployConfig(deployConfigPath);
if (!deployConfigs) {
process.exit(1);
}
// 註冊部署命令,註冊後支持deploy dev和deploy prod
deployConfigs.forEach(config => {
const { command, projectName, name } = config;
program
.command(`${command}`)
.description(`${underlineLog(projectName)}項目${underlineLog(name)}部署`)
.action(() => {
inquirer.prompt([
{
type: 'confirm',
message: `${underlineLog(projectName)}項目是否部署到${underlineLog(name)}?`,
name: 'sure'
}
]).then(answers => {
const { sure } = answers;
if (!sure) {
process.exit(1);
}
if (sure) {
const deploy = require('../lib/deploy');
deploy(config);
}
});
});
});
}
複製代碼
前提條件:能經過ssh連上服務器便可。
適用對象:目前還在採用手工部署又指望快速實現輕量化部署的小團隊或者我的項目,畢竟像阿里這種大公司都有完善的前端部署平臺。
npm i fe-deploy-cli -g
複製代碼
查看版本,安裝成功
deploy init
複製代碼
在當前項目下生成了deploy.config.js
部署配置文件位於deploy文件夾下的deploy.config.js
, 通常包含dev
(測試環境)和prod
(線上環境)兩個配置,再有多餘的環境配置形式與之相似,只有一個環境的能夠刪除另外一個多餘的配置(好比只有prod
線上環境,請刪除dev
測試環境配置)。
具體配置信息請參考配置文件註釋:
module.exports = {
privateKey: '', // 本地私鑰地址,位置通常在C:/Users/xxx/.ssh/id_rsa,非必填,有私鑰則配置
passphrase: '', // 本地私鑰密碼,非必填,有私鑰則配置
projectName: 'hivue', // 項目名稱
dev: { // 測試環境
name: '測試環境',
script: "npm run build-dev", // 測試環境打包腳本
host: '10.240.176.99', // 測試服務器地址
port: 22, // ssh port,通常默認22
username: 'root', // 登陸服務器用戶名
password: '123456', // 登陸服務器密碼
distPath: 'dist', // 本地打包dist目錄
webDir: '/var/www/html/dev/hivue', // // 測試環境服務器地址
},
prod: { // 線上環境
name: '線上環境',
script: "npm run build", // 線上環境打包腳本
host: '10.240.176.99', // 線上服務器地址
port: 22, // ssh port,通常默認22
username: 'root', // 登陸服務器用戶名
password: '123456', // 登陸服務器密碼
distPath: 'dist', // 本地打包dist目錄
webDir: '/var/www/html/prod/hivue' // 線上環境web目錄
}
// 再還有多餘的環境按照這個格式寫便可
}
複製代碼
配置好deploy.config.js
,運行
deploy --help
複製代碼
查看部署命令
測試環境部署採用的是dev
的配置
deploy dev
複製代碼
先有一個確認,確認後進入部署流程,腳本自動完成6步操做後,恭喜您,部署成功!!!
線上環境部署採用的是prod
的配置
deploy prod
複製代碼
部署流程和測試環境部署相同:
上面已經實現了腳手架自動化部署,評論區看到有一個老哥的評論:
查了下ssh.putDirectory
支持上傳目錄,因而針對以前的部署流程和代碼進行了優化,去除了archiver
打包zip、上傳zip、解壓zip的過程,部署核心代碼deploy.js
採用async await
替換Promise
優化了下。
部署流程優化爲:
流程以下:
ssh.execCommand
執行cd xxx
和rm -rf *
命令。ssh.putDirectory
直接上傳dist到web目錄。核心代碼以前採用Promise
寫法,優化爲採用async await
方式:
// deploy.js
const path = require('path');
const childProcess = require('child_process');
const node_ssh = require('node-ssh');
const { successLog, errorLog, underlineLog } = require('../utils/index');
const projectDir = process.cwd();
let ssh = new node_ssh(); // 生成ssh實例
// 部署流程入口
async function deploy(config) {
const { script, webDir, distPath, projectName, name } = config;
execBuild(script);
await connectSSH(config);
await clearOldFile(config.webDir);
await uploadDirectory(distPath, webDir);
successLog(`\n 恭喜您,${underlineLog(projectName)}項目${underlineLog(name)}部署成功了^_^\n`);
process.exit(0);
}
// 第一步,執行打包腳本
function execBuild(script) {
try {
console.log(`\n(1)${script}`);
childProcess.execSync(`${script}`);
successLog(' 打包成功');
} catch (err) {
errorLog(err);
process.exit(1);
}
}
// 第二步,鏈接SSH
async function connectSSH(config) {
const { host, port, username, password, privateKey, passphrase, distPath } = config;
const sshConfig = {
host,
port,
username,
password,
privateKey,
passphrase
};
try {
console.log(`(2)鏈接${underlineLog(host)}`);
await ssh.connect(sshConfig);
successLog(' SSH鏈接成功');
} catch (err) {
errorLog(` 鏈接失敗 ${err}`);
process.exit(1);
}
}
// 運行命令
async function runCommand(command, webDir) {
await ssh.execCommand(command, { cwd: webDir });
}
// 第三步,清空遠端目錄
async function clearOldFile(webDir) {
try {
console.log('(3)清空遠端目錄');
await runCommand(`cd ${webDir}`, webDir);
await runCommand(`rm -rf *`, webDir);
successLog(' 遠端目錄清空成功');
} catch (err) {
errorLog(` 遠端目錄清空失敗 ${err}`);
process.exit(1);
}
}
// 第四步,上傳文件夾
async function uploadDirectory(distPath, webDir) {
try {
console.log(`(4)上傳文件到${underlineLog(webDir)}`);
await ssh.putDirectory(path.resolve(projectDir, distPath), webDir, {
recursive: true,
concurrency: 10,
});
successLog(' 文件上傳成功');
} catch (err) {
errorLog(` 文件傳輸異常 ${err}`);
process.exit(1);
}
}
module.exports = deploy;
複製代碼
腳手架初始化、設定配置、註冊部署命令及使用指南與以前版本保持一致。
測試環境和部署環境打包流程打印信息有所變化。
測試環境部署採用的是dev
的配置。
deploy dev
複製代碼
去除壓縮zip的過程,操做步驟變成4步,恭喜您,部署成功!!!
線上環境部署採用的是prod
的配置。
deploy prod
複製代碼
線上環境部署與測試環境流程相同:
以上就是博主關於前端輕量化部署腳手架的一點小實踐,以爲有收穫的能夠關注一波,點贊一波,下載一波,使用一波,碼字不易,萬分感謝。
感謝LoneYin同窗的建議,多交流溝通,纔會有更好的idea,才能共同進步。
git地址:github.com/dadaiwei/fe… (歡迎star,感謝感謝)