需要事先說明的是,本節部分內容涉及到非前端的話題,比如服務的部署、日志,但會從前端項目管理開始,比如依賴管理、版本管理等。即使對自己定位是純粹前端開發的同學,也建議閱讀下,因為技不壓身,了解整個前端項目交付流程中需要考量的點能讓我們更有大局觀。
通常來說,項目構建完成之后,就成為待發布的版本,因此版本管理需要考慮,甚至做成自動化的,然后,最新的代碼需要部署到線上機器才能讓所有用戶訪問到,部署環節涉及到服務的啟動、重啟、日志管理等需要考慮。
下面我們介紹 npm script 在服務運維時的幾個用途:
## 使用 npm script 進行版本管理
每次構建完的代碼都應該有新的版本號,修改版本號直接使用 npm 內置的 version 自命令即可,如果是簡單粗暴的版本管理,可以在 package.json 中添加如下 scripts:
```
+ "release:patch": "npm version patch && git push && git push --tags",
+ "release:minor": "npm version minor && git push && git push --tags",
+ "release:major": "npm version major && git push && git push --tags",
"precommit": "lint-staged",
```
這 3 條命令遵循 [semver](https://semver.org) 的版本號規范來方便你管理版本,patch 是更新補丁版本,minor 是更新小版本,major 是更新大版本。在必要的時候,可以通過運行 npm run version:patch 來升補丁版本,運行輸出如下:

如果要求所有的版本號不超過 10,即 0.0.9 的下個版本是 0.1.0 而不是 0.0.10,可以編寫簡單的 shell 腳本來實現(**注意這樣會破壞 semver 的約定**),具體步驟如下:
首先,在 scripts 目錄下新增 bump.sh(**別忘了文件的可執行權限**:chmod a+x scripts/bump.sh):
```
#!/usr/bin/env bash
# get major/minor/patch version to change
version=`cat package.json| grep version | grep -v release | awk -F\" '{print $4}'`
components=($(echo $version | tr '.' '\n'))
major=${components[0]}
minor=${components[1]}
patch=${components[2]}
release='patch';
# decide which version to increment
if [ $patch -ge 9 ]; then
if [ $minor -ge 9 ]; then
release='major'
else
release='minor'
fi
else
release='patch'
fi
echo "major=$major, minor=$minor, patch=$patch, release=$release"
# upgrade version
npm run release:$release
```
然后,在 package.json 中新增 bump 子命令:
```
"release:major": "npm version major && git push && git push --tags",
+ "bump": "scripty",
"precommit": "lint-staged",
```
在必要的時候執行 npm run bump,輸出示例如下:

## 使用 npm script 進行服務進程和日志管理
在生產環境的服務進程和日志管理領域,[pm2](http://pm2.keymetrics.io) 是當之無愧的首選,功能很強大,使用簡單,開發環境常用的是 [nodemon](https://www.npmjs.com/package/nodemon)。
在我們的項目中使用 npm script 進行服務進程和日志管理的基本步驟如下:
### 1\. 準備 http 服務
在使用 npm script 作為構建流水線的基礎上,我們在項目中引入了 [express](https://www.npmjs.com/package/express) 和 [morgan](https://www.npmjs.com/package/morgan),并使用如下腳本啟動 http 服務器方便用戶訪問我們的網頁(morgan 使用來記錄用戶的訪問日志的):
先安裝依賴:
```
npm i express morgan -D
# npm install express morgan --save-dev
# yarn add express morgan -D
```
然后在根目錄下創建文件 server.js,內容如下:
```
const express = require('express');
const morgan = require('morgan');
const app = express();
const port = process.env.PORT || 8080;
app.use(express.static('./dist'));
app.use(morgan('combined'));
app.listen(port, err => {
if (err) {
console.error('server start error', err); // eslint-disable-line
process.exit(1);
}
console.log(`server started at port ${port}`); // eslint-disable-line
});
```
### 2\. 準備日志目錄
為簡單起見,我們項目中創建日志存儲目錄 logs,有些公司可能不會把日志存在項目部署目錄下:
```
mkdir logs
touch logs/.gitkeep
git add logs/.gitkeep
git commit -m 'add logs folder'
```
并且設置該目錄為 git 忽略的,再改動 .gitignore:
```
dist
+logs
```
> **TIP#21**:這里加 logs/.gitkeep 空文件的目的是為了能把 logs 目錄提交到 git 里面,但是我們故意忽略 logs 目錄里面的內容,這是在 git 中提交目錄結構而忽略其中內容的常見做法。
### 3\. 安裝和配置 pm2
安裝 pm2 作為依賴:
```
npm i pm2 -D
# npm install pm2 --save-dev
# yarn add pm2 -D
```
然后添加服務啟動配置到項目根目錄下 pm2.json,更多配置項可以參照[文檔](http://pm2.keymetrics.io/docs/usage/application-declaration):
```
{
"apps": [
{
"name": "npm-script-workflow",
"script": "./server.js",
"out_file": "./logs/stdout.log",
"error_file": "./logs/stderr.log",
"log_date_format": "YYYY-MM-DD HH:mm:ss",
"instances": 0,
"exec_mode": "cluster",
"max_memory_restart": "800M",
"merge_logs": true,
"env": {
"NODE_ENV": "production",
"PORT": 8080,
}
}
]
}
```
上面的配置指定了服務腳本為 server.js,日志輸出文件路徑,日志時間格式,進程數量 = CPU 核數,啟動方式為 cluster,以及兩個環境變量。
### 4\. 配置服務部署命令
在沒有集成 CI 服務之前,我們的部署命令應該是下面這樣的:
```
"release:major": "npm version major && git push && git push --tags",
+ "predeploy": "yarn && npm run build",
+ "deploy": "pm2 restart pm2.json",
"bump": "scripty",
```
即在部署前需要安裝最新的依賴,重新構建,然后使用 pm2 重新啟動服務即可,如果你有多臺機器跑通1個服務,建議有個集中的 CI 服務器專門負責構建,而部署時就不需要運行 build 了。
每次需要部署服務時只需要運行 npm run deploy 就行了,運行成功輸出如下:

### 5\. 配置日志查看命令
至于日志,雖然 pm2 提供了內置的 logs 管理命令,如果某臺服務器上啟動了多個不同的服務進程,那么 pm2 logs 會展示所有服務的日志,個人建議使用如下命令查看當前服務的日志:
```
+ "logs": "tail -f logs/*",
"bump": "scripty",
```
需要查看日志時,直接運行 npm run logs,運行輸入如下:

當然如果你有更復雜的日志查看需求,直接用 cat、grep 之類的命令好了。
> **到這里,小冊的內容基本結束了,接下來的一周,我會準備好視頻版教程,在圣誕節的時候放出來給大家。如果你對內容有任何疑問,歡迎留言或者在讀者群里面交流**
* * *
> 本節用到的代碼見 [GitHub](https://github.com/wangshijun/automated-workflow-with-npm-script/tree/13-use-npm-script-for-devops),想邊看邊動手練習的同學可以拉下來自己改,注意切換到正確的分支 `13-use-npm-script-for-devops`。
* * *
> **視頻版教程已經錄制完畢,下載地址:鏈接: https://pan.baidu.com/s/1gfeZ619 密碼: xx8j,請享用**
* * *
- 為什么選擇 npm script
- 入門篇 01:創建并運行 npm script 命令
- 入門篇 02:運行多個 npm script 的各種姿勢
- 入門篇 03:給 npm script 傳遞參數和添加注釋
- 進階篇 01:使用 npm script 的鉤子
- 進階篇 02:在 npm script 中使用環境變量
- 進階篇 03:實現 npm script 命令自動補全
- 高階篇 01:實現 npm script 跨平臺兼容
- 高階篇 02:把龐大的 npm script 拆到單獨文件中
- 高階篇 03:用 node.js 腳本替代復雜的 npm script
- 實戰篇 01:監聽文件變化并自動運行 npm script
- 實戰篇 02:結合 live-reload 實現自動刷新
- 實戰篇 03:在 git hooks 中運行 npm script
- 實戰篇 04:用 npm script 實現構建流水線
- 實戰篇 05:用 npm script 實現服務自動化運維