建立一個雙模式跨執行時的 JavaScript 包

chuckQu發表於2023-12-26

本文將指導你釋出雙模式、跨執行時的 JavaScript 包。瞭解如何建立與 ESM 和 CommonJS 以及 Node.js、Deno 和瀏覽器等不同執行時相容的庫。

隨著 JavaScript 開發的不斷髮展,人們越來越需要能在多種環境中執行的強大依賴包。在本文中,我們將探討如何釋出跨執行時、雙模式的 JavaScript 包。這些包彌補了 ESM 和 CommonJS 之間的差距,讓開發人員可以在任何環境下使用相同的包和檔案。

在深入瞭解之前,讓我們先熟悉一些關鍵概念:

雙模式包

雙模式包旨在與多個 JavaScript 模組系統(尤其是 ES Modules (ESM) 和 CommonJS (CJS))配合使用。這確保了程式碼在各種環境中的可重用性和靈活性。建立雙模式包有幾個好處:

  • 更廣泛的相容性:並非所有專案都已過渡到使用 ESM。雙模式確保你的包可以在仍然依賴於 CommonJS 的專案中使用。
  • 無縫過渡:隨著 JavaScript 生態系統逐步轉向 ESM,雙模式包可確保使用者實現無縫過渡,而無需更換包或重構程式碼庫。
  • 減少維護:雙模式包允許使用者管理單一程式碼庫,而無需分別維護 ESM 和 CJS 包。

不過,雙模式並不能保證軟體包在不同的執行環境下都能正常工作,這就帶來了以下問題:

跨執行時包

跨執行時包可在 Deno、瀏覽器和 Node.js 等多種環境中執行。它們旨在為不同執行時提供一致的 API。一個全面的跨執行時包應同時支援 ESM 和 CJS,尤其是因為 Node.js 在很大程度上仍在使用 CommonJS。

如果我們忽略 Node.js 的傳統限制(Node.js 嚴重依賴 CommonJS),我們可以只使用 ES 模組構建跨執行時包。這將簡化包,但會限制其與舊版 Node.js 專案的相容性。

Node還是Deno優先

你有兩種主要方法:從 Deno 或 Node.js 開始。Deno 優先方法使用 Deno 的內建工具和 Deno 到Node工具(DNT)。另一方面,Node優先方法使用傳統的構建工具來完成測試、檢查和打包等任務。這種方法是轉換現有 NPM 庫的首選。

Deno優先方法

Deno優先方法依賴於DNT,你可以在GitHub上找到。

該工具透過版本庫中的自定義構建指令碼使用。

第一步是建立一個基本的 Deno 庫,準備釋出到 deno.land/x。之後,你就可以使用 DNT 了。

新增指令碼

Deno優先方法的核心是構建流程。下面這個名為 scripts/build_npm.ts 的指令碼使用 DNT 建立一個 /npm 資料夾,其中包含一個完整的 NPM 包,可以隨時釋出。

該指令碼將處理清除 NPM 目錄、複製測試資料和構建軟體包等任務。它還會建立一個完整的 package.json 檔案。

讓我們一起來看看吧,請務必閱讀註釋。

import { build, copy, emptyDir } from "./deps.ts";

// Clear NPM directory
await emptyDir("./npm");

// (optional) Copy test data, if you have some
// await copy("tests/data", "npm/esm/tests/data", { overwrite: true });

// This assumes that the entrypoint of your module is ./mod.ts
await build({
  entryPoints: ["./mod.ts"],
  outDir: "./npm",
  shims: {
    deno: "dev",
  },
  /*
  mappings: {
    "<https://deno.land/x/zipjs@v2.7.17/index.js>": {
      name: "@zip.js/zip.js",
      version: "^2.7.17"
    },
  },
  */
  package: {
    // package.json template
    name: "my-library-name",
    version: Deno.args[0],
    description: "My library description.",
    license: "MIT",
    repository: {/* ... */},
    author: "You <your@mail>",
    /* Additional information */
  },
});

// (optional) post build steps, you might want to copy some files?
// ---------------------------------------------------------------
// Deno.copyFileSync("LICENSE", "npm/LICENSE");
// Deno.copyFileSync("README.md", "npm/README.md");

// (optional) Add .npmignore
// ---------------------------------------------------------------
// ensure the test data is ignored in the `.npmignore` file
// so it doesn't get published with your npm package, if relevant
/*
await Deno.writeTextFile(
  "npm/.npmignore",
  "esm/tests/data\nscript/tests/data\n",
  { append: true },
);
*/

現在,你只需執行 deno run -A scripts/build_npm.ts 0.0.1 來構建 0.0.1 版本的 npm 軟體包。所有相關檔案都將在 ./npm 中生成。

