首頁 >web前端 >js教程 >Node.js:cjs、捆綁器和 esm 簡史

Node.js:cjs、捆綁器和 esm 簡史

Mary-Kate Olsen
Mary-Kate Olsen原創
2024-12-15 05:36:10394瀏覽

Node.js: A brief history of cjs, bundlers, and esm

介紹

如果您是 Node.js 開發人員,您可能聽說過 cjs 和 esm 模組,但可能不確定為什麼有兩個模組以及它們如何在 Node.js 應用程式中共存。這篇部落格文章將簡要介紹 Node.js 中 JavaScript 模組的歷史(附有範例?),以便您在處理這些概念時更加自信。

全球範圍

最初,JavaScript 僅具有全域作用域,所有成員都已聲明。共享程式碼時這是有問題的,因為兩個獨立的檔案可能對成員使用相同的名稱。例如:

greet-1.js

function greet(name) {
  return `Hello ${name}!`;
}

greet-2.js

var greet = "...";

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Collision example</title>
  </head>
  <body>
    <!-- After this script, `greet` is a function -->
    <script src="greet-1.js"></script>

    <!-- After this script, `greet` is a string -->
    <script src="greet-2.js"></script>

    <script>
        // TypeError: "greet" is not a function
        greet();
    </script>
  </body>
</html>

CommonJS 模組

Node.js 透過 CommonJS(也稱為 cjs)正式引入了 JavaScript 模組的概念。這解決了共享全域範圍的衝突問題,因為開發人員可以決定要導出什麼(透過 module.exports)和導入(透過 require())。例如:

src/greet.js

// this remains "private"
const GREETING_PREFIX = "Hello";

// this will be exported
function greet(name) {
  return `${GREETING_PREFIX} ${name}!`;
}

// `exports` is a shortcut to `module.exports`
exports.greet = greet;

src/main.js

// notice the `.js` suffix is missing
const { greet } = require("./greet");

// logs: Hello Alice!
console.log(greet("Alice"));

npm 包

由於 npm 套件允許開發人員發布和使用可重複使用的 JavaScript 程式碼,Node.js 開發迅速流行。 npm 套件預設安裝在 node_modules 資料夾中。所有 npm 套件中存在的 package.json 檔案尤其重要,因為它可以透過「main」屬性指示 Node.js 哪個檔案是入口點。例如:

node_modules/greeter/package.json

{
  "name": "greeter",
  "main": "./entry-point.js"
  // ...
}

node_modules/greeter/entry-point.js

module.exports = {
  greet(name) {
    return `Hello ${name}!`;
  }
};

src/main.js

// notice there's no relative path (e.g. `./`)
const { greet } = require("greeter");

// logs: Hello Bob!
console.log(greet("Bob"));

捆綁者

npm 套件能夠利用其他開發人員的工作,從而大大提高了開發人員的工作效率。然而,它有一個主要缺點:cjs 與網頁瀏覽器不相容。為了解決這個問題,捆綁器的概念誕生了。 browserify 是第一個捆綁器,本質上是透過遍歷入口點並將所有 require() 程式碼「捆綁」到與 Web 瀏覽器相容的單一 .js 檔案中來運作的。隨著時間的推移,其他具有附加功能和差異化因素的捆綁器也被引入。最值得注意的是 webpack、parcel、rollup、esbuild 和 vite(按時間順序排列)。

ECMAScript 模組

隨著 Node.js 和 cjs 模組成為主流,ECMAScript 規範維護者決定納入模組概念。這就是為什麼原生 JavaScript 模組也被稱為 ESModules 或 esm(ECMAScript 模組的縮寫)。

esm 定義了用於匯出和匯入成員的新關鍵字和語法,並引入了預設匯出等新概念。隨著時間的推移,esm 模組獲得了新的功能,例如動態 import() 和頂級等待。例如:

src/greet.js

function greet(name) {
  return `Hello ${name}!`;
}

src/part.js

var greet = "...";

src/main.js

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Collision example</title>
  </head>
  <body>
    <!-- After this script, `greet` is a function -->
    <script src="greet-1.js"></script>

    <!-- After this script, `greet` is a string -->
    <script src="greet-2.js"></script>

    <script>
        // TypeError: "greet" is not a function
        greet();
    </script>
  </body>
</html>

隨著時間的推移,由於捆綁程式和 TypeScript 等語言,esm 被開發人員廣泛採用,因為它們能夠將 esm 語法轉換為 cjs。

Node.js cjs/esm 互通性

由於需求不斷增長,Node.js 在 12.x 版本中正式添加了對 esm 的支援。與 cjs 的向後相容性實作如下:

  • Node.js 將 .js 檔案解釋為 cjs 模組,除非 package.json 將「type」屬性設為「module」。
  • Node.js 將 .cjs 檔案解釋為 cjs 模組。
  • Node.js 將 .mjs 檔案解釋為 esm 模組。

當涉及 npm 套件相容性時,esm 模組可以使用 cjs 和 esm 入口點導入 npm 套件。然而,相反的情況也有一些警告。舉例:

node_modules/cjs/package.json

// this remains "private"
const GREETING_PREFIX = "Hello";

// this will be exported
function greet(name) {
  return `${GREETING_PREFIX} ${name}!`;
}

// `exports` is a shortcut to `module.exports`
exports.greet = greet;

node_modules/cjs/entry.js

// notice the `.js` suffix is missing
const { greet } = require("./greet");

// logs: Hello Alice!
console.log(greet("Alice"));

node_modules/esm/package.json

{
  "name": "greeter",
  "main": "./entry-point.js"
  // ...
}

node_modules/esm/entry.js

module.exports = {
  greet(name) {
    return `Hello ${name}!`;
  }
};

以下運作正常:

src/main.mjs

// notice there's no relative path (e.g. `./`)
const { greet } = require("greeter");

// logs: Hello Bob!
console.log(greet("Bob"));

但是,以下指令無法運作:

src/main.cjs

// this remains "private"
const GREETING_PREFIX = "Hello";

// this will be exported
export function greet(name) {
  return `${GREETING_PREFIX} ${name}!`;
}

不允許這樣做的原因是因為 esm 模組允許頂層等待,而 require() 函數是同步的。可以重寫程式碼以使用動態 import(),但由於它會傳回一個 Promise,因此它強制具有如下所示的內容:

src/main.cjs

// default export: new concept
export default function part(name) {
  return `Goodbye ${name}!`;
}

為了緩解此相容性問題,一些 npm 套件透過利用帶有條件導出的 package.json 的「exports」屬性來公開 cjs 和 mjs 入口點。例如:

node_modules/esm/entry.cjs:

// notice the `.js` suffix is required
import part from "./part.js";

// dynamic import: new capability
// top-level await: new capability
const { greet } = await import("./greet.js");

// logs: Hello Alice!
console.log(greet("Alice"));

// logs: Bye Bob!
console.log(part("Bob"));

node_modules/esm/package.json:

{
  "name": "cjs",
  "main": "./entry.js"
}

注意「main」如何指向 cjs 版本,以便向後相容不支援「exports」屬性的 Node.js 版本。

結論

這(幾乎)是您需要了解的有關 cjs 和 esm 模組的全部資訊(截至 2024 年 12 月?)。請在下面告訴我你的想法!

以上是Node.js:cjs、捆綁器和 esm 簡史的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn