Skip to main content
The JavaScript ecosystem is in a years-long transition from CommonJS modules to native ES modules (ESM). TypeScript enforces its own rules around import extensions that aren’t compatible with ESM, and build tools re-map paths through incompatible mechanisms. Bun aims to provide a consistent and predictable module resolution system that works without configuration.

Syntax

Consider the following files.
import { hello } from "./hello";

hello();
Running index.ts prints “Hello world!”.
terminal
bun index.ts
Hello world!
Here ./hello is a relative path with no extension. Extensioned imports are optional but supported. To resolve this import, Bun checks for the following files in order:
  • ./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
If the import path includes an extension, Bun only checks for a file with that exact extension.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
If you import from "*.js{x}", Bun also checks for a matching *.ts{x} file, to be compatible with TypeScript’s ES module support.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
import { hello } from "./hello.js"; // this also works
Bun supports both ES modules (import/export syntax) and CommonJS modules (require()/module.exports). The following CommonJS version also works in Bun.
const { hello } = require("./hello");

hello();
That said, using CommonJS is discouraged in new projects.

Module systems

Bun has native support for CommonJS and ES modules. ES modules are the recommended module format for new projects, but CommonJS modules are still widely used in the Node.js ecosystem. In Bun’s JavaScript runtime, both ES modules and CommonJS modules can use require. If the target module is an ES module, require returns the module namespace object (equivalent to import * as). If the target module is a CommonJS module, require returns the module.exports object (as in Node.js).
Module Typerequire()import * as
ES ModuleModule NamespaceModule Namespace
CommonJSmodule.exportsdefault is module.exports, keys of module.exports are named exports

Using require()

You can require() any file or package, even .ts or .mjs files.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
const { foo } = require("./foo"); // extensions are optional
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");
In 2016, ECMAScript added support for ES modules. ES modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules.CommonJS modules use module.exports to export values and are typically imported with require.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340fmy-commonjs.cjs
const stuff = require("./stuff");
module.exports = { stuff };
The biggest difference between CommonJS and ES modules is that CommonJS modules are synchronous, while ES modules are asynchronous. Other differences:
  • ES modules support top-level await and CommonJS modules don’t.
  • ES modules are always in strict mode, while CommonJS modules are not.
  • Browsers do not have native support for CommonJS modules, but they do have native support for ES modules through <script type="module">.
  • CommonJS modules are not statically analyzable, while ES modules only allow static imports and exports.
  • Static import statements run synchronously, just like CommonJS require. ES modules can also be loaded on the fly with the asynchronous import() function, called a “dynamic import”.

Using import

You can import any file or package, even .cjs files.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import { foo } from "./foo"; // extensions are optional
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";

Using import and require() together

In Bun, you can use import or require in the same file—they both work, all the time.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

const myStuff = require("./my-commonjs.cjs");

Top level await

The only exception to this rule is top-level await. You can’t require() a file that uses top-level await, since the require() function is inherently synchronous. Fortunately, very few libraries use top-level await, so this is rarely a problem. But if you’re using top-level await in your application code, make sure that file isn’t require()’d from elsewhere in your application. Use import or dynamic import() instead.

Importing packages

Bun implements the Node.js module resolution algorithm, so you can import packages from node_modules with a bare specifier.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import { stuff } from "foo";
The full algorithm is specified in the Node.js documentation. Briefly: if you import from "foo", Bun scans up the file system for a node_modules directory containing the package foo.

NODE_PATH

Bun supports NODE_PATH for additional module resolution directories:
NODE_PATH=./packages bun run src/index.js
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";
Multiple paths use the platform’s delimiter (: on Unix, ; on Windows):
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows
Once it finds the foo package, Bun reads its package.json to determine the package’s entrypoint. Bun first reads the exports field and checks for the following conditions.
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"
  }
}
Whichever of these conditions occurs first in the package.json determines the package’s entrypoint. Bun respects subpath "exports" and "imports".
package.json
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}
Subpath imports and conditional imports work in conjunction with each other.
package.json
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}
As in Node.js, specifying any subpath in the "exports" map prevents other subpaths from being importable; you can only import files that are explicitly exported. Given the preceding package.json:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bindex.ts
import stuff from "foo"; // this works
import stuff from "foo/index.mjs"; // this doesn't
Shipping TypeScript — Bun supports the special "bun" export condition. If your library is written in TypeScript, you can publish your un-transpiled TypeScript files to npm directly. If you specify your package’s *.ts entrypoint in the "bun" condition, Bun imports and executes your TypeScript source files directly.
If exports is not defined, Bun falls back to "module" (ESM imports only) then "main".
package.json
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}

Custom conditions

The --conditions flag specifies the conditions to use when resolving packages from package.json "exports". Both bun build and Bun’s runtime support this flag.
terminal
# 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
You can also use conditions programmatically with Bun.build:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35bbuild.ts
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});

Path re-mapping

Bun supports import path re-mapping through TypeScript’s compilerOptions.paths in tsconfig.json, which works well with editors. If you aren’t a TypeScript user, use a jsconfig.json in your project root for the same behavior.
tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // map specifier to file
      "components/*": ["components/*"] // wildcard matching
    }
  }
}
Bun also supports Node.js-style subpath imports in package.json, where mapped paths must start with #. This approach doesn’t work as well with editors, but you can use both options together.
package.json
{
  "imports": {
    "#config": "./config.ts", // map specifier to file
    "#components/*": "./components/*" // wildcard matching
  }
}
Bun’s JavaScript runtime has native support for CommonJS. When Bun’s JavaScript transpiler detects usages of module.exports, it treats the file as CommonJS. The module loader then wraps the transpiled module in a function shaped like this:
(function (module, exports, require) {
  // transpiled module
})(module, exports, require);
module, exports, and require are very much like the module, exports, and require in Node.js. These are assigned through a with scope in C++. An internal Map stores the exports object to handle cyclical require calls before the module is fully loaded.Once the CommonJS module is successfully evaluated, a Synthetic Module Record is created with the default ES Module export set to module.exports and keys of the module.exports object are re-exported as named exports (if the module.exports object is an object).Bun’s bundler works differently: it wraps the CommonJS module in a require_${moduleName} function which returns the module.exports object.

import.meta

The import.meta object exposes information about the current module. It’s part of the JavaScript language, but its contents are not standardized: each “host” (browser or runtime) implements its own properties on the import.meta object. Bun implements the following properties.
/path/to/project/file.ts
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"

import.meta.main; // `true` if this file is directly executed by `bun run`
// `false` otherwise

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
PropertyDescription
import.meta.dirAbsolute path to the directory containing the current file, e.g. /path/to/project. Equivalent to __dirname in CommonJS modules (and Node.js)
import.meta.dirnameAn alias to import.meta.dir, for Node.js compatibility
import.meta.envAn alias to process.env.
import.meta.fileThe name of the current file, e.g. index.tsx
import.meta.pathAbsolute path to the current file, e.g. /path/to/project/index.ts. Equivalent to __filename in CommonJS modules (and Node.js)
import.meta.filenameAn alias to import.meta.path, for Node.js compatibility
import.meta.mainIndicates whether the current file is the entrypoint to the current bun process: true if it’s executed directly by bun run, false if it’s imported
import.meta.resolveResolve a module specifier (e.g. "zod" or "./file.tsx") to a url. Equivalent to import.meta.resolve in browsers. Example: import.meta.resolve("zod") returns "file:///path/to/project/node_modules/zod/index.ts"
import.meta.urlA string url to the current file, e.g. file:///path/to/project/index.ts. Equivalent to import.meta.url in browsers