Skip to content

Moduli: Pacchetti

[Cronologia]

VersioneModifiche
v14.13.0, v12.20.0Aggiunto il supporto per i pattern "exports".
v14.6.0, v12.19.0Aggiunto il campo "imports" del pacchetto.
v13.7.0, v12.17.0Rimosse le esportazioni condizionali.
v13.7.0, v12.16.0Rimossa l'opzione --experimental-conditional-exports. Nella 12.16.0, le esportazioni condizionali sono ancora dietro a --experimental-modules.
v13.6.0, v12.16.0Rimosso il flag di auto-referenziazione di un pacchetto usando il suo nome.
v12.7.0Introdotto il campo "exports" in package.json come alternativa più potente al classico campo "main".
v12.0.0Aggiunto il supporto per i moduli ES usando l'estensione file .js tramite il campo "type" in package.json.

Introduzione

Un pacchetto è una struttura ad albero di cartelle descritta da un file package.json. Il pacchetto è costituito dalla cartella contenente il file package.json e tutte le sottocartelle fino alla successiva cartella contenente un altro file package.json o una cartella denominata node_modules.

Questa pagina fornisce una guida per gli autori di pacchetti che scrivono file package.json, insieme a un riferimento per i campi package.json definiti da Node.js.

Determinazione del sistema di moduli

Introduzione

Node.js tratterà quanto segue come moduli ES quando passato a node come input iniziale, o quando referenziato da istruzioni import o espressioni import():

  • File con estensione .mjs.
  • File con estensione .js quando il file package.json padre più vicino contiene un campo di primo livello "type" con un valore di "module".
  • Stringhe passate come argomento a --eval, o inviate a node tramite STDIN, con il flag --input-type=module.
  • Codice contenente una sintassi analizzata con successo solo come moduli ES, come istruzioni import o export o import.meta, senza un indicatore esplicito di come dovrebbe essere interpretato. Gli indicatori espliciti sono le estensioni .mjs o .cjs, i campi "type" di package.json con valori "module" o "commonjs", o il flag --input-type. Le espressioni dinamiche import() sono supportate sia nei moduli CommonJS che ES e non forzerebbero un file a essere trattato come un modulo ES. Vedi Rilevamento della sintassi.

Node.js tratterà quanto segue come CommonJS quando passato a node come input iniziale, o quando referenziato da istruzioni import o espressioni import():

  • File con estensione .cjs.
  • File con estensione .js quando il file package.json padre più vicino contiene un campo di primo livello "type" con un valore di "commonjs".
  • Stringhe passate come argomento a --eval o --print, o inviate a node tramite STDIN, con il flag --input-type=commonjs.
  • File con estensione .js senza un file package.json padre o dove il file package.json padre più vicino non ha un campo type, e dove il codice può essere valutato con successo come CommonJS. In altre parole, Node.js prova prima a eseguire tali file "ambigui" come CommonJS e riproverà a valutarli come moduli ES se la valutazione come CommonJS fallisce perché il parser ha trovato la sintassi del modulo ES.

Scrivere la sintassi del modulo ES in file "ambigui" comporta un costo in termini di prestazioni, e pertanto si consiglia agli autori di essere espliciti ove possibile. In particolare, gli autori dei pacchetti dovrebbero sempre includere il campo "type" nei loro file package.json, anche nei pacchetti in cui tutte le fonti sono CommonJS. Essere espliciti sul type del pacchetto metterà al sicuro il pacchetto nel caso in cui il tipo predefinito di Node.js cambi, e renderà anche più facile per gli strumenti di build e i loader determinare come i file nel pacchetto dovrebbero essere interpretati.

Rilevamento della sintassi

[Cronologia]

VersioneModifiche
v22.7.0Il rilevamento della sintassi è abilitato di default.
v21.1.0, v20.10.0Aggiunto in: v21.1.0, v20.10.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1.2 - Release candidate

Node.js ispezionerà il codice sorgente di input ambiguo per determinare se contiene la sintassi del modulo ES; se tale sintassi viene rilevata, l'input verrà trattato come un modulo ES.

L'input ambiguo è definito come:

  • File con estensione .js o senza estensione; e nessun file package.json di controllo o uno che manca di un campo type.
  • Input di stringhe (--eval o STDIN) quando --input-type non è specificato.

La sintassi del modulo ES è definita come sintassi che verrebbe lanciata se valutata come CommonJS. Ciò include quanto segue:

  • Istruzioni import (ma non espressioni import(), che sono valide in CommonJS).
  • Istruzioni export.
  • Riferimenti import.meta.
  • await al livello superiore di un modulo.
  • Ridefinizioni lessicali delle variabili wrapper CommonJS (require, module, exports, __dirname, __filename).

