模組:CommonJS 模組#

穩定性:2 - 穩定

CommonJS 模組是為 Node.js 打包 JavaScript 程式碼的最初方式。Node.js 也支援瀏覽器和其他 JavaScript 執行時使用的 ECMAScript 模組標準。

在 Node.js 中,每個檔案都被視為一個獨立的模組。例如,考慮一個名為 foo.js 的檔案:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`); 

在第一行,foo.js 載入了與 foo.js 位於同一目錄下的 circle.js 模組。

以下是 circle.js 的內容:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r; 

模組 circle.js 匯出了 area()circumference() 函式。透過在特殊的 exports 物件上指定附加屬性,函式和物件被新增到模組的根部。

模組內的區域性變數將是私有的,因為模組被 Node.js 包裝在一個函式中(參見 模組包裝器)。在這個例子中,變數 PIcircle.js 是私有的。

可以給 module.exports 屬性賦一個新值(例如一個函式或物件)。

在下面的程式碼中,bar.js 使用了 square 模組,該模組匯出一個 Square 類:

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`The area of mySquare is ${mySquare.area()}`); 

square 模組定義在 square.js 中:

// Assigning to exports will not modify module, must use module.exports
module.exports = class Square {
  constructor(width) {
    this.width = width;
  }

  area() {
    return this.width ** 2;
  }
}; 

CommonJS 模組系統是在 module 核心模組中實現的。

啟用#

Node.js 有兩個模組系統:CommonJS 模組和 ECMAScript 模組

預設情況下,Node.js 會將以下內容視為 CommonJS 模組:

  • 副檔名為 .cjs 的檔案;

  • 副檔名為 .js 的檔案,且其最近的父 package.json 檔案包含一個值為 "commonjs" 的頂層欄位 "type"

  • 副檔名為 .js 或沒有副檔名的檔案,當最近的父級 package.json 檔案不包含頂層欄位 "type",或者任何父級資料夾中都沒有 package.json 時;除非檔案包含的語法只有在作為 ES 模組評估時才會報錯。包作者應包含 "type" 欄位,即使在所有原始檔都是 CommonJS 的包中也是如此。明確包的型別將使構建工具和載入器更容易確定如何解釋包中的檔案。

  • 副檔名不是 .mjs.cjs.json.node.js(當最近的父 package.json 檔案包含一個值為 "module" 的頂層欄位 "type" 時,這些檔案只有在透過 require() 包含時才會被識別為 CommonJS 模組,而不是作為程式的命令列入口點時)。

更多詳情請參見確定模組系統

呼叫 require() 總是使用 CommonJS 模組載入器。呼叫 import() 總是使用 ECMAScript 模組載入器。

訪問主模組#

當一個檔案直接從 Node.js 執行時,require.main 會被設定為它的 module。這意味著可以透過測試 require.main === module 來判斷一個檔案是否被直接執行。

對於檔案 foo.js,如果透過 node foo.js 執行,這將是 true,但如果透過 require('./foo') 執行,則為 false

當入口點不是 CommonJS 模組時,require.mainundefined,並且主模組不可訪問。

包管理器提示#

Node.js require() 函式的語義設計得足夠通用,以支援合理的目錄結構。像 dpkgrpmnpm 這樣的包管理器程式有望能夠無需修改就從 Node.js 模組構建原生包。

下面,我們給出一個可行的建議目錄結構:

假設我們希望 /usr/lib/node// 目錄存放特定版本包的內容。

包可以相互依賴。為了安裝包 foo,可能需要安裝特定版本的包 barbar 包本身也可能有依賴,在某些情況下,這些依賴甚至可能衝突或形成迴圈依賴。

因為 Node.js 會查詢它載入的任何模組的 realpath(即,它會解析符號連結),然後node_modules 資料夾中查詢它們的依賴項,所以這種情況可以透過以下架構來解決:

  • /usr/lib/node/foo/1.2.3/foo 包版本 1.2.3 的內容。
  • /usr/lib/node/bar/4.3.2/foo 依賴的 bar 包的內容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar:指向 /usr/lib/node/bar/4.3.2/ 的符號連結。
  • /usr/lib/node/bar/4.3.2/node_modules/*:指向 bar 所依賴的包的符號連結。

因此,即使遇到迴圈或依賴衝突,每個模組也都能獲得它能使用的依賴版本。

foo 包中的程式碼執行 require('bar') 時,它將獲取到符號連結到 /usr/lib/node/foo/1.2.3/node_modules/bar 的版本。然後,當 bar 包中的程式碼呼叫 require('quux') 時,它將獲取到符號連結到 /usr/lib/node/bar/4.3.2/node_modules/quux 的版本。

此外,為了使模組查詢過程更加最佳化,我們可以不直接將包放在 /usr/lib/node 中,而是將它們放在 /usr/lib/node_modules// 中。這樣,Node.js 就不會費力在 /usr/node_modules/node_modules 中查詢缺失的依賴項。

為了使模組在 Node.js REPL 中可用,將 /usr/lib/node_modules 資料夾新增到 $NODE_PATH 環境變數中可能也很有用。由於使用 node_modules 資料夾的模組查詢都是相對的,並且基於呼叫 require() 的檔案的真實路徑,因此包本身可以位於任何地方。

使用 require() 載入 ECMAScript 模組#

穩定性:1.2 - 候選釋出

.mjs 副檔名是為 ECMAScript 模組保留的。有關哪些檔案被解析為 ECMAScript 模組的更多資訊,請參閱確定模組系統部分。

require() 僅支援載入滿足以下要求的 ECMAScript 模組:

  • 模組是完全同步的(不包含頂層 await);並且
  • 滿足以下條件之一:
    1. 副檔名為 .mjs
    2. 副檔名為 .js,且最近的 package.json 包含 "type": "module"
    3. 副檔名為 .js,最近的 package.json 不包含 "type": "commonjs",且模組包含 ES 模組語法。

如果正在載入的 ES 模組滿足要求,require() 可以載入它並返回模組名稱空間物件。在這種情況下,它類似於動態 import(),但是同步執行並直接返回名稱空間物件。

對於以下 ES 模組:

// distance.mjs
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } 
// point.mjs
export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
} 

CommonJS 模組可以使用 require() 載入它們:

const distance = require('./distance.mjs');
console.log(distance);
// [Module: null prototype] {
//   distance: [Function: distance]
// }

const point = require('./point.mjs');
console.log(point);
// [Module: null prototype] {
//   default: [class Point],
//   __esModule: true,
// } 

為了與現有將 ES 模組轉換為 CommonJS 的工具互操作,這些工具隨後可以透過 require() 載入真正的 ES 模組,如果返回的名稱空間有 `default` 匯出,它將包含一個 `__esModule: true` 屬性,以便工具生成的消費程式碼可以識別真實 ES 模組中的預設匯出。如果名稱空間已經定義了 `__esModule`,則不會新增此屬性。此屬性是實驗性的,未來可能會更改。它只應由將 ES 模組轉換為 CommonJS 模組的工具使用,遵循現有的生態系統慣例。直接在 CommonJS 中編寫的程式碼應避免依賴它。

當一個 ES 模組同時包含命名匯出和預設匯出時,require() 返回的結果是模組名稱空間物件,它將預設匯出放在 .default 屬性中,類似於 import() 返回的結果。要自定義 require(esm) 直接返回的內容,ES 模組可以使用字串名稱 "module.exports" 匯出所需的值。

// point.mjs
export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
}

// `distance` is lost to CommonJS consumers of this module, unless it's
// added to `Point` as a static property.
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); }
export { Point as 'module.exports' } 
const Point = require('./point.mjs');
console.log(Point); // [class Point]

// Named exports are lost when 'module.exports' is used
const { distance } = require('./point.mjs');
console.log(distance); // undefined 

請注意在上面的例子中,當使用 module.exports 匯出名稱時,命名匯出對於 CommonJS 消費者將會丟失。為了讓 CommonJS 消費者繼續訪問命名匯出,模組可以確保預設匯出是一個物件,並將命名匯出作為其屬性附加。例如,在上面的例子中,distance 可以作為靜態方法附加到預設匯出,即 Point 類上。

export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); }

export default class Point {
  constructor(x, y) { this.x = x; this.y = y; }
  static distance = distance;
}

export { Point as 'module.exports' } 
const Point = require('./point.mjs');
console.log(Point); // [class Point]

const { distance } = require('./point.mjs');
console.log(distance); // [Function: distance] 

如果被 require() 的模組包含頂層 await,或者它 import 的模組圖中包含頂層 await,將會丟擲 ERR_REQUIRE_ASYNC_MODULE。在這種情況下,使用者應該使用 import() 來載入非同步模組。

如果啟用了 --experimental-print-required-tla,Node.js 將不會在評估前丟擲 ERR_REQUIRE_ASYNC_MODULE,而是會評估該模組,嘗試定位頂層 await,並列印它們的位置以幫助使用者修復。

目前,使用 require() 載入 ES 模組的支援是實驗性的,可以使用 --no-experimental-require-module 停用。要列印此功能的使用位置,請使用 --trace-require-module

可以透過檢查 process.features.require_module 是否為 true 來檢測此功能。

總結#

要獲取呼叫 require() 時將載入的確切檔名,請使用 require.resolve() 函式。

綜合以上所有內容,以下是 require() 所做工作的高階虛擬碼演算法:

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with '/'
   a. set Y to the file system root
3. If X is equal to '.', or X begins with './', '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
   c. THROW "not found"
4. If X begins with '#'
   a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. THROW "not found"

MAYBE_DETECT_AND_LOAD(X)
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP.
2. Else, if the source code of X can be parsed as ECMAScript module using
  <a href="esm.md#resolver-algorithm-specification">DETECT_MODULE_SYNTAX defined in
  the ESM resolver</a>,
  a. Load X as an ECMAScript module. STOP.
3. THROW the SyntaxError from attempting to parse X as CommonJS in 1. STOP.

LOAD_AS_FILE(X)
1. If X is a file, load X as its file extension format. STOP
2. If X.js is a file,
    a. Find the closest package scope SCOPE to X.
    b. If no scope was found
      1. MAYBE_DETECT_AND_LOAD(X.js)
    c. If the SCOPE/package.json contains "type" field,
      1. If the "type" field is "module", load X.js as an ECMAScript module. STOP.
      2. If the "type" field is "commonjs", load X.js as a CommonJS module. STOP.
    d. MAYBE_DETECT_AND_LOAD(X.js)
3. If X.json is a file, load X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP

LOAD_INDEX(X)
1. If X/index.js is a file
    a. Find the closest package scope SCOPE to X.
    b. If no scope was found, load X/index.js as a CommonJS module. STOP.
    c. If the SCOPE/package.json contains "type" field,
      1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP.
      2. Else, load X/index.js as a CommonJS module. STOP.
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon. STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. If "main" is a falsy value, GOTO 2.
   c. let M = X + (json main field)
   d. LOAD_AS_FILE(M)
   e. LOAD_INDEX(M)
   f. LOAD_INDEX(X) DEPRECATED
   g. THROW "not found"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_PACKAGE_EXPORTS(X, DIR)
   b. LOAD_AS_FILE(DIR/X)
   c. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
   a. if PARTS[I] = "node_modules", GOTO d.
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIR + DIRS
   d. let I = I - 1
5. return DIRS + GLOBAL_FOLDERS

LOAD_PACKAGE_IMPORTS(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "imports" is null or undefined, return.
4. If `--experimental-require-module` is enabled
  a. let CONDITIONS = ["node", "require", "module-sync"]
  b. Else, let CONDITIONS = ["node", "require"]
5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
  CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH).

LOAD_PACKAGE_EXPORTS(X, DIR)
1. Try to interpret X as a combination of NAME and SUBPATH where the name
   may have a @scope/ prefix and the subpath begins with a slash (`/`).
2. If X does not match this pattern or DIR/NAME/package.json is not a file,
   return.
3. Parse DIR/NAME/package.json, and look for "exports" field.
4. If "exports" is null or undefined, return.
5. If `--experimental-require-module` is enabled
  a. let CONDITIONS = ["node", "require", "module-sync"]
  b. Else, let CONDITIONS = ["node", "require"]
6. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
   `package.json` "exports", CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
7. RESOLVE_ESM_MATCH(MATCH)

LOAD_PACKAGE_SELF(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "exports" is null or undefined, return.
4. If the SCOPE/package.json "name" is not the first segment of X, return.
5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
   "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
   <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH)

RESOLVE_ESM_MATCH(MATCH)
1. let RESOLVED_PATH = fileURLToPath(MATCH)
2. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension
   format. STOP
3. THROW "not found" 

快取#

模組在第一次載入後會被快取。這意味著(除其他事項外)每次呼叫 require('foo') 都會返回完全相同的物件,如果它解析到相同的檔案。

只要 require.cache 未被修改,多次呼叫 require('foo') 不會導致模組程式碼被多次執行。這是一個重要特性。有了它,可以返回“部分完成”的物件,從而允許載入傳遞性依賴,即使它們會導致迴圈。

要讓一個模組多次執行程式碼,可以匯出一個函式,然後呼叫該函式。

模組快取的注意事項#

模組是根據其解析後的檔名進行快取的。由於模組可能會根據呼叫模組的位置(從 node_modules 資料夾載入)解析為不同的檔名,因此並不能*保證* require('foo') 總是返回完全相同的物件,如果它解析到不同的檔案。

此外,在不區分大小寫的檔案系統或作業系統上,不同的解析檔名可能指向同一個檔案,但快取仍會將它們視為不同的模組,並多次重新載入該檔案。例如,require('./foo')require('./FOO') 會返回兩個不同的物件,無論 ./foo./FOO 是否是同一個檔案。

內建模組#

Node.js 有幾個編譯到二進位制檔案中的模組。這些模組在本文件的其他地方有更詳細的描述。

內建模組定義在 Node.js 原始碼中,位於 lib/ 資料夾內。

內建模組可以使用 node: 字首來識別,這種情況下它會繞過 require 快取。例如,require('node:http') 將總是返回內建的 HTTP 模組,即使 require.cache 中存在同名條目。

如果將某些內建模組的識別符號傳遞給 require(),它們總是會被優先載入。例如,require('http') 將總是返回內建的 HTTP 模組,即使存在同名檔案。

所有內建模組的列表可以從 module.builtinModules 獲取。所有模組都列出時沒有 node: 字首,除了那些強制要求該字首的模組(如下一節所述)。

強制使用 node: 字首的內建模組#

當透過 require() 載入時,一些內建模組必須使用 node: 字首進行請求。這個要求是為了防止新引入的內建模組與已經佔用該名稱的使用者態包發生衝突。目前需要 node: 字首的內建模組有:

這些模組的列表在 module.builtinModules 中公開,包括字首。

迴圈#

當存在迴圈 require() 呼叫時,一個模組在返回時可能尚未執行完畢。

考慮這種情況:

a.js:

console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done'); 

b.js:

console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done'); 

main.js:

console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done); 

main.js 載入 a.js 時,a.js 接著載入 b.js。此時,b.js 試圖載入 a.js。為了防止無限迴圈,一個**未完成的副本** a.js 的 exports 物件會返回給 b.js 模組。然後 b.js 完成載入,其 exports 物件被提供給 a.js 模組。

main.js 載入完兩個模組時,它們都已經完成了。因此,這個程式的輸出將是:

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true 

需要仔細規劃,以允許迴圈模組依賴在應用程式中正常工作。

檔案模組#

如果找不到確切的檔名,Node.js 將嘗試載入帶有附加副檔名的所需檔名:.js.json,最後是 .node。當載入具有不同副檔名(例如 .cjs)的檔案時,其完整名稱必須傳遞給 require(),包括其副檔名(例如 require('./file.cjs'))。

.json 檔案被解析為 JSON 文字檔案,.node 檔案被解釋為使用 process.dlopen() 載入的已編譯外掛模組。使用任何其他副檔名(或根本沒有副檔名)的檔案被解析為 JavaScript 文字檔案。請參考確定模組系統部分,以瞭解將使用哪種解析目標。

'/' 為字首的必需模組是檔案的絕對路徑。例如,require('/home/marco/foo.js') 將載入位於 /home/marco/foo.js 的檔案。

'./' 為字首的必需模組是相對於呼叫 require() 的檔案。也就是說,circle.js 必須與 foo.js 在同一目錄下,require('./circle') 才能找到它。

如果沒有前導的 '/''./''../' 來指示檔案,模組必須是核心模組或從 node_modules 資料夾載入。

如果給定的路徑不存在,require() 將丟擲一個 MODULE_NOT_FOUND 錯誤。

資料夾作為模組#

有三種方式可以將一個資料夾作為引數傳遞給 require()

第一種是在資料夾的根目錄下建立一個 package.json 檔案,其中指定一個 main 模組。一個示例 package.json 檔案可能如下所示:

{ "name" : "some-library",
  "main" : "./lib/some-library.js" } 

如果這位於 ./some-library 資料夾中,那麼 require('./some-library') 將嘗試載入 ./some-library/lib/some-library.js

如果目錄中不存在 package.json 檔案,或者 "main" 條目缺失或無法解析,那麼 Node.js 將嘗試從該目錄載入 index.jsindex.node 檔案。例如,如果在前面的示例中沒有 package.json 檔案,那麼 require('./some-library') 將嘗試載入:

  • ./some-library/index.js
  • ./some-library/index.node

如果這些嘗試失敗,Node.js 將報告整個模組丟失,並顯示預設錯誤:

Error: Cannot find module 'some-library' 

在上述所有三種情況下,呼叫 import('./some-library') 將導致 ERR_UNSUPPORTED_DIR_IMPORT 錯誤。使用包的子路徑匯出子路徑匯入可以提供與資料夾作為模組相同的包容性組織優勢,並且對 requireimport 都有效。

node_modules 資料夾載入#

如果傳遞給 require() 的模組識別符號不是內建模組,並且不以 '/''../''./' 開頭,那麼 Node.js 會從當前模組的目錄開始,新增 /node_modules,並嘗試從該位置載入模組。Node.js 不會將 node_modules 附加到已經以 node_modules 結尾的路徑上。

如果在那裡找不到,它會移動到父目錄,依此類推,直到到達檔案系統的根目錄。

例如,如果位於 '/home/ry/projects/foo.js' 的檔案呼叫了 require('bar.js'),那麼 Node.js 將按以下順序查詢以下位置:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

這允許程式本地化其依賴項,從而避免衝突。

可以透過在模組名後附加路徑字尾來 require 模組附帶的特定檔案或子模組。例如,require('example-module/path/to/file') 將相對於 example-module 所在的位置解析 path/to/file。這個帶字尾的路徑遵循相同的模組解析語義。

從全域性資料夾載入#

如果 NODE_PATH 環境變數被設定為一個以冒號分隔的絕對路徑列表,那麼 Node.js 在其他地方找不到模組時,會搜尋這些路徑。

在 Windows 上,NODE_PATH 使用分號 (;) 而不是冒號進行分隔。

NODE_PATH 最初是為了在當前的模組解析演算法定義之前支援從不同路徑載入模組而建立的。

NODE_PATH 仍然受支援,但現在 Node.js 生態系統已經就定位依賴模組的約定達成一致,因此它的必要性降低了。有時依賴 NODE_PATH 的部署會在人們不知道必須設定 NODE_PATH 時表現出令人驚訝的行為。有時模組的依賴關係會改變,導致在搜尋 NODE_PATH 時載入了不同版本(甚至是不同的模組)。

此外,Node.js 會在以下 GLOBAL_FOLDERS 列表中搜索:

  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node

其中 $HOME 是使用者的主目錄,$PREFIX 是 Node.js 配置的 node_prefix

這些主要是出於歷史原因。

強烈建議將依賴項放置在本地的 node_modules 資料夾中。這樣載入速度更快,也更可靠。

模組包裝器#

在模組程式碼執行之前,Node.js 會用一個函式包裝器將其包裹起來,如下所示:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
}); 

透過這樣做,Node.js 實現了幾件事:

  • 它將頂層變數(用 varconstlet 定義)的作用域限定在模組內,而不是全域性物件。
  • 它有助於提供一些看起來是全域性的但實際上是模組特有的變數,例如:
    • moduleexports 物件,實現者可以用它們從模組中匯出值。
    • 便利變數 __filename__dirname,包含模組的絕對檔名和目錄路徑。

模組作用域#

__dirname#

當前模組的目錄名。這與 __filenamepath.dirname() 相同。

示例:從 /Users/mjr 執行 node example.js

console.log(__dirname);
// Prints: /Users/mjr
console.log(path.dirname(__filename));
// Prints: /Users/mjr 

__filename#

當前模組的檔名。這是當前模組檔案的絕對路徑,並解析了符號連結。

對於主程式,這不一定與命令列中使用的檔名相同。

有關當前模組的目錄名,請參見 __dirname

示例

/Users/mjr 執行 node example.js

console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr 

給定兩個模組:ab,其中 ba 的一個依賴,目錄結構如下:

  • /Users/mjr/app/a.js
  • /Users/mjr/app/node_modules/b/b.js

b.js 中對 __filename 的引用將返回 /Users/mjr/app/node_modules/b/b.js,而在 a.js 中對 __filename 的引用將返回 /Users/mjr/app/a.js

exports#

module.exports 的引用,輸入更簡短。有關何時使用 exports 和何時使用 module.exports 的詳細資訊,請參見關於exports 快捷方式的部分。

module#

對當前模組的引用,請參閱關於module 物件的部分。特別是,module.exports 用於定義一個模組匯出什麼並透過 require() 提供什麼。

require(id)#

  • id <string> 模組名稱或路徑
  • 返回:<any> 匯出的模組內容

用於匯入模組、JSON 和本地檔案。模組可以從 node_modules 匯入。本地模組和 JSON 檔案可以使用相對路徑(例如 ././foo./bar/baz../foo)匯入,該路徑將相對於由 __dirname(如果已定義)或當前工作目錄命名的目錄進行解析。POSIX 風格的相對路徑以作業系統無關的方式解析,這意味著上述示例在 Windows 上的工作方式與在 Unix 系統上相同。

// Importing a local module with a path relative to the `__dirname` or current
// working directory. (On Windows, this would resolve to .\path\myLocalModule.)
const myLocalModule = require('./path/myLocalModule');

// Importing a JSON file:
const jsonData = require('./path/filename.json');

// Importing a module from node_modules or Node.js built-in module:
const crypto = require('node:crypto'); 
require.cache#

模組在被 require 時會快取在此物件中。透過從此物件中刪除一個鍵值,下一次 require 將會重新載入該模組。這不適用於原生外掛,因為重新載入會導致錯誤。

也可以新增或替換條目。此快取在內建模組之前檢查,如果一個與內建模組名稱匹配的名稱被新增到快取中,則只有帶 `node:` 字首的 require 呼叫才會接收到內建模組。請謹慎使用!

const assert = require('node:assert');
const realFs = require('node:fs');

const fakeFs = {};
require.cache.fs = { exports: fakeFs };

assert.strictEqual(require('fs'), fakeFs);
assert.strictEqual(require('node:fs'), realFs); 
require.extensions#

穩定性: 0 - 廢棄

指示 `require` 如何處理某些副檔名。

將副檔名為 .sjs 的檔案作為 .js 處理:

require.extensions['.sjs'] = require.extensions['.js']; 

已棄用。 過去,這個列表曾被用來透過按需編譯將非 JavaScript 模組載入到 Node.js 中。然而,在實踐中,有更好的方法可以做到這一點,例如透過其他 Node.js 程式載入模組,或者提前將它們編譯成 JavaScript。

避免使用 require.extensions。使用它可能會導致細微的錯誤,並且每註冊一個副檔名,解析副檔名的速度就會變慢。

require.main#

代表 Node.js 程序啟動時載入的入口指令碼的 Module 物件,如果程式的入口點不是 CommonJS 模組,則為 undefined。參見“訪問主模組”

entry.js 指令碼中:

console.log(require.main); 
node entry.js 
Module {
  id: '.',
  path: '/absolute/path/to',
  exports: {},
  filename: '/absolute/path/to/entry.js',
  loaded: false,
  children: [],
  paths:
   [ '/absolute/path/to/node_modules',
     '/absolute/path/node_modules',
     '/absolute/node_modules',
     '/node_modules' ] } 
require.resolve(request[, options])#
  • request <string> 要解析的模組路徑。
  • options <Object>
    • paths <string[]> 用於解析模組位置的路徑。如果存在,這些路徑將替代預設的解析路徑,但全域性資料夾(如 $HOME/.node_modules)總是被包含在內。這些路徑中的每一個都用作模組解析演算法的起點,意味著會從這個位置開始檢查 node_modules 層次結構。
  • 返回: <string>

使用內部的 require() 機制來查詢模組的位置,但不是載入模組,而是隻返回解析後的檔名。

如果找不到模組,會丟擲 MODULE_NOT_FOUND 錯誤。

require.resolve.paths(request)#

返回一個包含在解析 request 過程中搜索的路徑的陣列,如果 request 字串引用的是核心模組(例如 httpfs),則返回 null

module 物件#

在每個模組中,自由變數 module 是對代表當前模組的物件的引用。為方便起見,module.exports 也可以透過模組全域性變數 exports 訪問。module 實際上不是一個全域性變數,而是每個模組的區域性變數。

module.children#

首次被此模組所 require 的模組物件。

module.exports#

module.exports 物件是由 Module 系統建立的。有時這不被接受;許多人希望他們的模組是某個類的例項。要做到這一點,需要將期望的匯出物件賦給 module.exports。將期望的物件賦給 exports 只會重新繫結區域性的 exports 變數,這可能不是所期望的。

例如,假設我們正在建立一個名為 a.js 的模組:

const EventEmitter = require('node:events');

module.exports = new EventEmitter();

// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
  module.exports.emit('ready');
}, 1000); 

然後在另一個檔案中我們可以這樣做:

const a = require('./a');
a.on('ready', () => {
  console.log('module "a" is ready');
}); 

module.exports 的賦值必須立即完成。它不能在任何回撥中完成。這樣做是行不通的:

x.js:

setTimeout(() => {
  module.exports = { a: 'hello' };
}, 0); 

y.js:

const x = require('./x');
console.log(x.a); 
exports 快捷方式#

exports 變數在模組的檔案級作用域內可用,並在模組評估之前被賦予 module.exports 的值。

它提供了一個快捷方式,這樣 module.exports.f = ... 就可以更簡潔地寫成 exports.f = ...。但是,請注意,像任何變數一樣,如果給 exports 賦了一個新值,它就不再繫結到 module.exports

module.exports.hello = true; // Exported from require of module
exports = { hello: false };  // Not exported, only available in the module 

module.exports 屬性被一個新物件完全替換時,通常也會重新分配 exports

module.exports = exports = function Constructor() {
  // ... etc.
}; 

為了說明這種行為,想象一下 require() 的這個假設實現,它與 require() 的實際實現非常相似:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    function someFunc() {}
    exports = someFunc;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
  })(module, module.exports);
  return module.exports;
} 

module.filename#

模組的完全解析後的檔名。

module.id#

模組的識別符號。通常這是完全解析後的檔名。

module.isPreloading#

  • 型別:<boolean> 如果模組在 Node.js 預載入階段執行,則為 true

module.loaded#

模組是否已載入完成,或正在載入過程中。

module.parent#

穩定性:0 - 已棄用:請改用 require.mainmodule.children

第一個 require 此模組的模組,如果當前模組是當前程序的入口點,則為 null;如果模組是由非 CommonJS 模組(例如:REPL 或 import)載入的,則為 undefined

module.path#

模組的目錄名。這通常與 module.idpath.dirname() 相同。

module.paths#

模組的搜尋路徑。

module.require(id)#

module.require() 方法提供了一種載入模組的方式,就好像是從原始模組呼叫 require() 一樣。

為了做到這一點,有必要獲取對 module 物件的引用。由於 require() 返回的是 module.exports,而 module 通常*只*在特定模組的程式碼中可用,因此必須明確匯出才能使用。

Module 物件#

此部分已移至 模組:module 核心模組

Source map v3 支援#

此部分已移至模組:`module` 核心模組