Files
bun.sh/docs/runtime/modules.md
Colin McDonnell 011e157cac Docs restructuring (#2638)
* 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
2023-04-13 18:26:45 -07:00

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.