Bun implements a sophisticated module resolution system compatible with Node.js, enhanced with additional features for modern JavaScript development.
Module Systems
Bun supports both ES Modules (ESM) and CommonJS (CJS):
ES Modules
// Importing
import { readFile } from "node:fs/promises";
import React from "react";
import { add } from "./math.js";
// Exporting
export function greet(name) {
return `Hello, ${name}!`;
}
export default App;
Features:
- Static imports and exports
- Top-level
await support
- Tree-shaking friendly
- Strict mode by default
CommonJS
// Importing
const fs = require("node:fs");
const React = require("react");
const { add } = require("./math.js");
// Exporting
module.exports = {
greet(name) {
return `Hello, ${name}!`;
},
};
module.exports.default = App;
Features:
- Dynamic
require() calls
- Synchronous loading
- Circular dependency handling
exports shorthand
Interoperability
Bun seamlessly bridges ESM and CommonJS:
// Import CommonJS from ESM
import pkg from "./commonjs-module.js"; // module.exports
import { named } from "./commonjs-module.js"; // module.exports.named
// Require ESM from CommonJS (async)
const esm = await import("./esm-module.js");
Implementation: src/runtime.zig:167 - CommonJS named exports feature
Resolution Algorithm
Bun’s module resolution follows the Node.js algorithm with enhancements:
Resolution Steps
- Built-in modules - Check for
node:* and bun:* modules
- Relative/absolute paths - Resolve file paths
- Package imports - Search
node_modules
- Extensions - Try
.ts, .tsx, .js, .jsx, .mjs, .cjs
- Index files - Check
index.* files
- package.json - Resolve via
exports, main, module
Relative Imports
import { util } from "./utils.js"; // Same directory
import { config } from "../config.js"; // Parent directory
import { helper } from "./lib/helper.js"; // Subdirectory
Absolute Imports
import { db } from "/src/database.js"; // From root
import data from "file:///home/user/data.json"; // file:// URL
Package Imports
import React from "react"; // node_modules/react
import { parse } from "@babel/parser"; // Scoped package
import "./styles.css"; // Side-effect import
Extension Resolution
Bun tries extensions in this order:
- Exact match (if extension provided)
.ts
.tsx
.js
.jsx
.mjs
.cjs
.json
// All resolve to math.ts if it exists:
import { add } from "./math.ts"; // Explicit
import { add } from "./math"; // Implicit
Explicit extensions recommended for clarity and performance.
package.json Fields
Bun respects multiple package.json fields:
exports Field
Modern package entry points:
{
"name": "my-package",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
Import usage:
import pkg from "my-package"; // Uses exports["."]
import { util } from "my-package/utils"; // Uses exports["./utils"]
Conditional Exports
{
"exports": {
".": {
"bun": "./dist/bun.js",
"node": "./dist/node.js",
"browser": "./dist/browser.js",
"default": "./dist/index.js"
}
}
}
Condition priority (Bun target):
bun
node
import / require
default
Implementation: src/options.zig:508-533 - Default conditions per target
main, module, browser
Legacy entry point fields:
{
"main": "./dist/index.js", // CommonJS entry
"module": "./dist/index.mjs", // ESM entry
"browser": "./dist/browser.js", // Browser entry
"types": "./dist/index.d.ts" // TypeScript types
}
Resolution priority:
exports (if present)
module
main
index.js
Implementation: src/options.zig:455-505 - Main field resolution
type Field
Determines module system:
{
"type": "module" // All .js files are ESM
}
{
"type": "commonjs" // All .js files are CommonJS (default)
}
Overrides:
.mjs always ESM
.cjs always CommonJS
Path Mapping (tsconfig.json)
Configure import aliases:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@lib/*": ["./src/lib/*"],
"~/*": ["./*"]
}
}
}
import { Button } from "@components/Button";
import { api } from "@lib/api";
import config from "@/config";
import data from "~/data/users.json";
Implementation: src/resolver/resolver.zig - Path resolution
Built-in Modules
Bun provides built-in modules:
Node.js Compatibility
import fs from "node:fs";
import path from "node:path";
import { EventEmitter } from "node:events";
import { createServer } from "node:http";
All Node.js built-ins available with node: prefix.
Implementation: src/options.zig:150-333 - Node.js built-in patterns
Bun-specific Modules
import { serve } from "bun";
import { Database } from "bun:sqlite";
import { dlopen, FFIType } from "bun:ffi";
import { password } from "bun:password";
import { spawn } from "bun:spawn";
Available modules:
bun - Main Bun APIs
bun:ffi - Foreign Function Interface
bun:sqlite - SQLite database
bun:test - Test runner
bun:jsc - JavaScriptCore internals
bun:wrap - Internal runtime
Import Attributes
Specify how to import modules:
// Import as JSON
import data from "./data.json" with { type: "json" };
// Import as text
import content from "./file.txt" with { type: "text" };
// Import assertions (older syntax)
import config from "./config.json" assert { type: "json" };
Supported types:
json - Parse as JSON
text - Load as string
file - Copy to output
toml - Parse as TOML
Dynamic Imports
Load modules at runtime:
// ESM dynamic import
const module = await import("./module.js");
// Conditional loading
if (isDevelopment) {
const dev = await import("./dev-tools.js");
dev.init();
}
// Dynamic path
const locale = "en";
const messages = await import(`./locales/${locale}.js`);
Benefits:
- Code splitting
- Lazy loading
- Conditional imports
- String-based paths
Circular Dependencies
Bun handles circular dependencies:
// a.js
import { b } from "./b.js";
export const a = "A";
console.log(b); // "B"
// b.js
import { a } from "./a.js";
export const b = "B";
console.log(a); // undefined (not yet initialized)
Best practice: Avoid circular dependencies when possible.
Import Maps
Map bare specifiers to URLs:
{
"imports": {
"react": "https://esm.sh/react@18",
"lodash": "https://esm.sh/lodash@4"
}
}
import React from "react"; // Loads from esm.sh
import _ from "lodash";
Import maps are experimental and primarily for browser compatibility.
Resolution Cache
Bun caches module resolution results:
Cache location:
- Resolution cache: In-memory
- Transpiler cache: Disk (see TypeScript)
Cache invalidation:
- File system changes (watch mode)
- package.json modifications
- node_modules updates
Module Loader Hooks
Customize module loading (advanced):
import { plugin } from "bun";
plugin({
name: "custom-loader",
setup(build) {
// Custom resolution
build.onResolve({ filter: /^@app/ }, (args) => {
return {
path: args.path.replace("@app", "./src"),
};
});
// Custom loading
build.onLoad({ filter: /\.custom$/ }, async (args) => {
const text = await Bun.file(args.path).text();
return {
contents: `export default ${JSON.stringify(text)}`,
loader: "js",
};
});
},
});
Preloading
Load modules before main script:
bun --preload ./setup.ts run app.ts
preload = ["./setup.ts", "./polyfills.ts"]
Use cases:
- Environment setup
- Polyfills
- Global initialization
- Instrumentation
Optimization Tips
-
Use explicit extensions
import { util } from "./util.ts"; // Fast
import { util } from "./util"; // Slower (tries multiple extensions)
-
Prefer ESM over CommonJS
- Static analysis
- Tree-shaking
- Async loading
-
Use barrel files sparingly
// Slow (loads entire barrel)
import { Button } from "./components/index.js";
// Fast (direct import)
import { Button } from "./components/Button.js";
-
Leverage dynamic imports for code splitting
const Chart = await import("./Chart.js");
Debugging
Trace Resolution
BUN_DEBUG_QUIET_LOGS=0 bun run app.ts 2>&1 | grep -i resolve
Print Resolved Paths
console.log(import.meta.resolveSync("react"));
// "/path/to/node_modules/react/index.js"
Resolution Failures
# Module not found
error: Cannot find module "./missing.js"
# Check if file exists
ls -la ./missing.js
# Check extensions
ls -la ./missing.*
Implementation Details
Module resolution implementation:
- Resolver:
src/resolver/resolver.zig - Core resolution logic
- Module loader:
src/bun.js/module_loader/ - Module loading
- Import record:
src/import_record.zig - Import tracking
- Package.json:
src/resolver/package_json.zig - Package metadata