Caricatori di moduli

Node.js ha due sistemi per risolvere uno specificatore e caricare i moduli.

C'è il caricatore di moduli CommonJS:

  • È completamente sincrono.
  • È responsabile della gestione delle chiamate require().
  • È modificabile tramite monkey patch.
  • Supporta le cartelle come moduli.
  • Quando si risolve uno specificatore, se non viene trovata una corrispondenza esatta, tenterà di aggiungere estensioni (.js, .json e infine .node) e quindi tenterà di risolvere cartelle come moduli.
  • Considera .json come file di testo JSON.
  • I file .node vengono interpretati come moduli addon compilati caricati con process.dlopen().
  • Considera tutti i file privi di estensione .json o .node come file di testo JavaScript.
  • Può essere utilizzato solo per caricare moduli ECMAScript da moduli CommonJS se il grafico dei moduli è sincrono (ovvero non contiene await di livello superiore). Se utilizzato per caricare un file di testo JavaScript che non è un modulo ECMAScript, il file verrà caricato come modulo CommonJS.

C'è il caricatore di moduli ECMAScript:

  • È asincrono, a meno che non venga utilizzato per caricare moduli per require().
  • È responsabile della gestione delle istruzioni import e delle espressioni import().
  • Non è modificabile tramite monkey patch, può essere personalizzato utilizzando gli hook del caricatore.
  • Non supporta le cartelle come moduli, gli indici di directory (ad es. './startup/index.js') devono essere specificati completamente.
  • Non effettua ricerche di estensioni. Un'estensione di file deve essere fornita quando lo specificatore è un URL di file relativo o assoluto.
  • Può caricare moduli JSON, ma è richiesto un attributo del tipo di importazione.
  • Accetta solo estensioni .js, .mjs e .cjs per file di testo JavaScript.
  • Può essere utilizzato per caricare moduli JavaScript CommonJS. Tali moduli vengono passati attraverso cjs-module-lexer per cercare di identificare le esportazioni con nome, che sono disponibili se possono essere determinate tramite analisi statica. I moduli CommonJS importati hanno i loro URL convertiti in percorsi assoluti e vengono quindi caricati tramite il caricatore di moduli CommonJS.

package.json ed estensioni dei file

All'interno di un package, il campo "type" del package.json definisce come Node.js deve interpretare i file .js. Se un file package.json non ha un campo "type", i file .js vengono trattati come CommonJS.

Un valore "type" di "module" nel package.json indica a Node.js di interpretare i file .js all'interno di quel package come se utilizzassero la sintassi ES module.

Il campo "type" si applica non solo ai punti di ingresso iniziali (node my-app.js) ma anche ai file a cui si fa riferimento tramite istruzioni import ed espressioni import().

js
// my-app.js, trattato come un ES module perché esiste un file package.json
// nella stessa cartella con "type": "module".

import './startup/init.js'
// Caricato come ES module poiché ./startup non contiene un file package.json,
// e quindi eredita il valore "type" dal livello superiore.

import 'commonjs-package'
// Caricato come CommonJS poiché ./node_modules/commonjs-package/package.json
// non ha un campo "type" o contiene "type": "commonjs".

import './node_modules/commonjs-package/index.js'
// Caricato come CommonJS poiché ./node_modules/commonjs-package/package.json
// non ha un campo "type" o contiene "type": "commonjs".

I file che terminano con .mjs vengono sempre caricati come ES module indipendentemente dal package.json principale più vicino.

I file che terminano con .cjs vengono sempre caricati come CommonJS indipendentemente dal package.json principale più vicino.

js
import './legacy-file.cjs'
// Caricato come CommonJS poiché .cjs viene sempre caricato come CommonJS.

import 'commonjs-package/src/index.mjs'
// Caricato come ES module poiché .mjs viene sempre caricato come ES module.

