Un namespace è un blocco con nome che raggruppa funzioni, costanti e tipi sotto un solo nome. I membri marcati export sono raggiungibili come Name.member; gli altri restano privati del blocco. A runtime un namespace è un oggetto semplice.
In cosa viene compilato un namespace
I namespace sono una delle poche funzionalità di TypeScript che generano codice. Il blocco qui sopra diventa una funzione che riempie un oggetto:
var Geometry;
(function (Geometry) {
const TAU = Math.PI * 2;
function circumference(radius) {
return TAU * radius;
}
Geometry.circumference = circumference;
function area(radius) {
return Math.PI * radius ** 2;
}
Geometry.area = area;
})(Geometry || (Geometry = {}));
È lo schema che il codice JavaScript usava prima che esistessero i moduli, per evitare di mettere ogni nome nello scope globale. TAU è una variabile locale della funzione, ed è per questo che il resto del codice non la vede. Provare a leggerla è un errore di compilazione:
Il compilatore segnala index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. Aggiungi export davanti a const rate ed entrambe le righe vengono eseguite.
Annidamento, fusione e alias
I namespace si possono annidare, e due blocchi con lo stesso nome si fondono in uno solo. È il Geometry || (Geometry = {}) nell'output a renderlo possibile: il secondo blocco aggiunge all'oggetto esistente. import X = A.B crea un alias breve.
namespace A.B.C { } è una scorciatoia per tre blocchi annidati. La vecchia scrittura module Shop { } significa la stessa cosa, ma ora viene rifiutata con l'errore TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.
Fusione con funzioni e classi
Un namespace può condividere il nome con una funzione, una classe o un enum e aggiungervi dei membri. È ancora il modo più pulito per descrivere una funzione che porta con sé anche delle proprietà, o una classe con degli helper attaccati.
Per una classe, un metodo static fa lo stesso lavoro ed è JavaScript puro. Per una funzione, puoi anche fare a meno del namespace e assegnare format.prefix = "$" subito dopo la dichiarazione; TypeScript tiene traccia delle proprietà assegnate in quel modo.
Namespace vs moduli
Prima degli ES module, un grande programma TypeScript era fatto di molti file script che condividevano namespace globali, cuciti insieme con /// <reference path="..." /> e compilati in un solo file con outFile. I moduli hanno sostituito tutto questo: ogni file ha il suo scope, le dipendenze sono import espliciti e i bundler possono scartare gli export inutilizzati. TypeScript 7 ha rimosso outFile (errore TS5102), quindi la configurazione con namespace su più file non è più un'opzione di build.
| Namespace | Modulo | |
|---|---|---|
| Unità | un blocco con nome in un file | il file stesso |
| Scope | globale, se non è dentro un modulo | sempre il proprio |
| Dipendenze | implicite, per ordine di caricamento | import espliciti |
| Output | un oggetto costruito da una funzione | import/export o require |
| Rimozione del codice inutilizzato | i bundler tengono ogni membro | i bundler possono scartare gli export inutilizzati |
| Funziona con il type stripping di Node | solo se contiene solo tipi | sì |
Dentro un modulo, avvolgere tutto in un namespace aggiunge un secondo livello di nomi senza alcun vantaggio: chi importa dovrebbe scrivere Utils.Utils.format. Esporta direttamente le funzioni e lascia a chi importa la scelta di import * as Utils from "./utils.js" se vuole un prefisso.
Dove incontri ancora i namespace
I file di dichiarazione li usano per descrivere librerie che espongono un unico oggetto globale e per raggruppare tipi:
// jquery-like.d.ts: a global function that also has properties
declare function $(selector: string): unknown;
declare namespace $ {
const version: string;
function ajax(url: string): Promise<unknown>;
}
I pacchetti di tipi li usano per esporre tipi che puoi estendere. @types/node dichiara namespace NodeJS, e per aggiungere qualcosa alla sua interfaccia ProcessEnv da un qualsiasi modulo serve declare global:
// env.d.ts
export {};
declare global {
namespace NodeJS {
interface ProcessEnv {
API_URL: string; // process.env.API_URL is now string, not string | undefined
}
}
}
Anche le interfacce dentro namespace fusi si fondono, ed è questo che fa funzionare l'augmentation.
Namespace e type stripping
Node 24 esegue i file .ts cancellando la sintassi dei tipi. Un namespace con valori non si può cancellare, deve essere compilato nell'oggetto mostrato sopra, quindi node app.ts si ferma con:
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode
Due tipi di namespace vanno bene perché spariscono del tutto: un declare namespace, e un namespace i cui membri sono tutti tipi o interfacce. L'opzione del compilatore erasableSyntaxOnly: true segnala gli altri in fase di compilazione con l'errore TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., ed è così che i progetti che girano con il type stripping li tengono fuori. node --experimental-transform-types invece compila i namespace, con un avviso sperimentale.
Domande frequenti
Meglio namespace o moduli in TypeScript?
Usa i moduli (import ed export) per il codice nuovo. Ogni file ha già il suo scope, bundler e Node capiscono i moduli, e gli export inutilizzati si possono rimuovere. I namespace restano utili nei file di dichiarazione, per la global augmentation e per attaccare tipi o helper a una funzione o a una classe con lo stesso nome.
In cosa viene compilato un namespace TypeScript?
In un oggetto riempito da una funzione invocata subito: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. I membri esportati diventano proprietà di quell'oggetto; quelli senza export restano locali alla funzione.
Che differenza c'è tra namespace e module in TypeScript?
Un modulo è un file con un import o un export al livello più alto. Un namespace è un blocco con nome dentro un file. Il vecchio TypeScript chiamava i namespace "internal modules" e accettava module Foo {}; quella scrittura ora è l'errore TS1540, e si accetta solo namespace Foo {}.
Node può eseguire file TypeScript che usano namespace?
Non con il type stripping predefinito. Un namespace che contiene valori produce ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, perché lo stripping si limita a cancellare i tipi e un namespace richiede codice generato. I namespace che contengono solo tipi, e declare namespace, vengono cancellati e funzionano senza problemi.