mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
* Restructure * Update nav * Reorg * Reshuffle ecosystem pages * Split up runtime/runtime * Back to runtime/index * Fix issue * Split up runtime/index * Add Writing Tests page * Prettier matcher table * More updates
159 lines
5.1 KiB
Markdown
159 lines
5.1 KiB
Markdown
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.
|