最後一步是導航到 ./npm 目錄,然後執行 npm publish,就可以了!

使用 deno.json 更新構建管道流

要記錄這一構建步驟,可以修改 deno.jsontask部分,將新的 NPM 構建步驟包括在內。下面是一個設定測試和 NPM 構建的配置示例:

{
    /* ... existing configuration ... */
    "tasks": {
        "test": "deno test tests --allow-read",
        "build": "deno run -A scripts/build_npm.ts"
    }
}

現在,執行 deno task build 0.0.1 時將生成 npm 包。

Node優先方法

或者,你也可以選擇Node優先的方法來建立跨執行時包。

第一步是確保你的專案同時支援 ESM 和 CommonJS。這既可以手動完成,也可以使用構建工具來處理。程式碼庫最好是非轉譯的 javascript 或 typescript,以便 Rollup 或類似工具處理。

讓我們以 @hexagon/base64 庫為例進行分析。該庫使用 Rollup 生成 ESM 和 CommonJS 版本的程式碼,配置如下:

// rollup.config.js
export default [
	{
		input: "./src/base64.single.js",
		output: {
			file: "dist/base64.cjs",
			format: "umd",
			name: "base64",
			exports: "default"
		}
	},
	{	
		input: "./src/base64.js",
		output: {
			file: "dist/base64.mjs",
			format: "es"
		}
	}
];

該庫的原始碼(/src/base64.js)是以各種方式匯出 base64 物件的普通 ES JavaScript。

// src/base64.js
/* ...
   Library code making up the base64 object
   ... */

// Default export
export default base64;

// Named export
export { base64 };

Rollup 無法處理多重匯出,因此我還建立了一個 /src/base64.single.js,預設情況下它只負責重新匯出 base64 物件。這是 Rollup 配置的 UMD 目標所使用的。

// /src/base64.single.js
import base64 from "./base64.js";
export default base64;

package.json

package.json 檔案是設定雙模式、跨執行時 JavaScript 包的關鍵。它決定了包在不同環境中的結構和行為方式。讓我們來仔細看看其中的關鍵部分及其重要性:

{
  /* ... your metadata ... */

  "scripts": {
    /* ... your existing build steps ... */
    "build:dist": "rollup -c ./rollup.config.js",
  },

  "type": "module",

  "main": "./dist/base64.cjs",
  "browser": "./dist/base64.min.js",
  "module": "./src/base64.js",
  "types": "types/base64.single.d.ts",

  "exports": {
    ".": {
      "import": "./src/base64.js",
      "require": "./dist/base64.cjs",
      "browser": "./dist/base64.min.js"
    }
  }
}
  • "scripts" :該部分包含構建和管理包所需的指令碼。在提供的示例中,"build:dist"用於觸發 Rollup 打包過程。根據包的具體要求,你可能還需要其他指令碼來進行測試、檢查或執行其他任務。
  • "type" :該欄位設定為"module",表示你的包是為使用 ESM(ES 模組)匯入而設計的。
  • "main" :該欄位指定了 CommonJS 環境(如 Node.js)的入口點。它指向包的 CommonJS 版本,通常位於 dist 目錄中。
  • "browser" :該欄位用於指定瀏覽器環境的替代入口點。它指向包的最小化版本,以增強與瀏覽器的相容性。
  • "module" :與 "main"欄位類似,該欄位用於指定 ESM 環境的入口點。它指向軟體包的 ESM 版本。
  • "types" :此欄位指明軟體包的 TypeScript 宣告檔案(.d.ts)的位置。這些檔案為 TypeScript 使用者提供了型別資訊,改善了開發人員的體驗。
  • "exports" :該欄位是一項最新功能,允許你定義如何匯入包。它為 ESM、CommonJS 和瀏覽器環境指定了不同的匯入路徑,確保了跨執行時的流暢相容性。

根據包的具體需求和配置,你可能需要對 package.json 進行或多或少的修改。仔細調整和測試該檔案以確保其在釋出時正常執行至關重要。

跨執行時部分

前面提到的步驟主要是在 Node.js 中設定雙模式相容性。雖然 Deno 可以使用開箱即用的 npm 軟體包,但要建立一個完整的跨執行時包,你還應該將其適配到 Deno。

這包括閱讀 Deno 庫的工作原理將軟體包釋出到 deno.land/x

還有就是,讓你的軟體包成為雙模式軟體也能幫助其他專案。

總結

建立雙模式、跨執行時的 JavaScript 包是一種有益的體驗。它能使你的程式碼具有可移植性和可重用性,讓你在不同的 JavaScript 環境中接觸到更多的使用者。雖然會有一些障礙和注意事項,如管理相容性以及與不同模組系統和執行時的配合,但利大於弊。

相關文章