Le estensioni .mjs e .cjs possono essere utilizzate per combinare i tipi all'interno dello stesso package:

  • All'interno di un package con "type": "module", Node.js può essere istruito per interpretare un determinato file come CommonJS nominandolo con un'estensione .cjs (poiché sia i file .js che .mjs vengono trattati come ES module all'interno di un package con "module").
  • All'interno di un package con "type": "commonjs", Node.js può essere istruito per interpretare un determinato file come ES module nominandolo con un'estensione .mjs (poiché sia i file .js che .cjs vengono trattati come CommonJS all'interno di un package con "commonjs").

Flag --input-type

Aggiunto in: v12.0.0

Le stringhe passate come argomento a --eval (o -e), o inviate tramite pipe a node via STDIN, sono trattate come moduli ES quando è impostato il flag --input-type=module.

bash
node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"

echo "import { sep } from 'node:path'; console.log(sep);" | node --input-type=module

Per completezza, esiste anche --input-type=commonjs, per eseguire esplicitamente l'input di stringa come CommonJS. Questo è il comportamento predefinito se --input-type non è specificato.

Determinare il gestore di pacchetti

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

Sebbene ci si aspetti che tutti i progetti Node.js siano installabili da tutti i gestori di pacchetti una volta pubblicati, i loro team di sviluppo sono spesso tenuti a utilizzare uno specifico gestore di pacchetti. Per semplificare questo processo, Node.js viene fornito con uno strumento chiamato Corepack che mira a rendere tutti i gestori di pacchetti trasparentemente disponibili nel tuo ambiente, a condizione che tu abbia installato Node.js.

Per impostazione predefinita, Corepack non applicherà alcun gestore di pacchetti specifico e utilizzerà le versioni generiche "Last Known Good" associate a ogni versione di Node.js, ma puoi migliorare questa esperienza impostando il campo "packageManager" nel package.json del tuo progetto.

Punti di ingresso del pacchetto

Nel file package.json di un pacchetto, due campi possono definire i punti di ingresso per un pacchetto: "main" e "exports". Entrambi i campi si applicano sia ai punti di ingresso del modulo ES che del modulo CommonJS.

Il campo "main" è supportato in tutte le versioni di Node.js, ma le sue capacità sono limitate: definisce solo il punto di ingresso principale del pacchetto.

Il campo "exports" fornisce un'alternativa moderna a "main" consentendo di definire più punti di ingresso, il supporto della risoluzione dell'ingresso condizionale tra gli ambienti e impedendo qualsiasi altro punto di ingresso oltre a quelli definiti in "exports". Questa incapsulamento consente agli autori di moduli di definire chiaramente l'interfaccia pubblica per il loro pacchetto.

Per i nuovi pacchetti destinati alle versioni attualmente supportate di Node.js, si consiglia il campo "exports". Per i pacchetti che supportano Node.js 10 e versioni precedenti, è richiesto il campo "main". Se sono definiti sia "exports" che "main", il campo "exports" ha la precedenza su "main" nelle versioni supportate di Node.js.

Le esportazioni condizionali possono essere utilizzate all'interno di "exports" per definire diversi punti di ingresso del pacchetto per ambiente, incluso se il pacchetto viene referenziato tramite require o tramite import. Per maggiori informazioni sul supporto di moduli CommonJS ed ES in un singolo pacchetto, si prega di consultare la sezione pacchetti a doppio modulo CommonJS/ES.

I pacchetti esistenti che introducono il campo "exports" impediranno ai consumatori del pacchetto di utilizzare qualsiasi punto di ingresso non definito, incluso il package.json (ad es. require('your-package/package.json')). Questo sarà probabilmente una modifica che causa interruzioni.

Per rendere non interruttiva l'introduzione di "exports", assicurarsi che ogni punto di ingresso precedentemente supportato venga esportato. È meglio specificare esplicitamente i punti di ingresso in modo che l'API pubblica del pacchetto sia ben definita. Ad esempio, un progetto che in precedenza esportava main, lib, feature e package.json potrebbe utilizzare il seguente package.exports:

json
{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
}

In alternativa, un progetto potrebbe scegliere di esportare intere cartelle sia con che senza sottopath estesi utilizzando modelli di esportazione:

json
{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./lib/*.js": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./feature/*.js": "./feature/*.js",
    "./package.json": "./package.json"
  }
}

Con quanto sopra che fornisce la retrocompatibilità per qualsiasi versione minore del pacchetto, una futura modifica importante per il pacchetto può quindi limitare correttamente le esportazioni solo alle esportazioni specifiche delle funzionalità esposte:

json
{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./feature/*.js": "./feature/*.js",
    "./feature/internal/*": null
  }
}

Punto di ingresso principale per l'esportazione

Quando si scrive un nuovo pacchetto, si raccomanda di utilizzare il campo "exports":

json
{
  "exports": "./index.js"
}

Quando il campo "exports" è definito, tutti i sottocammini del pacchetto sono incapsulati e non più disponibili per gli importatori. Per esempio, require('pkg/subpath.js') genera un errore ERR_PACKAGE_PATH_NOT_EXPORTED.

Questa incapsulamento delle esportazioni fornisce garanzie più affidabili sulle interfacce dei pacchetti per gli strumenti e quando si gestiscono gli aggiornamenti semver per un pacchetto. Non è un incapsulamento forte, poiché un require diretto di qualsiasi sottocammino assoluto del pacchetto come require('/path/to/node_modules/pkg/subpath.js') caricherà comunque subpath.js.

Tutte le versioni attualmente supportate di Node.js e gli strumenti di build moderni supportano il campo "exports". Per i progetti che utilizzano una versione precedente di Node.js o un relativo strumento di build, la compatibilità può essere raggiunta includendo il campo "main" insieme a "exports" che punta allo stesso modulo:

json
{
  "main": "./index.js",
  "exports": "./index.js"
}

Esportazione di sottocammini

Aggiunto in: v12.7.0

Quando si utilizza il campo "exports", i sottocammini personalizzati possono essere definiti insieme al punto di ingresso principale trattando il punto di ingresso principale come il sottocammino ".":

json
{
  "exports": {
    ".": "./index.js",
    "./submodule.js": "./src/submodule.js"
  }
}

Ora solo il sottocammino definito in "exports" può essere importato da un consumatore:

js
import submodule from 'es-module-package/submodule.js'
// Carica ./node_modules/es-module-package/src/submodule.js

Mentre altri sottocammini genereranno errori:

js
import submodule from 'es-module-package/private-module.js'
// Genera ERR_PACKAGE_PATH_NOT_EXPORTED

Estensioni nei sottocammini

Gli autori dei pacchetti dovrebbero fornire sottocammini con estensione (import 'pkg/subpath.js') o senza estensione (import 'pkg/subpath') nelle loro esportazioni. Ciò garantisce che ci sia un solo sottocammino per ogni modulo esportato, in modo che tutti i dipendenti importino lo stesso specificatore coerente, mantenendo chiaro il contratto del pacchetto per i consumatori e semplificando i completamenti dei sottocammini del pacchetto.

Tradizionalmente, i pacchetti tendevano a utilizzare lo stile senza estensione, che ha i vantaggi della leggibilità e del mascheramento del vero percorso del file all'interno del pacchetto.

Con le mappe di importazione che ora forniscono uno standard per la risoluzione dei pacchetti nei browser e in altri runtime JavaScript, l'utilizzo dello stile senza estensione può comportare definizioni di mappe di importazione gonfie. Le estensioni di file esplicite possono evitare questo problema consentendo alla mappa di importazione di utilizzare una mappatura della cartella dei pacchetti per mappare più sottocammini dove possibile invece di una voce di mappa separata per ciascuna esportazione di sottocammino del pacchetto. Questo rispecchia anche il requisito di utilizzare il percorso completo dello specificatore negli specificatori di importazione relativi e assoluti.

Esportazioni semplificate

Aggiunto in: v12.11.0

Se l'esportazione "." è l'unica esportazione, il campo "exports" fornisce una scorciatoia per questo caso, essendo il valore diretto del campo "exports".

json
{
  "exports": {
    ".": "./index.js"
  }
}

può essere scritto:

json
{
  "exports": "./index.js"
}

Importazioni di sottopath

Aggiunto in: v14.6.0, v12.19.0

Oltre al campo "exports", esiste un campo "imports" del pacchetto per creare mappature private che si applicano solo agli specificatori di importazione dall'interno del pacchetto stesso.

Le voci nel campo "imports" devono sempre iniziare con # per garantire che siano disambiguate dagli specificatori di pacchetti esterni.

Ad esempio, il campo imports può essere utilizzato per ottenere i vantaggi delle esportazioni condizionali per i moduli interni:

json
// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
}

dove import '#dep' non ottiene la risoluzione del pacchetto esterno dep-node-native (incluse le sue esportazioni a sua volta) e invece ottiene il file locale ./dep-polyfill.js relativo al pacchetto in altri ambienti.

A differenza del campo "exports", il campo "imports" consente la mappatura a pacchetti esterni.

Le regole di risoluzione per il campo imports sono altrimenti analoghe al campo exports.

Modelli di sottopath

[Cronologia]

VersioneModifiche
v16.10.0, v14.19.0Supporto per i trailer di pattern nel campo "imports".
v16.9.0, v14.19.0Supporto per i trailer di pattern.
v14.13.0, v12.20.0Aggiunto in: v14.13.0, v12.20.0

Per i pacchetti con un piccolo numero di esportazioni o importazioni, si consiglia di elencare esplicitamente ciascuna voce di sottopath di esportazione. Ma per i pacchetti che hanno un gran numero di sottopath, ciò potrebbe causare un'eccessiva espansione del file package.json e problemi di manutenzione.

Per questi casi d'uso, è possibile utilizzare invece pattern di esportazione di sottopath:

json
// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js"
  },
  "imports": {
    "#internal/*.js": "./src/internal/*.js"
  }
}

* le mappe espongono i sottopath nidificati in quanto è solo una sintassi di sostituzione di stringhe.

Tutte le istanze di * sul lato destro verranno quindi sostituite con questo valore, anche se contiene separatori /.

js
import featureX from 'es-module-package/features/x.js'
// Carica ./node_modules/es-module-package/src/features/x.js

import featureY from 'es-module-package/features/y/y.js'
// Carica ./node_modules/es-module-package/src/features/y/y.js

import internalZ from '#internal/z.js'
// Carica ./node_modules/es-module-package/src/internal/z.js

Questa è una corrispondenza e sostituzione statica diretta senza alcuna gestione speciale per le estensioni di file. Includere "*.js" su entrambi i lati della mappatura limita le esportazioni di pacchetti esposte solo ai file JS.

La proprietà delle esportazioni che possono essere staticamente enumerate viene mantenuta con i pattern di esportazione poiché le singole esportazioni per un pacchetto possono essere determinate trattando il pattern di destinazione sul lato destro come un glob ** rispetto all'elenco di file all'interno del pacchetto. Poiché i percorsi node_modules sono vietati nelle destinazioni di esportazione, questa espansione dipende solo dai file del pacchetto stesso.

Per escludere sottocartelle private dai pattern, è possibile utilizzare destinazioni null:

json
// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js",
    "./features/private-internal/*": null
  }
}
js
import featureInternal from 'es-module-package/features/private-internal/m.js'
// Restituisce: ERR_PACKAGE_PATH_NOT_EXPORTED

import featureX from 'es-module-package/features/x.js'
// Carica ./node_modules/es-module-package/src/features/x.js

Esportazioni condizionali

[Cronologia]

VersioneModifiche
v13.7.0, v12.16.0Rimosse le flag per le esportazioni condizionali.
v13.2.0, v12.16.0Aggiunte in: v13.2.0, v12.16.0

Le esportazioni condizionali forniscono un modo per mappare a percorsi diversi a seconda di determinate condizioni. Sono supportate sia per le importazioni CommonJS che per i moduli ES.

Ad esempio, un pacchetto che desidera fornire esportazioni di moduli ES diversi per require() e import può essere scritto come:

json
// package.json
{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
}

Node.js implementa le seguenti condizioni, elencate in ordine dalla più specifica alla meno specifica, poiché le condizioni dovrebbero essere definite:

  • "node-addons" - simile a "node" e corrisponde a qualsiasi ambiente Node.js. Questa condizione può essere utilizzata per fornire un punto di ingresso che utilizza add-on C++ nativi in contrapposizione a un punto di ingresso più universale e che non si basa su add-on nativi. Questa condizione può essere disabilitata tramite il flag --no-addons.
  • "node" - corrisponde a qualsiasi ambiente Node.js. Può essere un file CommonJS o un modulo ES. Nella maggior parte dei casi, non è necessario specificare esplicitamente la piattaforma Node.js.
  • "import" - corrisponde quando il pacchetto viene caricato tramite import o import(), o tramite qualsiasi importazione di primo livello o operazione di risoluzione dal caricatore di moduli ECMAScript. Si applica indipendentemente dal formato del modulo del file di destinazione. Sempre mutualmente esclusiva con "require".
  • "require" - corrisponde quando il pacchetto viene caricato tramite require(). Il file di riferimento dovrebbe essere caricabile con require(), sebbene la condizione corrisponda indipendentemente dal formato del modulo del file di destinazione. I formati previsti includono CommonJS, JSON, add-on nativi e moduli ES. Sempre mutualmente esclusiva con "import".
  • "module-sync" - corrisponde indipendentemente dal fatto che il pacchetto venga caricato tramite import, import() o require(). Il formato previsto è quello dei moduli ES che non contengono await di primo livello nel suo grafo di moduli: in caso contrario, verrà generato ERR_REQUIRE_ASYNC_MODULE quando il modulo viene require()-ed.
  • "default" - il fallback generico che corrisponde sempre. Può essere un file CommonJS o un modulo ES. Questa condizione dovrebbe sempre essere l'ultima.

All'interno dell'oggetto "exports", l'ordine delle chiavi è significativo. Durante la corrispondenza delle condizioni, le voci precedenti hanno una priorità maggiore e hanno la precedenza sulle voci successive. La regola generale è che le condizioni dovrebbero andare dalla più specifica alla meno specifica nell'ordine dell'oggetto.

L'utilizzo delle condizioni "import" e "require" può portare ad alcuni rischi, che sono ulteriormente spiegati nella sezione sui pacchetti duali CommonJS/moduli ES.

La condizione "node-addons" può essere utilizzata per fornire un punto di ingresso che utilizza add-on C++ nativi. Tuttavia, questa condizione può essere disabilitata tramite il flag --no-addons. Quando si utilizza "node-addons", si consiglia di trattare "default" come un miglioramento che fornisce un punto di ingresso più universale, ad es. utilizzando WebAssembly anziché un add-on nativo.

Le esportazioni condizionali possono essere estese anche ai sotto-percorsi di esportazione, ad esempio:

json
{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
}

Definisce un pacchetto in cui require('pkg/feature.js') e import 'pkg/feature.js' potrebbero fornire implementazioni diverse tra Node.js e altri ambienti JS.

Quando si utilizzano rami di ambiente, includere sempre una condizione "default" quando possibile. Fornire una condizione "default" garantisce che qualsiasi ambiente JS sconosciuto sia in grado di utilizzare questa implementazione universale, il che aiuta a evitare che questi ambienti JS debbano fingere di essere ambienti esistenti per supportare pacchetti con esportazioni condizionali. Per questo motivo, l'utilizzo dei rami di condizione "node" e "default" è generalmente preferibile all'utilizzo dei rami di condizione "node" e "browser".

Condizioni annidate

Oltre ai mapping diretti, Node.js supporta anche oggetti di condizione annidati.

Ad esempio, per definire un pacchetto che ha solo punti di ingresso in modalità duale per l'uso in Node.js ma non nel browser:

json
{
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs"
  }
}

Le condizioni continuano a essere corrisposte in ordine come con le condizioni piatte. Se una condizione nidificata non ha alcun mapping, continuerà a controllare le condizioni rimanenti della condizione padre. In questo modo le condizioni nidificate si comportano in modo analogo alle istruzioni if JavaScript nidificate.

Risoluzione delle condizioni utente

Aggiunto in: v14.9.0, v12.19.0

Quando si esegue Node.js, le condizioni utente personalizzate possono essere aggiunte con il flag --conditions:

bash
node --conditions=development index.js

che quindi risolverebbe la condizione "development" nelle importazioni e nelle esportazioni di pacchetti, risolvendo allo stesso tempo le condizioni esistenti "node", "node-addons", "default", "import" e "require" in modo appropriato.

Un numero qualsiasi di condizioni personalizzate può essere impostato con flag ripetuti.

Le condizioni tipiche dovrebbero contenere solo caratteri alfanumerici, utilizzando ":", "-" o "=" come separatori, se necessario. Qualsiasi altra cosa potrebbe incorrere in problemi di compatibilità al di fuori di Node.

In Node, le condizioni hanno pochissime restrizioni, ma in particolare queste includono:

Definizioni delle condizioni della community

Le stringhe di condizione diverse dalle condizioni "import", "require", "node", "module-sync", "node-addons" e "default" implementate nel core di Node.js vengono ignorate di default.

Altre piattaforme potrebbero implementare altre condizioni e le condizioni utente possono essere abilitate in Node.js tramite il flag --conditions / -C.

Poiché le condizioni di pacchetto personalizzate richiedono definizioni chiare per garantire un uso corretto, viene fornito di seguito un elenco di condizioni di pacchetto comuni note e le loro definizioni rigorose per aiutare il coordinamento dell'ecosistema.

  • "types" - può essere utilizzato dai sistemi di tipizzazione per risolvere il file di tipizzazione per l'esportazione indicata. Questa condizione dovrebbe essere sempre inclusa per prima.
  • "browser" - qualsiasi ambiente browser web.
  • "development" - può essere utilizzato per definire un punto di ingresso per l'ambiente di solo sviluppo, ad esempio per fornire un contesto di debug aggiuntivo come messaggi di errore migliori quando si esegue in modalità di sviluppo. Deve essere sempre mutuamente esclusivo con "production".
  • "production" - può essere utilizzato per definire un punto di ingresso per l'ambiente di produzione. Deve essere sempre mutuamente esclusivo con "development".

Per altri runtime, le definizioni delle chiavi di condizione specifiche della piattaforma sono gestite dal WinterCG nella specifica della proposta Runtime Keys.

Nuove definizioni di condizioni possono essere aggiunte a questo elenco creando una pull request alla documentazione di Node.js per questa sezione. I requisiti per l'inserimento di una nuova definizione di condizione qui sono che:

  • La definizione deve essere chiara e non ambigua per tutti gli implementatori.
  • Il caso d'uso per cui la condizione è necessaria deve essere chiaramente giustificato.
  • Dovrebbe esistere un utilizzo sufficiente dell'implementazione esistente.
  • Il nome della condizione non deve essere in conflitto con un'altra definizione di condizione o condizione in ampio uso.
  • L'elenco della definizione di condizione dovrebbe fornire un vantaggio di coordinamento all'ecosistema che altrimenti non sarebbe possibile. Ad esempio, questo non sarebbe necessariamente il caso per le condizioni specifiche dell'azienda o dell'applicazione.
  • La condizione dovrebbe essere tale che un utente di Node.js si aspetterebbe che fosse nella documentazione principale di Node.js. La condizione "types" è un buon esempio: non appartiene realmente alla proposta Runtime Keys ma è adatta qui nella documentazione di Node.js.

Le definizioni di cui sopra possono essere spostate in un registro di condizioni dedicato a tempo debito.

Riferimento automatico a un package usando il suo nome

[Cronologia]

VersioneModifiche
v13.6.0, v12.16.0Rimosso il flag dal riferimento automatico a un package usando il suo nome.
v13.1.0, v12.16.0Aggiunto in: v13.1.0, v12.16.0

All'interno di un package, i valori definiti nel campo "exports" del package.json del package possono essere referenziati tramite il nome del package. Ad esempio, supponendo che il package.json sia:

json
// package.json
{
  "name": "a-package",
  "exports": {
    ".": "./index.mjs",
    "./foo.js": "./foo.js"
  }
}

Quindi, qualsiasi modulo in quel package può referenziare un export all'interno del package stesso:

js
// ./a-module.mjs
import { something } from 'a-package' // Importa "something" da ./index.mjs.

Il riferimento automatico è disponibile solo se package.json ha "exports", e consentirà l'importazione solo di ciò che "exports" (nel package.json) consente. Quindi il codice seguente, dato il package precedente, genererà un errore a runtime:

js
// ./another-module.mjs

// Importa "another" da ./m.mjs. Fallisce perché
// il campo "exports" di "package.json"
// non fornisce un export chiamato "./m.mjs".
import { another } from 'a-package/m.mjs'

Il riferimento automatico è disponibile anche quando si usa require, sia in un modulo ES che in uno CommonJS. Ad esempio, anche questo codice funzionerà:

js
// ./a-module.js
const { something } = require('a-package/foo.js') // Carica da ./foo.js.

Infine, il riferimento automatico funziona anche con i package con scope. Ad esempio, anche questo codice funzionerà:

json
// package.json
{
  "name": "@my/package",
  "exports": "./index.js"
}
js
// ./index.js
module.exports = 42
js
// ./other.js
console.log(require('@my/package'))
bash
$ node other.js
42

Package duali CommonJS/ES module

Vedi il repository degli esempi di package per i dettagli.

Definizioni dei campi package.json di Node.js

Questa sezione descrive i campi usati dal runtime di Node.js. Altri strumenti (come npm) usano campi aggiuntivi che sono ignorati da Node.js e non documentati qui.

I seguenti campi nei file package.json sono usati in Node.js:

  • "name" - Rilevante quando si usano importazioni con nome all'interno di un package. Usato anche dai gestori di package come nome del package.
  • "main" - Il modulo predefinito quando si carica il package, se exports non è specificato e nelle versioni di Node.js precedenti all'introduzione di exports.
  • "packageManager" - Il gestore di package consigliato quando si contribuisce al package. Sfruttato dagli shim di Corepack.
  • "type" - Il tipo di package che determina se caricare i file .js come CommonJS o moduli ES.
  • "exports" - Export del package ed export condizionali. Quando presente, limita quali sottomoduli possono essere caricati dall'interno del package.
  • "imports" - Import del package, per l'uso da parte dei moduli all'interno del package stesso.

"name"

[Cronologia]

VersioneCambiamenti
v13.6.0, v12.16.0Rimosssa l'opzione --experimental-resolve-self.
v13.1.0, v12.16.0Aggiunto in: v13.1.0, v12.16.0
json
{
  "name": "nome-pacchetto"
}

Il campo "name" definisce il nome del tuo pacchetto. La pubblicazione nel registro npm richiede un nome che soddisfi determinati requisiti.

Il campo "name" può essere utilizzato in aggiunta al campo "exports" per fare riferimento a se stesso ad un pacchetto utilizzando il suo nome.

"main"

Aggiunto in: v0.4.0

json
{
  "main": "./index.js"
}

Il campo "main" definisce il punto di ingresso di un pacchetto quando viene importato per nome tramite una ricerca node_modules. Il suo valore è un percorso.

Quando un pacchetto ha un campo "exports", questo avrà la precedenza sul campo "main" quando si importa il pacchetto per nome.

Definisce anche lo script che viene utilizzato quando la directory del pacchetto viene caricata tramite require().

js
// Questo si risolve in ./path/to/directory/index.js.
require('./path/to/directory')

"packageManager"

Aggiunto in: v16.9.0, v14.19.0

[Stabile: 1 - Sperimentale]

Stabile: 1 Stabilità: 1 - Sperimentale

json
{
  "packageManager": "<nome del gestore pacchetti>@<versione>"
}

Il campo "packageManager" definisce quale gestore di pacchetti dovrebbe essere utilizzato quando si lavora al progetto corrente. Può essere impostato su uno qualsiasi dei gestori di pacchetti supportati e garantirà che i tuoi team utilizzino le stesse versioni del gestore di pacchetti senza dover installare nient'altro oltre a Node.js.

Questo campo è attualmente sperimentale e deve essere attivato; controlla la pagina Corepack per i dettagli sulla procedura.

"type"

[Cronologia]

VersioneCambiamenti
v13.2.0, v12.17.0Rimosso il flag --experimental-modules.
v12.0.0Aggiunto in: v12.0.0

Il campo "type" definisce il formato del modulo che Node.js utilizza per tutti i file .js che hanno quel file package.json come il loro genitore più vicino.

I file che terminano con .js vengono caricati come moduli ES quando il file package.json genitore più vicino contiene un campo di livello superiore "type" con valore "module".

Il package.json genitore più vicino è definito come il primo package.json trovato durante la ricerca nella cartella corrente, nella cartella genitore di quella cartella e così via fino a quando non viene raggiunta una cartella node_modules o la radice del volume.

json
// package.json
{
  "type": "module"
}
bash
# Nella stessa cartella del precedente package.json {#in-same-folder-as-preceding-packagejson}
node my-app.js # Viene eseguito come modulo ES

Se il package.json genitore più vicino non ha un campo "type", o contiene "type": "commonjs", i file .js vengono trattati come CommonJS. Se viene raggiunta la radice del volume e non viene trovato alcun package.json, i file .js vengono trattati come CommonJS.

Le istruzioni import dei file .js vengono trattate come moduli ES se il package.json genitore più vicino contiene "type": "module".

js
// my-app.js, parte dello stesso esempio di sopra
import './startup.js' // Caricato come modulo ES a causa di package.json

Indipendentemente dal valore del campo "type", i file .mjs vengono sempre trattati come moduli ES e i file .cjs vengono sempre trattati come CommonJS.

"exports"

[Cronologia]

VersioneCambiamenti
v14.13.0, v12.20.0Aggiunto il supporto per i pattern "exports".
v13.7.0, v12.17.0Rimosso il flag degli export condizionali.
v13.7.0, v12.16.0Implementato l'ordinamento logico degli export condizionali.
v13.7.0, v12.16.0Rimosso l'opzione --experimental-conditional-exports. Nella versione 12.16.0, gli export condizionali sono ancora dietro --experimental-modules.
v13.2.0, v12.16.0Implementati gli export condizionali.
v12.7.0Aggiunto in: v12.7.0
json
{
  "exports": "./index.js"
}

Il campo "exports" consente di definire i punti di ingresso di un pacchetto quando importato per nome caricato tramite una ricerca node_modules o un autoriferimento al suo nome. È supportato in Node.js 12+ come alternativa a "main" che può supportare la definizione di esportazioni di sottopercorsi e esportazioni condizionali incapsulando al contempo i moduli interni non esportati.

Le Esportazioni Condizionali possono essere utilizzate anche all'interno di "exports" per definire diversi punti di ingresso del pacchetto per ambiente, incluso se il pacchetto viene referenziato tramite require o tramite import.

Tutti i percorsi definiti in "exports" devono essere URL di file relativi che iniziano con ./.

"imports"

Aggiunto in: v14.6.0, v12.19.0

json
// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
}

Le voci nel campo imports devono essere stringhe che iniziano con #.

Le importazioni di pacchetto consentono la mappatura a pacchetti esterni.

Questo campo definisce le importazioni di sottopercorso per il pacchetto corrente.