Skip to content

La risoluzione dei moduli in JavaScript è un argomento complesso.

L'ecosistema è attualmente nel mezzo di una transizione pluriennale dai moduli CommonJS ai moduli ES nativi. TypeScript impone il proprio insieme di regole intorno alle estensioni di import che non sono compatibili con ESM. Diversi strumenti di build supportano la rimappatura dei percorsi tramite meccanismi disparati e non compatibili.

Bun mira a fornire un sistema di risoluzione dei moduli coerente e prevedibile che funzioni semplicemente. Purtroppo è ancora piuttosto complesso.

Sintassi

Considera i seguenti file.

ts
import { hello } from "./hello";

hello();
ts
export function hello() {
  console.log("Hello world!");
}

Quando eseguiamo index.ts, stampa "Hello world!".

bash
bun index.ts
Hello world!

In questo caso, stiamo importando da ./hello, un percorso relativo senza estensione. Gli import con estensione sono opzionali ma supportati. Per risolvere questo import, Bun controllerà i seguenti file in ordine:

  • ./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

I percorsi di import possono opzionalmente includere estensioni. Se è presente un'estensione, Bun controllerà solo un file con quella esatta estensione.

ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // funziona

Se importi from "*.js{x}", Bun controllerà inoltre un file *.ts{x} corrispondente, per essere compatibile con il supporto ai moduli ES di TypeScript.

ts
import { hello } from "./hello";
import { hello } from "./hello.ts"; // funziona
import { hello } from "./hello.js"; // funziona anche questo

Bun supporta sia i moduli ES (sintassi import/export) che i moduli CommonJS (require()/module.exports). La seguente versione CommonJS funzionerebbe anche in Bun.

js
const { hello } = require("./hello");

hello();
js
function hello() {
  console.log("Hello world!");
}

exports.hello = hello;

Detto questo, l'uso di CommonJS è sconsigliato nei nuovi progetti.


Sistemi di moduli

Bun ha supporto nativo per CommonJS e moduli ES. I moduli ES sono il formato di modulo consigliato per i nuovi progetti, ma i moduli CommonJS sono ancora ampiamente utilizzati nell'ecosistema Node.js.

Nel runtime JavaScript di Bun, require può essere usato sia dai moduli ES che dai moduli CommonJS. Se il modulo di destinazione è un modulo ES, require restituisce l'oggetto namespace del modulo (equivalente a import * as). Se il modulo di destinazione è un modulo CommonJS, require restituisce l'oggetto module.exports (come in Node.js).

Tipo di Modulorequire()import * as
Modulo ESNamespace ModuloNamespace Modulo
CommonJSmodule.exportsdefault è module.exports, le chiavi di module.exports sono export nominati

Usare require()

Puoi usare require() con qualsiasi file o pacchetto, anche file .ts o .mjs.

index.ts
ts
const { foo } = require("./foo"); // le estensioni sono opzionali
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");

Cos'è un modulo CommonJS?">

Nel 2016, ECMAScript ha aggiunto il supporto per i moduli ES. I moduli ES sono lo standard per i moduli JavaScript. Tuttavia, milioni di pacchetti npm usano ancora i moduli CommonJS.

I moduli CommonJS sono moduli che usano module.exports per esportare valori. Tipicamente, require è usato per importare moduli CommonJS.

ts
const stuff = require("./stuff");
module.exports = { stuff };

La differenza più grande tra CommonJS e i moduli ES è che i moduli CommonJS sono sincroni, mentre i moduli ES sono asincroni. Ci sono anche altre differenze.

  • I moduli ES supportano await a livello superiore e i moduli CommonJS no.
  • I moduli ES sono sempre in strict mode, mentre i moduli CommonJS no.
  • I browser non hanno supporto nativo per i moduli CommonJS, ma hanno supporto nativo per i moduli ES tramite <script type="module">.
  • I moduli CommonJS non sono analizzabili staticamente, mentre i moduli ES consentono solo import ed export statici.

Moduli CommonJS: Questi sono un tipo di sistema di moduli usato in JavaScript. Una caratteristica chiave dei moduli CommonJS è che vengono caricati ed eseguiti in modo sincrono. Questo significa che quando importi un modulo CommonJS, il codice in quel modulo viene eseguito immediatamente e il tuo programma aspetta che finisca prima di passare al compito successivo. È simile alla lettura di un libro dall'inizio alla fine senza saltare pagine.

