一、前言
在實習(xí)之前,在學(xué)校做個人項目的時候,每每需要將前端構(gòu)建產(chǎn)物部署到服務(wù)器的時候都需要自己手動的經(jīng)歷本地環(huán)境打包構(gòu)建、打開sftp傳輸工具、代碼上傳至服務(wù)器指定目錄(前端靜態(tài)文件通過Tomcat部署,只需要更新文件即可,不需要重啟Tomcat)等一系列繁瑣卻又簡單的工作,也就是說如果我想要在生產(chǎn)環(huán)境看到自己在開發(fā)環(huán)境中產(chǎn)生的變化,那么我就需要經(jīng)過如上步驟,有時候還要涉及云服務(wù)器登陸,登陸又需要去尋找登陸密碼(因為密碼都是使用的默認(rèn)的,記不住)。因此,想要立馬看到自己的更改,往往是需要幾分鐘的,而且有時候迭代頻繁,這些工作都需要機(jī)械重復(fù)的去執(zhí)行,這對于一名程序員來說,這些是有點傻的操作,那么就急需將上述重復(fù)的步驟交給程序來操作,因此我產(chǎn)生了發(fā)布一款自動化部署工具到npm倉庫的想法,供大家(主要用戶還是自己)使用。
以下所構(gòu)思的“前端部署小幫手”npm包已經(jīng)發(fā)布到了npm倉庫,以下是github地址,喜歡的話給個star吧,謝謝大家^_^!
前端部署小幫手 - deploy-helper
二、前端自動化部署初探
之前想要實現(xiàn)的是用戶登陸目標(biāo)服務(wù)器安裝一款npm包(由自己開發(fā))到服務(wù)器本地,然后執(zhí)行一系列指令即可完成服務(wù)器生產(chǎn)環(huán)境的搭建(包括node環(huán)境、java環(huán)境、tomcat、數(shù)據(jù)庫等環(huán)境),并通過webhook監(jiān)聽遠(yuǎn)程倉庫的變化,自動拉取代碼到服務(wù)器本地部署。但是實現(xiàn)起來并不簡單,而且工作量很大,為了解決當(dāng)前項目部署繁瑣、機(jī)械的過程,我僅采取了實現(xiàn)將本地開發(fā)環(huán)境的構(gòu)建產(chǎn)物打包、傳輸、服務(wù)端解壓縮并清理壓縮產(chǎn)物的整個過程。而上述目標(biāo)則作為更為宏遠(yuǎn)的目標(biāo),逐步完善,漸進(jìn)式開發(fā)。
2.1 deploy-helper
我打算將此包命名為deploy-helper,意為“部署小幫手”。它主要有以下幾個核心功能:

