NodeJS發光發熱之打包hooks

NodeJS發光發熱之打包hooks

讓NodeJS在項目中發光發熱系列文章, 沒看過這個的朋友建議先看一下,不然直接這個可能會有點吃力。

寫在前面

最近離職了,閒着也是閒着。就想起來了之前Node相關的文章還有一部分沒寫,剛好有時間,今天給續上。現如今的前端開發,通過Node可以高度自定義的爲我們的項目打造一條龍服務。既然一條龍那麼不僅僅是開發階段,打包之後的事情,我們也要處理。這篇文章就聊一聊打包之後的一些用得上的hooks。同樣的這篇文章也是拋磚引玉的作用,文章裏涉及的都是一些簡單的部分,深層次的騷操作還要各位朋友自己去挖掘。

首先來看一下大致的效果

![](./images/build-hooks.png '打包hooks')

準備工作

準備一個項目,Vue/React/Angular的都可以,我們這裏以vue-base-template爲項目模板

改造打包命令

因爲我使用的是Vue-cli3.0版本的腳手架,它打包的命令是通過vue-cli-service build命令來執行。npm直接運行的話我們就沒法加hooks了, 所有我們自己寫一個Node腳本來執行打包命令, 在scripts文件夾下新建build.js,這個腳本用來組織我們所有的打包相關東西。

build.js的職責其實很簡單:

  1. 組織打包參數
  2. 運行打包命令
  3. 檢測打包完成之後運行自定義hooks

有了需求再去寫功能,對大家來說都是小case,下面就來實現這三個需求。

  1. 打包參數

這個參數就是我們在命令行中輸入的參數,也就是package.json scripts中寫好的參數,例如

"scripts": {
  "build": "node scripts/build.js --mode production"
}

這裏我們暫時不對參數做特殊的處理,僅僅取出來給打包命令用,如果你需要複雜的參數建議你使用commander.js,我這個項目現在還沒涉及到複雜的打包參數。所以直接使用process.argv取出來就行了,關於process具體使用感興趣可以自己看看文檔

const scriptArgv = process.argv.slice(2) // 刪除node scripts/build.js
const args = scriptArgv.join(' ') // 組裝成正常格式
  1. 運行命令

這個對Node來說,這個很簡單。通過child_process就能完美實現,我比較懶,直接用了tasksfile這個庫。你也可以自己採用原生Node寫,不過要注意異步的問題。

const { sh } = require('tasksfile')
// 同步執行打包命令
sh(`vue-cli-service build ${args}`, {
  silent: false
})
  1. 打包完成執行Hooks,這就是執行個方法。

下面是build.js中全部的代碼。

const ora = require('ora')
const { sh } = require('tasksfile')
const { Notify } = require('./util') 
const builtHooks = require('./build-hooks')
const scriptArgv = process.argv.slice(2)
const args = scriptArgv.join(' ')

const spinner = ora(`building for ${process.env.NODE_ENV}...\n`)
spinner.start()
// real pack command
sh(`vue-cli-service build ${args}`, {
  silent: false
})
// build success
spinner.succeed("打包完成")
// notify
Notify.showNotify("打包完成", "即將進行下一步操作")
// delay 2s
setTimeout(() =>{
  // run hooks
  builtHooks()
},2000)

自定義hooks

這個發揮的空間有點大,大部分都是爲了提升我們的效率的,我就寫幾個我自己認爲常用一點的。

  1. 發佈到服務器
  2. 本地預覽
  3. 生成Zip文件
  4. 備份Zip文件到本地

在執行完build.js的前兩步之後,接着就會運行hooks。因爲hooks腳本存在的目的是爲開發者最大可能性的節約時間。所以就設計爲了非強制性的選項。

首先新建文件build-hooks.js

第一步設計hooks選項,同樣的使用我們的老朋友inquirer

const builtHooks = () => {
  inquirer.prompt([
    {
      type: 'list',
      message: `檢測到production環境打包完成,請選擇下一步操作`,
      name: 'next',
      choices: [
        {
          name: '退出腳本',
          value: 0
        },
        {
          name: '發佈到服務器',
          value: 1
        },
        {
          name: '本地預覽',
          value: 2
        },
        {
          name: '生成Zip文件',
          value: 3
        },
        {
          name: '備份Zip文件到本地',
          value: 4
        }
      ]
    }
  ]).then(answers => {
    afterHooks.get(answers.next)()
  })
}

第二步設計不同選項對應的行爲

const afterHooks = new Map([
  [0, () => {
    Log.logger('退出程序')
    process.exit(0)
  }],
  [1, () => {
    Log.logger('即將進行發佈🎈')
    require('./deploy')
  }],
  [2, () => {
    Log.logger('開始本地預覽💻')
    require('./server')
  }],
  [3, async () => {
    Log.logger('開始壓縮zip文件👜')
    await FileUtil.zipDir()
  }],
  [4, async () => {
    Log.logger('開始備份Zip文件到本地📦')
    await Backup.doBackup()
  }]
])