Moduli ES (ESM): Questi sono un altro tipo di sistema di moduli introdotto in JavaScript. Hanno un comportamento leggermente diverso rispetto a CommonJS. In ESM, gli import statici (import fatti usando istruzioni import) sono sincroni, proprio come CommonJS. Questo significa che quando importi un ESM usando una normale istruzione import, il codice in quel modulo viene eseguito immediatamente e il tuo programma procede passo dopo passo. Pensalo come leggere un libro pagina per pagina.

Import dinamici: Ora, ecco la parte che potrebbe creare confusione. I moduli ES supportano anche l'import di moduli al volo tramite la funzione import(). Questo è chiamato "import dinamico" ed è asincrono, quindi non blocca l'esecuzione del programma principale. Invece, recupera e carica il modulo in background mentre il tuo programma continua a funzionare. Una volta che il modulo è pronto, puoi usarlo. È come ottenere informazioni aggiuntive da un libro mentre lo stai ancora leggendo, senza dover interrompere la lettura.

In sintesi:

  • I moduli CommonJS e i moduli ES statici (istruzioni import) funzionano in modo sincrono simile, come leggere un libro dall'inizio alla fine.
  • I moduli ES offrono anche l'opzione di importare moduli in modo asincrono usando la funzione import(). È come cercare informazioni aggiuntive nel mezzo della lettura del libro senza fermarsi.

Usare import

Puoi import qualsiasi file o pacchetto, anche file .cjs.

index.ts
ts
import { foo } from "./foo"; // le estensioni sono opzionali
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";

Usare import e require() insieme

In Bun, puoi usare import o require nello stesso file—funzionano entrambi, sempre.

index.ts
ts
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

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

Top level await

L'unica eccezione a questa regola è il top-level await. Non puoi require() un file che usa top-level await, poiché la funzione require() è intrinsecamente sincrona.

Fortunatamente, pochissime librerie usano top-level await, quindi questo è raramente un problema. Ma se stai usando top-level await nel tuo codice applicativo, assicurati che quel file non venga require() da altre parti della tua applicazione. Invece, dovresti usare import o dynamic import().


Importare pacchetti

Bun implementa l'algoritmo di risoluzione dei moduli di Node.js, quindi puoi importare pacchetti da node_modules con uno specificatore semplice.

index.ts
ts
import { stuff } from "foo";

Le specifiche complete di questo algoritmo sono documentate ufficialmente nella documentazione di Node.js; non le ripeteremo qui. In breve: se importi from "foo", Bun scansiona il file system alla ricerca di una directory node_modules contenente il pacchetto foo.

NODE_PATH

Bun supporta NODE_PATH per directory di risoluzione dei moduli aggiuntive:

bash
NODE_PATH=./packages bun run src/index.js
ts
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";

Percorsi multipli usano il delimitatore della piattaforma (: su Unix, ; su Windows):

bash
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows

Una volta trovato il pacchetto foo, Bun legge il package.json per determinare come il pacchetto dovrebbe essere importato. Per determinare il punto di ingresso del pacchetto, Bun legge prima il campo exports e controlla le seguenti condizioni.

json
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node": "./index.js",
    "require": "./index.js", // se l'importatore è CommonJS
    "import": "./index.mjs", // se l'importatore è modulo ES
    "default": "./index.js"
  }
}

Quale di queste condizioni si verifica per prima nel package.json viene usata per determinare il punto di ingresso del pacchetto.

Bun rispetta i subpath "exports" e "imports".

json
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}

Gli import di subpath e gli import condizionali funzionano in congiunzione tra loro.

json
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}

Come in Node.js, specificare qualsiasi subpath nella mappa "exports" impedirà ad altri subpath di essere importabili; puoi importare solo file che sono esplicitamente esportati. Dato il package.json sopra:

ts
import stuff from "foo"; // funziona
import stuff from "foo/index.mjs"; // non funziona

NOTE

**Distribuire TypeScript** — Nota che Bun supporta la condizione di export speciale `"bun"`. Se la tua libreria è scritta in TypeScript, puoi pubblicare i tuoi file TypeScript (non transpilati!) direttamente su `npm`. Se specifichi il punto di ingresso `*.ts` del tuo pacchetto nella condizione `"bun"`, Bun importerà ed eseguirà direttamente i tuoi file sorgente TypeScript.