配置項 .deploy.config.json
{
"host": "", // 域名或ip地址
"port": 22, // 默認(rèn)sftp連接端口號
"localPath": "./dist", // 需要上傳到服務(wù)器的本地文件夾目錄
"remotePath": "/root/dist", // 需要上傳到目標(biāo)服務(wù)器的文件夾目錄
"readyTimeout": 20000 // 默認(rèn)連接超時時間
"shellScripts": "" // shell腳本
}
構(gòu)建產(chǎn)物壓縮
/**
* 文件壓縮
*
* @param {*} localPath
*/
async function compress(localPath) {
// 壓縮產(chǎn)出文件夾名稱
const folderName = path.basename(localPath);
// 產(chǎn)出文件夾位置
const outDir = path.resolve(localPath, '../', `${folderName}.zip`);
const output = fs.createWriteStream(outDir);
const archive = archiver('zip', {
zlib: { level: 9 } // 設(shè)置壓縮級別.
});
// 監(jiān)聽壓縮完成,文件流關(guān)閉事件
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', function() {
console.log('Data has been drained');
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function(err) {
if (err.code === 'ENOENT') {
// log warning
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
archive.on('error', function(err) {
throw err;
});
// pipe archive data to the file
archive.pipe(output);
// 添加壓縮文件(夾)
await compressDir(archive, localPath);
// 壓縮
await archive.finalize();
return outDir;
}
/**
* 壓縮文件、文件夾
*
* @param {*} archive
* @param {*} dir
*/
function compressDir(archive, dir) {
return new Promise((resolve, reject) => {
fs.stat(dir, (err,data)=>{
if (err){
reject(err);
return console.log(err);
}
const filename = path.basename(dir);
// 如果是文件夾 則壓縮該文件夾
if (data.isDirectory()) {
archive.directory(dir, filename);
} else {
// 否則壓縮該文件
archive.append(fs.createReadStream(dir), { name: filename });
}
resolve(true);
})
})
}
連接目標(biāo)服務(wù)器
1. 目標(biāo)服務(wù)器地址可配置
根據(jù)配置的.deploy.config.json里面的 host 以及 port,通過ssh2即可實現(xiàn)連接遠(yuǎn)程服務(wù)器。ssh2提供了豐富的api,可以上傳、下載、執(zhí)行shell腳本等。
const conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready'); // 服務(wù)器連接就緒
...
}).connect({
readyTimeout: 20000,
...config,
...args
});
壓縮文件上傳
compress(localPath).then((outDir) => {
const zipFileName = path.basename(outDir);
let p = config.remotePath;
p = p.replace(/\/+$/, '');
const remotePath = p + '/' + zipFileName;
console.log(outDir, remotePath);
uploadFile(conn, outDir, remotePath, (res) => {
console.log('上傳成功!', res === undefined ? '' : res);
console.log('開始解壓文件...');
...
}
}
執(zhí)行自定義shell腳本
1. 文件解壓縮
2. 執(zhí)行npm scripts
3. 執(zhí)行常見Linux命令
.......
// 執(zhí)行shell腳本命令
conn.shell((err, stream) => {
if (err) throw err;
stream.on('close', () => {
console.log('Stream :: close');
conn.end();
}).on('data', (data) => {
console.log('OUTPUT: ' + data.slice(0, 100));
});
// 進(jìn)入指定文件夾解壓縮文件到當(dāng)前文件夾 并刪除壓縮包
// 執(zhí)行自定義腳本
const scripsts = config.shellScripts === undefined ? '' : config.shellScripts;
stream.end(`cd ${p}\nrm -rf ${folderName}\njar xvf ${zipFileName}&&rm -rf ${zipFileName}\n${scripsts}\nexit\n`);
});
以上就是整個deploy-helper的執(zhí)行過程的關(guān)鍵流程,它可以滿足將靜態(tài)文件打包上傳至目標(biāo)服務(wù)器的指定目錄,并可以在此之后回調(diào)執(zhí)行一些用戶自定義指令。
但是他目前還有很多缺陷,比如:
對于shell腳本的執(zhí)行結(jié)果沒有進(jìn)行判斷,不知道是否執(zhí)行成功
遠(yuǎn)程文件夾采用直接覆蓋的方式,無法進(jìn)行版本控制,遇到問題無法快速回滾
...
這些問題,在以后的迭代中,根據(jù)實際會逐一解決。
三、npm發(fā)包初探
1. 注冊npm賬號 對于填寫的郵箱,一定要去驗證,否則發(fā)包的時候會報403
2. 創(chuàng)建新的文件夾,文件夾命名為你的包名,執(zhí)行npm init生成package.json文件
3. 如果是第一次發(fā)布,執(zhí)行npm adduser,否則執(zhí)行npm login
4. 進(jìn)入項目文件夾,執(zhí)行npm publish發(fā)布
5. 版本控制 執(zhí)行npm version [updateType]
- patch 補丁,小改動,版本最后一位數(shù)字加一
- minor 次要修改,次版本號加一
- major 主要修改,大改,版本首位加一
發(fā)包過程截圖:

四、總結(jié)
經(jīng)過npm init 到源碼構(gòu)建,再到npm publish,至此,我們已經(jīng)完成了deploy-helper前端部署小助手工具包的全部工作,它能幫我們壓縮上傳構(gòu)建產(chǎn)物到目標(biāo)服務(wù)器指定目錄,并進(jìn)行部署。我們不用再打開sftp工具,或者scp手動傳輸構(gòu)建產(chǎn)物了,在一定程度上簡化了我們的發(fā)布過程。正如我上面所說,小助手仍有很多問題需要進(jìn)一步解決,正因為任何事情都不是一蹴而就的,蜂巢、蟻巢都是一點一點的筑成的,小助手會在今后的迭代中逐步完善,提供更為簡潔和可靠的功能。
同時也歡迎大家給我提出寶貴的改進(jìn)意見,新人經(jīng)驗欠缺,文中若有不足的地方也望大家包涵!
以下是通過小助手發(fā)包的運行過程實例截圖:
