一天一夜,寫完了這份高效組織 Npm Script 優(yōu)秀實踐
本文轉載自微信公眾號「全棧成長之路」,作者山月行。轉載本文請聯系全棧成長之路公眾號。
用了兩天,終于把這篇文章趕完了。有興趣的可以加我微信好友 shanyue94 進行交流。這篇文章也可以在山月的博客上找到,正文開始
眾所周知,一個 Javasript 項目的腳本類工具,可以使用 package.json 中的 scripts 字段來組織,簡單來說,這就是 npm script。
最典型最常用約定俗成的一個是 npm start,用以啟動項目:
- {
- "scripts": {
- "start": "next"
- }
- }
約定速成的還有很多,如下所列
- npm install
- npm test
- npm publish
- ...
約定速成的親兒子腳本自然和其它第三方腳本不一樣,如果需要執(zhí)行它,直接使用 npm 前綴即可,如 npm start,那其它腳本呢?那就需要 npm run 前綴了。而 yarn 就沒這么多講究了,一視同仁。
- $ npm run <user defined>
- $ npm run-script dev
- # 為了簡單方便,等同于
- $ npm run dev
- # yarn
- $ yarn dev
以上是眾所周知的,以下講一講有可能不是眾所周知的
運行: npm run dev 與 npm start 的區(qū)別
對于一個「純生成靜態(tài)頁面打包」的前端項目而言,它們是沒有多少區(qū)別的:生產環(huán)境的部署只依賴于構建生成的資源,更不依賴 npm scripts。可見 如何部署前端項目[1]。
使用 create-react-app 生成的項目,它的 npm script 中只有 npm start
- {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
- }
使用 vuepress 生成的項目,它的 npm script 中只有 npm run dev
- {
- "dev": "vuepress dev",
- "build": "vuepress build"
- }
在一個「面向服務端」的項目中,如 next、nuxt 與 nest。dev 與 start 的區(qū)別趨于明顯,一個為生產環(huán)境,一個為開發(fā)環(huán)境
dev: 在開發(fā)環(huán)境啟動項目,一般帶有 watch 選項,監(jiān)聽文件變化而重啟服務,此時會耗費大量的 CPU 性能,不宜放在生產環(huán)境
start: 在生產環(huán)境啟動項目
在 nest 項目中進行配置
- {
- "start": "nest start",
- "dev": "nest start --watch"
- }
運行:Script Hooks -> 如何把新項目快速跑起來
新人入職新上手項目,如何把它跑起來,這是所有人都會碰到的問題:所有人都是從新手開始的。
有可能你會脫口而出:npm run dev/npm start,但實際工作中,處處藏坑,往往沒這么簡單。
- 查看是否有 CI/CD,如果有跟著 CI/CD 部署的腳本跑命令
- 查看是否有 dockerfile,如果有跟著 dockerfile 跑命令
- 查看 npm scripts 中是否有 dev/start,嘗試 npm run dev/npm start
- 查看是否有文檔,如果有跟著文檔走。為啥要把文檔放到最后一個?原因你懂的
但即便是十分謹慎,也有可能遇到以下幾個叫苦不迭、浪費了一下午時間的坑:
- 前端有可能在「本地環(huán)境啟動時需要依賴前端構建時所產生的文件」,所以有時需要「先正常部署一遍,再試著按照本地環(huán)境啟動 (即需要先 npm run build 一下,再 npm run dev/npm start)」。(比如,一次我們的項目 npm run dev 時需要 webpack DllPlugin 構建后的東西)
- 別忘了設置環(huán)境變量或者配置文件
因此,設置一個少的 script,可以很好地避免后人踩坑,更重要的是,可以避免后人罵你
- {
- "scripts": {
- "start": "npm run dev",
- "config": "node assets && node config",
- "build": "webpack",
- // 設置一個 dev 的鉤子,在 npm run dev 前執(zhí)行,此處有可能不是必須的
- "predev": "npm run build",
- "dev": "webpack-dev-server --inline --progress"
- }
- }
Hooks
在 npm script 中,對于每一個命令都有 Pre/Post 鉤子,分別在命令執(zhí)行前后執(zhí)行
- npm run <script>
- pre<script>
- <script>
- post<script>
在工作中,這些鉤子與內置的命令為項目提供了簡便的操作方式,也提供了更安全的項目操作流程
- 裝包之后,進行 husky(v5.0) 的設置
- 打包之前,清理目標文件件
- 發(fā)包之前,進行打包構建
- 運行之前,準備好資源文件
- {
- "scripts": {
- "postinstall": "husky install",
- "prebuild": "rimraf dist",
- "build": "webpack",
- "predev": "npm run assets",
- "dev": "webpack-dev-server --inline --progress"
- }
- }
構建
構建打包,基本上所有的項目都含有這個命令,并且默認為 npm run build。
在 CI 或前端托管平臺 Vercel/Netlify 中,對于部署前端項目,最重要的一步就是打包。但是有些項目有可能不需要打包,此時可以使用 if-present 參數,代表如果存在該 script,則執(zhí)行,否則跳過
- $ npm run --if-present build
- {
- "scripts": {
- "build": "next build"
- }
- }
測試: Script 后綴
對于完成一件極為復雜的事情,可以使用前綴進行分組組織 npm script,比如測試。
- npm run test: 使用 mocha[2] 進行單元測試
- npm run test:coverage: 使用 nyc[3] 查看單元測試覆蓋率
- npm run test:e2e: 使用 cypress[4] 進行 UI 自動化測試
- {
- "test": "mocha",
- "test:coverage": "nyc npm test",
- "test:e2e": "npm run cy:run --",
- "cy:run": "cypress run --config-file cypress/config.json",
- "cy:open": "cypress open --config-file cypress/config.json"
- }
對于測試而言,mocha 與 nyc 結合可以很好地進行單元測試,并提供覆蓋率報告。
對于前端 e2e 測試而言,cypress 與 puppeteer 無疑是最流行的框架。
那如何對 Vue/React 組件進行更好地測試及文檔呢?
組件測試:
storybook[5] 可以更好地對 React/Vue 組件進行調試、測試并形成幫助文檔。開發(fā)基礎組件庫時,可以配置 npm run storybook 進行更好的測試
- $ npm run storybook
- {
- "scripts": {
- "storybook": "start-storybook -p 9001 -c .storybook",
- "storybook:build": "build-storybook -c .storybook -o .out",
- "prepublishOnly": "npm run build"
- }
- }
如何使用 storybook
格式化: Prettier
Prettier[6] 是一款支持多種編程語言,如 html、css、js、graphql、markdown 等并且可與編輯器 (vscode) 深度集成的代碼格式化工具。
支持多種編程語言
在 npm script 中配置代碼格式化如下所示:對 js、css、json、markdown 進行格式化
- {
- "scripts": {
- // 配置文件: .prettierrc
- // 格式化: --write
- // 文件: *.js
- "prettier": "prettier --config .prettierrc --write {.,components,lib,pages}/*.{js,css,json,md}",
- }
- }
.prettierrc 是 prettier 的配置文件,一般是比較簡單的配置,可供配置的 Prettier Options[7] 也沒有很多。
- {
- "singleQuote": true,
- "printWidth": 100,
- "semi": false,
- "arrowParens": "avoid"
- }
Lint: 代碼格式化及質量檢查
那 Prettier 與 ESLint/StyleLint/TSLint 有什么區(qū)別?
Prettier 僅僅作代碼的格式化,如空格、是否添加分號之類。而 ESLint 之類對代碼格式化外,還對代碼進行「質量檢查」,如 no-unused-vars, no-implicit-globals 等規(guī)則。
JS 與 TS 的質量檢查,還是要看 eslint[8]。
- {
- "scripts": {
- "lint": "eslint .",
- "lint:fix": "eslint . --fix"
- }
- }
除了 eslint 之外,還可以對 markdown、gitcommit 進行格式化
- markdownlint[9]
- commitlint[10]
Git: 你好,代碼不合格,這里禁止提交
你的代碼不合格,為了避免你被他人吐槽,這里不允許提交。這時候 Git Hooks 就派上了用場。
Git Hooks 中的 precommit hook 會在代碼提交之前執(zhí)行腳本,如果腳本不通過 (Exit Code 不是 0),則禁止提交。
husky[11] 與 lint-staged[12] 是 Git Hooks 的最佳搭配。
- {
- "scripts": {
- "lint": "eslint .",
- "prettier": "prettier --config .prettierrc --write {.,components,lib,pages}/*.{js,css,json,md}",
- },
- "husky": {
- "hooks": {
- "pre-commit": "lint-staged"
- }
- },
- "lint-staged": {
- "*.js": [
- "npm run lint"
- ],
- "*.{js,css,json,md}": [
- "npm run prettier"
- ]
- }
- }
Outdated: 你的依賴已過期
當一個庫過期了會怎么樣?
- 找不到文檔,無處下手
- 經常有 Bug 由過期庫引起,很難修復
- 存在安全風險
沒有人會喜歡過期的庫。
使用 npm outdated 可以發(fā)現 package.json 中依賴的過期庫
- $ npm outdated
- Package Current Wanted Latest Location Depended by
- @vuepress/plugin-google-analytics 1.7.1 1.8.2 1.8.2 node_modules/@vuepress/plugin-google-analytics blog
- axios 0.21.0 0.21.1 0.21.1 node_modules/axios blog
- dayjs 1.9.6 1.10.4 1.10.4 node_modules/dayjs blog
- graphql 15.4.0 15.5.0 15.5.0 node_modules/graphql blog
- koa 2.13.0 2.13.1 2.13.1 node_modules/koa blog
- npm-check-updates 10.2.2 10.3.1 11.3.0 node_modules/npm-check-updates blog
- vuepress 1.7.1 1.8.2 1.8.2 node_modules/vuepress
但是 npm outdated 并不好用,比如如何一鍵升級?就像應用商店升級所有手機軟件一樣。
node-check-updates 是加強版的 npm outdated,它最簡單的功能是一鍵升級,細化功能是升級策略與安全升級。ncu 是它的二進制命令
- $ ncu
- Checking package.json
- [====================] 5/5 100%
- express 4.12.x → 4.13.x
- multer ^0.1.8 → ^1.0.1
- react-bootstrap ^0.22.6 → ^0.24.0
- react-a11y ^0.1.1 → ^0.2.6
- webpack ~1.9.10 → ~1.10.5
- Run ncu -u to upgrade package.json
使用 ncu --doctor,在升級每一個依賴時會對項目進行測試,如果測試通過則安裝依賴成功,否則回退到原先版本
- $ ncu --doctor -u
- npm install
- npm run test
- ncu -u
- npm install
- npm run test
- Failing tests found:
- /projects/myproject/test.js:13
- throw new Error('Test failed!')
- ^
- Now let's identify the culprit, shall we?
- Restoring package.json
- Restoring package-lock.json
- npm install
- npm install --no-save react@16.0.0
- npm run test
- ✓ react 15.0.0 → 16.0.0
- npm install --no-save react-redux@7.0.0
- npm run test
- ✗ react-redux 6.0.0 → 7.0.0
- Saving partially upgraded package.json
在 npm script 中進行配置 ncu:
- {
- "scripts": {
- "ncu": "ncu"
- }
- }
Audit: 你的依賴存在安全風險
當某一個 package 存在安全風險時,這時候就要小心了,畢竟誰也不想自己的網站被攻擊。唯一的解決辦法就是 package 升級版本。就像 Github 的機器人這樣:
Github 機器人風險提示并提交 PR
那使用 ncu 把所有依賴包升級到最新還會有安全風險嗎?
會有,因為 ncu 只會把 package.json 中的依賴升級到最新,而不會把 lock file 中的依賴升級到最新。
npm audit 可以發(fā)現項目中的風險庫,并使用 npm audit fix 進行修復。
然而美中不足,npm audit 的精準度沒有 yarn audit 高。
再美中不足,yarn audit 并不支持 yarn audit fix 自動修復
- $ npm audit
- $ npm audit fix
snyk 是一個檢查包風險的一個服務,他提供了命令行工具檢測風險,可以使用它代替 npm audit。他也有缺陷,依賴一個服務,可以根據容器自建或者使用 SASS。
- {
- "scripts": {
- "audit": "snyk test",
- "audit:fix": "snyk protect"
- }
- }
Size: 控制你的 bundle 大小
size limit[13] 與 bundle size[14] 都是可以控制 bundle 體積的兩個工具,不過 size-limit 對啟動時間也有更強的支持。
- {
- "scripts": {
- "size": "size-limit",
- "analyze": "size-limit --why"
- },
- "size-limit": [
- {
- "path": "dist/promise-utils.cjs.production.min.js",
- "limit": "10 KB"
- },
- {
- "path": "dist/promise-utils.esm.js",
- "limit": "10 KB"
- }
- ]
- }
總結
在工作中高效使用 npm script,可以極高效率與代碼質量,本文中涉及到的 package 如下所示
- husky[15]
- mocha[16]
- nyc[17]
- cypress[18]
- puppeteer[19]
- storybook[20]
- prettier[21]
- eslint[22]
- markdownlint[23]
- @commitlint/cli[24]
- lint-staged[25]
- husky[26]
- npm-check-updates[27]
- lerna[28]
- size-limit[29]
- bundle-size[30]
你可以在 npm devtool[31] 中找到更多有趣有用的庫
Reference
[1]如何部署前端項目:
https://shanyue.tech/frontend-engineering/docker.html
[2]mocha:https://npm.devtool.tech/mocha
[3]nyc:https://npm.devtool.tech/nyc
[4]cypress:https://npm.devtool.tech/cypress
[5]storybook:https://storybook.js.org/
[6]Prettier:https://npm.devtool.tech/prettier
[7]Prettier Options:https://prettier.io/docs/en/options.html#parser
[8]eslint:https://npm.devtool.tech/eslint
[9]markdownlint:https://npm.devtool.tech/markdownlint
[10]commitlint:https://www.npmjs.com/package/@commitlint/cli
[11]husky:https://npm.devtool.tech/husky
[12]lint-staged:https://github.com/okonet/lint-staged
[13]size limit:https://github.com/ai/size-limit/
[14]bundle size:https://github.com/siddharthkp/bundlesize
[15]husky:https://npm.devtool.tech/husky
[16]mocha:https://npm.devtool.tech/mocha
[17]nyc:https://npm.devtool.tech/nyc
[18]cypress:https://npm.devtool.tech/cypress
[19]puppeteer:https://npm.devtool.tech/puppeteer
[20]storybook:https://npm.devtool.tech/storybook
[21]prettier:https://npm.devtool.tech/prettier
[22]eslint:https://npm.devtool.tech/eslint
[23]markdownlint:https://npm.devtool.tech/markdownlint
[24]@commitlint/cli:https://npm.devtool.tech/@commitlint/cli
[25]lint-staged:https://npm.devtool.tech/lint-staged
[26]husky:https://npm.devtool.tech/husky
[27]npm-check-updates:https://npm.devtool.tech/npm-check-updates
[28]lerna:https://npm.devtool.tech/lerna
[29]size-limit:https://npm.devtool.tech/size-limit
[30]bundle-size:https://npm.devtool.tech/bundle-size
[31]npm devtool:https://npm.devtool.tech