JavaScript 中的模組解析是一個複雜的話題。
生態系統目前正處於從 CommonJS 模組到原生 ES 模組的多年過渡期。TypeScript 強制執行其自己的關於匯入擴充套件的規則集,這與 ESM 不相容。不同的構建工具透過不同的不相容機制支援路徑重對映。
Bun 旨在提供一個一致且可預測的模組解析系統,它能正常工作。不幸的是,它仍然相當複雜。
語法
考慮以下檔案。
import { hello } from "./hello";
hello();
export function hello() {
console.log("Hello world!");
}
當我們執行 index.ts 時,它會列印“Hello world!”。
bun index.tsHello 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 將只檢查具有該確切副檔名的檔案。
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
如果您從 "*.js{x}" 匯入,Bun 將額外檢查匹配的 *.ts{x} 檔案,以與 TypeScript 的ES 模組支援相容。
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 中也可以工作。
const { hello } = require("./hello");
hello();
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 模組 | 模組名稱空間 | 模組名稱空間 |
| CommonJS | module.exports | default 是 module.exports,module.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";
同時使用 import 和 require()
在 Bun 中,你可以在同一個檔案中使用 import 或 require——它們始終都有效。
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 會向上掃描檔案系統以查詢包含包 foo 的 node_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 欄位並檢查以下條件。
{
"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 中首先出現的任何一個都用於確定包的入口點。
{
"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"。
{
"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 不支援任何形式的匯入路徑重新對映。
{
"compilerOptions": {
"paths": {
"config": ["./config.ts"], // map specifier to file
"components/*": ["components/*"], // wildcard matching
}
}
}
如果你不是 TypeScript 使用者,你可以在專案根目錄中建立一個jsconfig.json以實現相同的行為。
Bun 中 CommonJS 互操作的底層細節