Syntax
Consider the following files.index.ts
, it prints “Hello world!”.
terminal
./hello
, a relative path with no extension. Extensioned imports are optional but supported. To resolve this import, Bun will check 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
from "*.js{x}"
, Bun will additionally check for a matching *.ts{x}
file, to be compatible with TypeScript’s ES module support.
import
/export
syntax) and CommonJS modules (require()
/module.exports
). The following CommonJS version would also work in Bun.
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,require
can be used by both ES Modules and CommonJS modules. 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 Type | require() | import * as |
---|---|---|
ES Module | Module Namespace | Module Namespace |
CommonJS | module.exports | default is module.exports , keys of module.exports are named exports |
Using require()
You can require()
any file or package, even .ts
or .mjs
files.
What is a CommonJS module?
What is a CommonJS module?
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 are modules that use
my-commonjs.cjsThe biggest difference between CommonJS and ES Modules is that CommonJS modules are synchronous, while ES Modules are asynchronous. There are other differences too.
module.exports
to export values. Typically, require
is used to import CommonJS modules.- 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 via
<script type="module">
. - CommonJS modules are not statically analyzable, while ES Modules only allow static imports and exports.
import
statements) are synchronous, just like CommonJS. This means that when you import an ESM using a regular import
statement, the code in that module runs immediately, and your program proceeds in a step-by-step manner. Think of it like reading a book page by page.Dynamic imports: Now, here comes the part that might be confusing. ES Modules also support importing modules on the fly via the import()
function. This is called a “dynamic import” and it’s asynchronous, so it doesn’t block the main program execution. Instead, it fetches and loads the module in the background while your program continues to run. Once the module is ready, you can use it. This is like getting additional information from a book while you’re still reading it, without having to pause your reading.In summary:- CommonJS modules and static ES Modules (
import
statements) work in a similar synchronous way, like reading a book from start to finish. - ES Modules also offer the option to import modules asynchronously using the
import()
function. This is like looking up additional information in the middle of reading the book without stopping.
Using import
You can import
any file or package, even .cjs
files.
Using import
and require()
together
In Bun, you can use import
or require
in the same file—they both work, all the time.
Top level await
The only exception to this rule is top-level await. You can’trequire()
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 being require()
from elsewhere in your application. Instead, you should use import
or dynamic import()
.
Importing packages
Bun implements the Node.js module resolution algorithm, so you can import packages fromnode_modules
with a bare specifier.
from "foo"
, Bun scans up the file system for a node_modules
directory containing the package foo
.
Once it finds the foo
package, Bun reads the package.json
to determine how the package should be imported. To determine the package’s entrypoint, Bun first reads the exports
field and checks for the following conditions.
package.json
package.json
is used to determine the package’s entrypoint.
Bun respects subpath "exports"
and "imports"
.
package.json
package.json
"exports"
map will prevent other subpaths from being importable; you can only import files that are explicitly exported. Given the package.json
above:
Shipping TypeScript — Note that 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 will
directly import and execute your TypeScript source files.exports
is not defined, Bun falls back to "module"
(ESM imports only) then "main"
.
package.json
Custom conditions
The--conditions
flag allows you to specify a list of conditions to use when resolving packages from package.json "exports"
.
This flag is supported in both bun build
and Bun’s runtime.
terminal
conditions
programmatically with Bun.build
:
Path re-mapping
In the spirit of treating TypeScript as a first-class citizen, the Bun runtime will re-map import paths according to thecompilerOptions.paths
field in tsconfig.json
. This is a major divergence from Node.js, which doesn’t support any form of import path re-mapping.
tsconfig.json
jsconfig.json
in your project root to achieve the same behavior.
Low-level details of CommonJS interop in Bun
Low-level details of CommonJS interop in Bun
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 will then wrap the transpiled module in a function shaped like this:module
, exports
, and require
are very much like the module
, exports
, and require
in Node.js. These are assigned via 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).When using Bun’s bundler, this works differently. The bundler will wrap the CommonJS module in a require_${moduleName}
function which returns the module.exports
object.import.meta
The import.meta
object is a way for a module to access information about itself. It’s part of the JavaScript language, but its contents are not standardized. Each “host” (browser, runtime, etc) is free to implement any properties it wishes on the import.meta
object.
Bun implements the following properties.
/path/to/project/file.ts
Property | Description |
---|---|
import.meta.dir | Absolute path to the directory containing the current file, e.g. /path/to/project . Equivalent to __dirname in CommonJS modules (and Node.js) |
import.meta.dirname | An alias to import.meta.dir , for Node.js compatibility |
import.meta.env | An alias to process.env . |
import.meta.file | The name of the current file, e.g. index.tsx |
import.meta.path | Absolute path to the current file, e.g. /path/to/project/index.ts . Equivalent to __filename in CommonJS modules (and Node.js) |
import.meta.filename | An alias to import.meta.path , for Node.js compatibility |
import.meta.main | Indicates whether the current file is the entrypoint to the current bun process. Is the file being directly executed by bun run or is it being imported? |
import.meta.resolve | Resolve 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.url | A string url to the current file, e.g. file:///path/to/project/index.ts . Equivalent to import.meta.url in browsers |