在雲端 Mac 上驗收 Node.js 原生模組的 Apple Silicon 建置

在雲端 Mac 上驗收 Node.js 原生模組的 Apple Silicon 建置

Node.js 原生模組能在開發機上載入,不代表它能在 Apple Silicon 雲端 Mac 上穩定交付。快取中的預編譯檔案、未納入發佈套件的建置產物,以及錯誤的動態函式庫路徑,都可能讓 npm install 看似成功,卻在乾淨機器上第一次執行 require() 時失敗。更可靠的做法,是將原始碼重建、二進位檢查、封裝檢查與隔離安裝串成一條完整的驗收鏈。

先固定驗收基準

開始前先記錄執行環境,不要直接清除快取並重新安裝。Node.js 原生模組可能使用 N-API,也可能依賴特定的模組 ABI;兩者的相容範圍並不相同。專案應透過 .nvmrc.node-version 或 CI 參數固定 Node.js 主要版本,並將 npm 版本一併寫入日誌。

uname -m
sw_vers
node --version
npm --version
node -p "process.versions.napi || 'no-napi'"
node -p "process.versions.modules"
xcode-select -p
clang --version

在 Apple Silicon 環境中,uname -m 應回傳 arm64。如果終端機程序本身是在其他架構下執行,後續建置結果就無法代表目標環境。還要確認 xcode-select -p 指向預期的工具鏈,以免系統更新後,命令列工具在未察覺的情況下切換。

驗收的對象不是「目前目錄能否執行」,而是「能否從儲存庫原始碼產生符合目標架構,且可由最終發佈套件載入的產物」。

在乾淨工作區強制從原始碼重建

不要在長期使用的工作目錄中進行發佈驗收。先簽出指定的提交,再移除相依套件目錄與模組本身的建置目錄。如果專案依賴安裝指令碼產生 .node 檔案,就必須保留完整的安裝日誌。

git status --short
git rev-parse HEAD
rm -rf node_modules build
npm cache verify
npm ci
npm rebuild --build-from-source

npm cache verify 只會檢查快取結構,無法證明建置過程未使用快取,因此關鍵步驟仍是 --build-from-source。如果專案有明確的建置指令碼,還應執行 npm run build,而不是假設 npm rebuild 已涵蓋所有產生步驟。

發生失敗時,應先保留完整輸出,再核對 Python、編譯器、SDK 路徑與標頭檔。不要一遇到錯誤就刪除所有快取;無差別清理會破壞問題現場,也無法判斷問題究竟來自工具鏈、原始碼還是安裝指令碼。

區分 N-API 與模組 ABI

使用 N-API 的模組應記錄其宣告的最低 N-API 版本,並在目標 Node.js 版本上執行載入測試。直接依賴模組 ABI 的擴充功能通常需要針對不同的 Node.js 主要版本分別建置,不能將某個版本產生的二進位檔複製到另一個版本繼續使用。

檢查 Mach-O 架構與動態相依項目

建置完成後,先找出所有原生二進位檔,不要只檢查慣用路徑中的第一個檔案。

find . -type f -name '*.node' -not -path './node_modules/.cache/*' -print0 |
  xargs -0 file

find . -type f -name '*.node' -not -path './node_modules/.cache/*' -print0 |
  while IFS= read -r -d '' binary; do
    echo "== $binary"
    lipo -info "$binary"
    otool -L "$binary"
  done

當目標為 Apple Silicon 時,每個用於交付的 .node 檔案都應包含 arm64。只有在發佈範圍明確涵蓋兩種架構時,才需要產生通用二進位檔;不要為了形式而合併兩個未經同等測試的切片。

檢查 otool -L 時,重點是找出開發機上的絕對路徑、暫存目錄,以及未隨套件交付的自訂動態函式庫。系統框架路徑通常可以保留,但指向個人目錄或建置工作區的參照必須在發佈前修正。如果模組附帶 .dylib,還要確認它確實已納入封裝清單,並使用可移植的載入路徑。

驗收實際要發佈的 npm 套件

直接在原始碼目錄中執行測試,會讓未宣告的檔案、相對路徑和殘留產物參與載入。發佈前應先產生 tarball,再到空白目錄中安裝。

rm -rf .package-check
mkdir .package-check

npm pack --json > .package-check/pack.json
PACKAGE_FILE="$(node -e "
const fs=require('fs');
const data=JSON.parse(fs.readFileSync('.package-check/pack.json','utf8'));
process.stdout.write(data[0].filename);
")"

tar -tzf "$PACKAGE_FILE"
cd .package-check
npm init -y >/dev/null
npm install "../$PACKAGE_FILE"
node -e "const addon=require('your-module'); console.log(typeof addon)"

your-module 替換成實際套件名稱,並把最後一行擴充為最小可行的業務呼叫。冒煙測試至少要觸發一次原生函式,而不能只驗證 JavaScript 進入點檔案存在。如果模組同時提供同步與非同步介面,兩者都應各執行一次,並檢查程序結束碼。

同時檢查 tar -tzf 的輸出:授權條款、型別宣告、JavaScript 進入點、目標 .node 檔案及必要的動態函式庫都應存在;原始碼快取、測試憑證、暫存日誌與本機設定則不應被打包。

將常見故障轉化為發佈門檻

建議將驗收拆分成彼此獨立的失敗條件,避免籠統的「建置失敗」掩蓋根本原因:

  1. 環境門檻:機器架構、Node.js、npm、N-API 或 ABI 與宣告不一致時停止。
  2. 重建門檻:刪除現有產物後,必須能從原始碼成功建置並產生新的 .node 檔案。
  3. 架構門檻:每個交付用二進位檔都必須包含目標 arm64 切片。
  4. 相依性門檻:不得參照工作區、個人目錄或未封裝的動態函式庫。
  5. 套件內容門檻:以 npm pack 的結果為準,而非原始碼目錄。
  6. 執行門檻:在隔離目錄中安裝 tarball,並執行至少一次原生 API 呼叫。

在 DPLYMAC 上執行這套流程時,可以將每次工作的環境清單、提交雜湊值、tarball 校驗值與冒煙測試輸出儲存為同一份建置證據。如此一來,即使之後升級 Node.js 或編譯工具,也能判斷差異究竟發生在環境、編譯階段、封裝階段還是執行階段。

最終交付標準應該很簡單:乾淨簽出後可以重建、二進位架構正確、動態相依項目可移植、發佈套件內容完整,而且該發佈套件能在隔離環境中完成真實呼叫。只有滿足這五點,才能算是原生模組通過 Apple Silicon 驗收。

常見問題

為什麼不能只用 npm install 成功作為驗收結果?

安裝可能使用快取或已下載的預編譯檔。還要強制從原始碼重建,並在隔離目錄安裝最終發佈包。

Apple Silicon 環境至少要檢查哪些項目?

至少檢查 Node.js 與 npm 版本、N-API 或模組 ABI、.node 檔案的 arm64 架構、動態函式庫路徑及最小 API 呼叫。

何時需要建立通用二進位檔?

只有發佈目標同時包含 arm64 與 x86_64 時才需要。若執行環境僅限 Apple Silicon,明確提供 arm64 即可。

雲端 Mac

將經驗驗證的工作流程部署至獨享實體節點

依工作選擇 M4 或 M4 Pro、目標節點與計費週期,下單前確認設定與附加選項。

選擇設定並下單