Module resolution in JavaScript is a complex topic. The ecosystem is currently in the midst of a years-long transition from CommonJS modules to native ES modules. TypeScript enforces its own set of rules around import extensions that aren't compatible with ESM. Different build tools support path re-mapping via disparate non-compatible mechanisms. Bun aims to provide a consistent and predictable module resolution system that just works. Unfortunately it's still quite complex. ## Syntax Consider the following files. {% codetabs %} ```ts#index.ts import { hello } from "./hello"; hello(); ``` ```ts#hello.ts export function hello() { console.log("Hello world!"); } ``` {% /codetabs %} When we run `index.ts`, it prints "Hello world". ```bash $ bun index.ts Hello world! ``` In this case, we are importing from `./hello`, a relative path with no extension. To resolve this import, Bun will check for the following files in order: - `./hello.ts` - `./hello.tsx` - `./hello.js` - `./hello.mjs` - `./hello.cjs` - `./hello/index.ts` - `./hello/index.js` - `./hello/index.json` - `./hello/index.mjs` Import paths are case-insensitive. ```ts#index.ts import { hello } from "./hello"; import { hello } from "./HELLO"; import { hello } from "./hElLo"; ``` Import paths can optionally include extensions. If an extension is present, Bun will only check for a file with that exact extension. ```ts#index.ts import { hello } from "./hello"; import { hello } from "./hello.ts"; // this works ``` There is one exception: if you import `from "*.js{x}"`, Bun will additionally check for a matching `*.ts{x}` file, to be compatible with TypeScript's [ES module support](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#new-file-extensions). ```ts#index.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 would also work in Bun. {% codetabs %} ```ts#index.js const { hello } = require("./hello"); hello(); ``` ```ts#hello.js function hello() { console.log("Hello world!"); } exports.hello = hello; ``` {% /codetabs %} That said, using CommonJS is discouraged in new projects. ## Resolution Bun implements the Node.js module resolution algorithm, so you can import packages from `node_modules` with a bare specifier. ```ts import { stuff } from "foo"; ``` The full specification of this algorithm are officially documented in the [Node.js documentation](https://nodejs.org/api/modules.html); we won't rehash it here. Briefly: if you import `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. Unless `"type": "module"` is specified, Bun assumes the package is using CommonJS and transpiles into a synchronous ES module internally. To determine the package's entrypoint, Bun first reads the `exports` field in and checks the following conditions in order: ```jsonc#package.json { "name": "foo", "exports": { "bun": "./index.js", // highest priority "worker": "./index.js", "module": "./index.js", "node": "./index.js", "browser": "./index.js", "default": "./index.js" // lowest priority } } ``` Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) and [`"imports"`](https://nodejs.org/api/packages.html#imports). Specifying any subpath in the `"exports"` map will prevent other subpaths from being importable. ```jsonc#package.json { "name": "foo", "exports": { ".": "./index.js", "./package.json": "./package.json" // subpath } } ``` {% callout %} **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. {% /callout %} If `exports` is not defined, Bun falls back to `"module"` (ESM imports only) then [`"main"`](https://nodejs.org/api/packages.html#main). ```json#package.json { "name": "foo", "module": "./index.js", "main": "./index.js" } ``` ## Path re-mapping In the spirit of treating TypeScript as a first-class citizen, the Bun runtime will re-map import paths according to the [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) field in `tsconfig.json`. This is a major divergence from Node.js, which doesn't support any form of import path re-mapping. ```jsonc#tsconfig.json { "compilerOptions": { "paths": { "config": ["./config.ts"], // map specifier to file "components/*": ["components/*"], // wildcard matching } } } ``` If you aren't a TypeScript user, you can create a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root to achieve the same behavior. ## CommonJS Bun has native support for CommonJS modules (added in Bun v0.6.5). In Bun's JavaScript runtime, `require` can be used by both ES Modules and CommonJS modules. In Bun, you can `require()` ESM modules from CommonJS modules. | 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 | 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. ### What is a CommonJS module? In 2016, ECMAScript added support for [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). ES Modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules. CommonJS modules are modules that use `module.exports` to export values. Typically, `require` is used to import CommonJS modules. ```ts // my-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. There are other differences too, like ES Modules support top-level `await` and CommonJS modules don't. ES Modules are always in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/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 (`