捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:專案腳本

畫面與功能做完後,現在需要一些指令來讓專案在跑起來。而如果一個專案沒有在 README 提到任何啟動資訊,通常工程師會往 package.json 中的 scripts 欄位尋找相關提示。而今天會分享一些個人設定 scripts 的方法。

本文

在 package.json scripts 設定腳本

鐵人賽第 4 天 有提到,個人習慣在專案的根目錄留一個 ./script 資料夾,目的是負責收納「啟動、打包專案的腳本」。而為了能在終端輸入 yarn start 就觸發啟動、打包流程,我的 package.json script 欄位會長這樣:

{
  "name": "ithome-2023",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "start": "node -r esbuild-runner/register ./script/start.ts"
  }
}

node -r esbuild-runner/register 代表「在 node 環境註冊 esbuild-runner/register 套件」,然後再執行 ./script/start.ts。更多關於 esbuild-runner 的說明可參考 官方 README 內容。基本上,這就是一個幫忙把 .ts 檔轉成 .js,好讓 Node.js 環境可以順利執行 .ts 檔的轉譯工具。

script/start.ts 的內容如下:

/* Packages */
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';

/* Data */
import devConfig, {
  devServerConfig,
} from './config/webpack.config.development';

/* Functions */
async function runApp() {
  const compiler = webpack(devConfig);
  const devServer = new WebpackDevServer(devServerConfig, compiler);
  await devServer.start();
}

/* Main */
runApp();

參考 webpack-dev-server API 以及 Compiler Instance 可得知:沒有餵 callback 給 webpack function 時,此功能會回傳一個 webpack.Compiler 實例(即上方程式碼中的 compiler)。

If you don’t pass the webpack runner function a callback, it will return a webpack Compiler instance.

將此實例與 webpack-dev-server 專用的 devServerConfig 設定檔做為參數來建構一個 WebpackDevServer 實例後,得到的 devServer 即可透過 .start() 來啟動本機伺服器、預覽專案內容。

script/start.ts 的任務非常單純,他負責的事情只是「根據開發者餵進來的 webpack 設定」來啟動一個本機伺服器。至於這個本機伺服器「會有哪些行為」,則是透過 ./src/config 的設定決定。

透過 Makefile 設定腳本

除了將腳本寫在 package.json scripts 中,還有另外一種選擇,那就是透過 makeMakefilemake 是一種組建自動化工具(build automation tool),搭配 Makefile 後,開發者可在終端輸入 make $(target) 來執行各種已經在 Makefile 內設定好的腳本。

以上方的 yarn start 為例,其 Makefile 版本如下:

# 開啟本機伺服器來運行此前端專案
.PHONY: start
start:
	node -r esbuild-runner/register ./script/start.ts

.PHONY: start 代表偵測到 make start 指令時,要執行下方的 node -r esbuild-runner/register ./script/start.ts 內容。使用 .PHONY 能優化效能、也能避免執行 make 腳本時撞名(比如資料夾中真的有一個叫做 start 的檔案,如果沒有 .PHONY: start 的話,就會變成該 start 檔案被執行)。

gnu.org > 4.6 Phony Targets: A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.


個人愛用的安裝腳本如下,先移除既有的 node_modules 資料夾後,再以「不更動 yarn.lock 內容」為前提進行套件安裝。並透過 --production=false 確保 devDependencies 套件不會被漏掉。

.PHONY: i
i:
	rm -rf node_modules && \
	yarn install --frozen-lockfile --production=false

以上腳本還可改寫為:

# 移除 node_modules 資料夾
.PHONY: delete-node-modules
delete-node-modules:
	rm -rf node_modules

# 在不更動 yarn.lock 內容的情況下,安裝全部的專案套件
.PHONY: i
i: delete-node-modules
	yarn install --frozen-lockfile --production=false

i: delete-node-modules 代表「執行 target i 之前,先執行 target delete-node-modules 的內容」。更多關於 target dependency 的內容可以參考 Makefile Cheat Sheet

提示:透過 # 字開頭的內容都會被視為註解。

gnu.org > 3.1 What Makefiles Contain: ‘#’ in a line of a makefile starts a comment. It and the rest of the line are ignored, except that a trailing backslash not escaped by another backslash will continue the comment across multiple lines.


有時候,你可能需要在執行腳本時引用 .env 檔案內的變數。先假設 .env 內容為:

# 指定打包後輸出的目的地資料夾
BUILD_DESTINATION=build

那麽在 Makefile 中,只要加上以下內容就能直接透過 $(BUILD_DESTINATION) 來取得 .env 中的變數內容(即 build):

ifneq (,$(wildcard ./.env))
    include .env
    export
endif

# 移除打包檔案
.PHONY: remove-build
remove-build:
	rm -f -r $(BUILD_DESTINATION)

ifneq (,$(wildcard ./.env)) 代表「wildcard ./.env 回傳的內容是否為空字串」,如果「不是空字串」,則透過 include .env / export 來「引入、並將其中的變數匯出」,於是開發者可以透過 $(BUILD_DESTINATION) 來取得 .env 中的內容了。

完整的 syntax 說明可參考官方文件:


不過,如果想要透過 Makefile 來執行 jest -u(更新 snapshots 內容) 或 jest path/to/specific/file.test.ts(指定測試特定檔案)這類需要傳入 command line flag 的腳本時,個人認為 make 就比較不直覺了:

# 執行單元測試
.PHONY: test
test:
	yarn jest $(option)

此時需要在終端輸入 make test option=-umake test option=path/to/specific/file.test.ts 才能滿足效果。

提示:關鍵字 option 可換成任意單字,這邊僅是透過 option 一詞來將終端機取得的內容轉交給 make 而已。

結論

使用 Makefile 的好處是:能寫註解、可對腳本進行換行排版、簡單幾行 code 就能引用 .env 檔的內容。缺點是在執行 jest 這類經常搭配 command line flag 的套件時,需要額外透過自訂關鍵字來取得 flag 資料。

大原則是將腳本集中一處管理,選定使用 scriptsMakefile 後,就盡量把專案中所有的腳本都收納在選好的地方、不要再到處散落了。

以上就是個人對 package.json scriptsmake / Makefile 的經驗分享,感謝你的閱讀 ( •̀ω•́)