是不是非常簡單,簡潔易懂哈哈哈。 下面我們來看一下具體的操作實現細節

發佈到服務器🎈

這個在這裏就不說了, 可以直接看這裏

本地預覽💻

簡單問題了,無非是在本地搭建一個輕量級的Web服務器, 用第三方庫實現的話就更簡單了。代碼如下server.js

const http = require('http')
const fs = require('fs')
const path = require('path')
const { Log } = require('./util')
const httpPort = 8088
const filePath = path.resolve(__dirname, '../dist/index.html')
const open = require('open')
// create current http server
http.createServer((req, res) => {
  Log.logger(req.url)
  try {
    const content = fs.readFileSync(filePath, 'utf-8')
    // deal resource
    if (req.url.indexOf('static') !== -1 || req.url.indexOf('vendor') !== -1 || req.url == "/favicon.ico") {
      const data = fs.readFileSync(path.resolve(__dirname, `../dist${req.url}`))
      return res.end(data)
    }
    // index.html
    res.setHeader('Content-Type','text/html;charset=utf-8')
    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })
  res.end(content)
  } catch (error) {
    Log.error('We cannot open "index.htm" file.')
  }
}).listen(httpPort, async () => {
  const location = `http://localhost:${httpPort}`
  Log.success(`Server listening on: ${location}, Open in the browser after 3 seconds`)
  // open default browser
  setTimeout(async ()=> {
    // 自動打開默認瀏覽器
    await open(location)
  }, 3000)
})

效果如下:

![](./images/preview.png '')

壓縮zip文件👜

這個也沒什麼好說的😂, 就是通過Node壓縮dist文件夾,在這裏我用了[zip-local]()來實現需求(你也可以使用Node實現, 但是我比較懶哈哈哈)代碼如下

/**
   * 壓縮文件夾
   * @param {*} dir 要壓縮的文件夾 默認 ROOTPATH.distDir
   * @param {*} zipedPath 壓縮之後 的zip存放路徑
   */
  static async zipDir (dir = ROOTPATH.distDir, zipedPath = ROOTPATH.distZipPath) {
    try {
      if(fs.existsSync(zipedPath)) {
        Log.logger('zip已經存在, 即將刪除壓縮包')
        fs.unlinkSync(zipedPath)
      } else {
        Log.logger('即將開始壓縮zip文件')
      }
      await zipper.sync.zip(dir).compress().save(zipedPath);
      Log.success('文件夾壓縮成功')
    } catch (error) {
      Log.error(error)
      Log.error('壓縮dist文件夾失敗')
    }
  }

備份Zip文件到本地📦

壓縮+備份(就是複製一份文件到指定文件夾)

我在深思熟慮之後還是決定把備份給加上,我反正是吃過沒備份的虧😂, 這個選項會爲我們在backups文件目錄下生成一個新的以當前日期命名的壓縮文件。

熟悉Node的文件系統的話這個對大家來說就很簡單了。backup.js代碼如下:

const path = require('path')
const { FileUtil, StringUtil, DateUtil, ROOTPATH, Log, Notify } = require('./util')

class Backup {
  /**
   * @author: etongfu
   * @description: 清空所有備份
   */
  static clearBackups () {

  }
  /**
   * @author: etongfu
   * @description: 執行備份
   * @param {type}  {*}
   * @returns:  {*}
   */
  static async doBackup () {
    try {
      // 文件名是當前日期 精確到秒
      let date = StringUtil.trim(DateUtil.getCurrentDate("YYYY-MM-DD hh:mm:ss"), 1)
      date = StringUtil.replaceAll(date,"-", "")
      date = StringUtil.replaceAll(date,":", "")
      let targetPath = path.resolve(__dirname, `../backups/${date}.backup.zip`)
      if(FileUtil.fileExist(targetPath)) {
        return Log.warning(`${targetPath}已存在,已放棄備份`)
      }
      // Zip File
      await FileUtil.zipDir(ROOTPATH.distDir, targetPath)
      Log.success(`本地備份完成, 文件:${targetPath}`)
      Notify.showNotify("本地備份", `本次備份完成, 文件地址:${targetPath}`)
    } catch (error) {
      Log.error(`備份文件失敗:${error}`)
    }
  }
}
module.exports = Backup

效果如下:

![](./images/backup.png '')

總結

其實hooks完全不必和打包耦合在一起,完全可以拆出來使用,不過這個是下一個版本的需求哈哈哈😀。完這些再親身使用了一段時間之後,真的感覺可以節省下來不少時間。免去了一些繁瑣的手工操作。推薦大家嘗試一下。

示例代碼

原文地址 如果覺得有用得話給個⭐吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章