NodeJS發光發熱之打包hooks
讓NodeJS在項目中發光發熱系列文章, 沒看過這個的朋友建議先看一下,不然直接這個可能會有點吃力。
寫在前面
最近離職了,閒着也是閒着。就想起來了之前Node
相關的文章還有一部分沒寫,剛好有時間,今天給續上。現如今的前端開發,通過Node
可以高度自定義的爲我們的項目打造一條龍服務。既然一條龍那麼不僅僅是開發階段,打包之後的事情,我們也要處理。這篇文章就聊一聊打包之後的一些用得上的hooks
。同樣的這篇文章也是拋磚引玉的作用,文章裏涉及的都是一些簡單的部分,深層次的騷操作還要各位朋友自己去挖掘。
首先來看一下大致的效果
準備工作
準備一個項目,Vue/React/Angular
的都可以,我們這裏以vue-base-template
爲項目模板
改造打包命令
因爲我使用的是Vue-cli3.0
版本的腳手架,它打包的命令是通過vue-cli-service build
命令來執行。npm
直接運行的話我們就沒法加hooks
了, 所有我們自己寫一個Node
腳本來執行打包命令, 在scripts
文件夾下新建build.js
,這個腳本用來組織我們所有的打包相關東西。
build.js
的職責其實很簡單:
- 組織打包參數
- 運行打包命令
- 檢測打包完成之後運行自定義
hooks
有了需求再去寫功能,對大家來說都是小case,下面就來實現這三個需求。
- 打包參數
這個參數就是我們在命令行中輸入的參數,也就是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(' ') // 組裝成正常格式
- 運行命令
這個對Node
來說,這個很簡單。通過child_process
就能完美實現,我比較懶,直接用了tasksfile這個庫。你也可以自己採用原生Node
寫,不過要注意異步的問題。
const { sh } = require('tasksfile')
// 同步執行打包命令
sh(`vue-cli-service build ${args}`, {
silent: false
})
- 打包完成執行
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
這個發揮的空間有點大,大部分都是爲了提升我們的效率的,我就寫幾個我自己認爲常用一點的。
- 發佈到服務器
- 本地預覽
- 生成Zip文件
- 備份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)
})
效果如下:
壓縮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
效果如下:
總結
其實hooks完全不必和打包耦合在一起,完全可以拆出來使用,不過這個是下一個版本的需求哈哈哈😀。完這些再親身使用了一段時間之後,真的感覺可以節省下來不少時間。免去了一些繁瑣的手工操作。推薦大家嘗試一下。
原文地址 如果覺得有用得話給個⭐吧