Se exports non è definito, Bun ripiega su "module" (solo import ESM) poi su "main".

json
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}

Condizioni personalizzate

Il flag --conditions ti permette di specificare un elenco di condizioni da usare quando si risolvono i pacchetti dal "exports" del package.json.

Questo flag è supportato sia in bun build che nel runtime di Bun.

sh
# Usalo con bun build:
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# Usalo con il runtime di bun:
bun --conditions="react-server" ./app/foo/route.js

Puoi anche usare conditions programmaticamente con Bun.build:

ts
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});

Rimappatura dei percorsi

Bun supporta la rimappatura dei percorsi di import tramite compilerOptions.paths di TypeScript in tsconfig.json, che funziona bene con gli editor. Se non sei un utente TypeScript, puoi ottenere lo stesso comportamento usando un jsconfig.json nella root del tuo progetto.

json
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // mappa lo specificatore al file
      "components/*": ["components/*"] // corrispondenza wildcard
    }
  }
}

Bun supporta anche gli import di subpath in stile Node.js in package.json, dove i percorsi mappati devono iniziare con #. Questo approccio non funziona altrettanto bene con gli editor, ma entrambe le opzioni possono essere usate insieme.

json
{
  "imports": {
    "#config": "./config.ts", // mappa lo specificatore al file
    "#components/*": "./components/*" // corrispondenza wildcard
  }
}

Dettagli di basso livello dell'interop CommonJS in Bun">

Il runtime JavaScript di Bun ha supporto nativo per CommonJS. Quando il transpiler JavaScript di Bun rileva usi di module.exports, tratta il file come CommonJS. Il caricatore di moduli avvolgerà quindi il modulo transpilato in una funzione strutturata così:

js
(function (module, exports, require) {
  // modulo transpilato
})(module, exports, require);

module, exports, e require sono molto simili al module, exports, e require in Node.js. Questi sono assegnati tramite uno with scope in C++. Una Map interna memorizza l'oggetto exports per gestire le chiamate require cicliche prima che il modulo sia completamente caricato.

Una volta che il modulo CommonJS è stato valutato con successo, viene creato un Synthetic Module Record con l'export del modulo ES default impostato su module.exports e le chiavi dell'oggetto module.exports sono riesportate come export nominati (se l'oggetto module.exports è un oggetto).

Quando si usa il bundler di Bun, questo funziona in modo diverso. Il bundler avvolgerà il modulo CommonJS in una funzione require_${moduleName} che restituisce l'oggetto module.exports.


import.meta

L'oggetto import.meta è un modo per un modulo di accedere alle informazioni su se stesso. Fa parte del linguaggio JavaScript, ma i suoi contenuti non sono standardizzati. Ogni "host" (browser, runtime, ecc) è libero di implementare qualsiasi proprietà desideri sull'oggetto import.meta.

Bun implementa le seguenti proprietà.

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` se questo file è eseguito direttamente da `bun run`
// `false` altrimenti

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
ProprietàDescrizione
import.meta.dirPercorso assoluto alla directory contenente il file corrente, ad es. /path/to/project. Equivalente a __dirname nei moduli CommonJS (e Node.js)
import.meta.dirnameUn alias per import.meta.dir, per compatibilità con Node.js
import.meta.envUn alias per process.env.
import.meta.fileIl nome del file corrente, ad es. index.tsx
import.meta.pathPercorso assoluto al file corrente, ad es. /path/to/project/index.ts. Equivalente a __filename nei moduli CommonJS (e Node.js)
import.meta.filenameUn alias per import.meta.path, per compatibilità con Node.js
import.meta.mainIndica se il file corrente è il punto di ingresso del processo bun corrente. Il file viene eseguito direttamente da bun run o viene importato?
import.meta.resolveRisolve uno specificatore di modulo (ad es. "zod" o "./file.tsx") in un url. Equivalente a import.meta.resolve nei browser. Esempio: import.meta.resolve("zod") restituisce "file:///path/to/project/node_modules/zod/index.ts"
import.meta.urlUn url string al file corrente, ad es. file:///path/to/project/index.ts. Equivalente a import.meta.url nei browser

Bun a cura di www.bunjs.com.cn