Bun

模組解析

JavaScript 中的模組解析是一個複雜的話題。

生態系統目前正處於從 CommonJS 模組到原生 ES 模組的多年過渡期。TypeScript 強制執行其自己的關於匯入擴充套件的規則集,這與 ESM 不相容。不同的構建工具透過不同的不相容機制支援路徑重對映。

Bun 旨在提供一個一致且可預測的模組解析系統,它能正常工作。不幸的是,它仍然相當複雜。

語法

考慮以下檔案。

index.ts
hello.ts
index.ts
import { hello } from "./hello";

hello();
hello.ts
export function hello() {
  console.log("Hello world!");
}

當我們執行 index.ts 時,它會列印“Hello world!”。

bun index.ts
Hello world!

在這種情況下,我們從 ./hello 匯入,這是一個沒有副檔名的相對路徑。帶副檔名的匯入是可選的,但受支援。為了解析此匯入,Bun 將按順序檢查以下檔案

  • ./hello.tsx
  • ./hello.jsx
  • ./hello.ts
  • ./hello.mjs
  • ./hello.js
  • ./hello.cjs
  • ./hello.json
  • ./hello/index.tsx
  • ./hello/index.jsx
  • ./hello/index.ts
  • ./hello/index.mjs
  • ./hello/index.js
  • ./hello/index.cjs
  • ./hello/index.json

匯入路徑可以選擇包含副檔名。如果存在副檔名,Bun 將只檢查具有該確切副檔名的檔案。

index.ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works

如果您從 "*.js{x}" 匯入,Bun 將額外檢查匹配的 *.ts{x} 檔案,以與 TypeScript 的ES 模組支援相容。

index.ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
import { hello } from "./hello.js"; // this also works

Bun 支援 ES 模組(import/export 語法)和 CommonJS 模組(require()/module.exports)。以下 CommonJS 版本在 Bun 中也可以工作。

index.js
hello.js
index.js
const { hello } = require("./hello");

hello();
hello.js
function hello() {
  console.log("Hello world!");
}

exports.hello = hello;

儘管如此,在新專案中不鼓勵使用 CommonJS。

模組系統

Bun 對 CommonJS 和 ES 模組有原生支援。ES 模組是新專案推薦的模組格式,但 CommonJS 模組仍在 Node.js 生態系統中廣泛使用。

在 Bun 的 JavaScript 執行時中,ES 模組和 CommonJS 模組都可以使用 require。如果目標模組是 ES 模組,require 返回模組名稱空間物件(相當於 import * as)。如果目標模組是 CommonJS 模組,require 返回 module.exports 物件(如在 Node.js 中)。

模組型別require()import * as
ES 模組模組名稱空間模組名稱空間
CommonJSmodule.exportsdefaultmodule.exportsmodule.exports 的鍵是命名匯出

使用 require()

你可以 require() 任何檔案或包,甚至 .ts.mjs 檔案。

const { foo } = require("./foo"); // extensions are optional
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");

什麼是 CommonJS 模組?

使用 import

你可以 import 任何檔案或包,甚至是 .cjs 檔案。

import { foo } from "./foo"; // extensions are optional
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";

同時使用 importrequire()

在 Bun 中,你可以在同一個檔案中使用 importrequire——它們始終都有效。

import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";
const myStuff = require("./my-commonjs.cjs");

頂級 await

此規則唯一的例外是頂級 await。你不能 require() 一個使用頂級 await 的檔案,因為 require() 函式本質上是同步的。

幸運的是,很少有庫使用頂級 await,所以這很少是個問題。但是,如果你的應用程式程式碼中使用了頂級 await,請確保該檔案沒有在應用程式的其他地方被 require()。相反,你應該使用 import動態 import()

匯入包

Bun 實現了 Node.js 模組解析演算法,因此你可以使用裸說明符從 node_modules 匯入包。

import { stuff } from "foo";

此演算法的完整規範已在Node.js 文件中正式記錄;我們在此不再贅述。簡而言之:如果你從 "foo" 匯入,Bun 會向上掃描檔案系統以查詢包含包 foonode_modules 目錄。

NODE_PATH

Bun 支援 NODE_PATH 用於額外的模組解析目錄

NODE_PATH=./packages bun run src/index.js
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";

多個路徑使用平臺的定界符(Unix 上為 :,Windows 上為 ;

NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows

一旦找到 foo 包,Bun 會讀取 package.json 以確定應如何匯入該包。為了確定包的入口點,Bun 首先讀取 exports 欄位並檢查以下條件。

package.json
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node": "./index.js",
    "require": "./index.js", // if importer is CommonJS
    "import": "./index.mjs", // if importer is ES module
    "default": "./index.js",
  }
}

這些條件中在 package.json首先出現的任何一個都用於確定包的入口點。

Bun 遵守子路徑"exports""imports"

package.json
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}

子路徑匯入和條件匯入協同工作。

{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}

與 Node.js 一樣,在 "exports" 對映中指定任何子路徑將阻止其他子路徑被匯入;你只能匯入明確匯出的檔案。給定上述 package.json

import stuff from "foo"; // this works
import stuff from "foo/index.mjs"; // this doesn't

釋出 TypeScript — 請注意,Bun 支援特殊的 "bun" 匯出條件。如果你的庫是用 TypeScript 編寫的,你可以直接將你的(未轉譯的!)TypeScript 檔案釋出到 npm。如果你在 "bun" 條件中指定你的包的 *.ts 入口點,Bun 將直接匯入並執行你的 TypeScript 原始檔。

如果未定義 exports,Bun 將回退到 "module"(僅 ESM 匯入),然後是"main"

package.json
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}

自定義條件

--conditions 標誌允許你指定一個條件列表,用於從 package.json "exports" 解析包時使用。

此標誌在 bun build 和 Bun 的執行時中都受支援。

# Use it with bun build:
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# Use it with bun's runtime:
bun --conditions="react-server" ./app/foo/route.js

你也可以透過 Bun.build 以程式設計方式使用 conditions

await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});

路徑重對映

本著將 TypeScript 視為一等公民的精神,Bun 執行時將根據 tsconfig.json 中的compilerOptions.paths欄位重新對映匯入路徑。這與 Node.js 存在重大差異,Node.js 不支援任何形式的匯入路徑重新對映。

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"],         // map specifier to file
      "components/*": ["components/*"],  // wildcard matching
    }
  }
}

如果你不是 TypeScript 使用者,你可以在專案根目錄中建立一個jsconfig.json以實現相同的行為。

Bun 中 CommonJS 互操作的底層細節