前端自動化部署&npm發(fā)包初探

一、前言

在實習(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-helper.png

配置項 .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

npm官網(wǎng)

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ā)包過程截圖:


發(fā)包過程截圖.png

四、總結(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ā)包的運行過程實例截圖:


運行過程截圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容