Menu

Namespace in TypeScript: cos'è e quando usarlo

Un namespace di TypeScript raggruppa valori e tipi sotto un solo nome e viene compilato in un oggetto semplice. Impara la sintassi, come i namespace si fondono tra loro e con funzioni e classi, perché gli ES module li hanno sostituiti e dove li incontri ancora: file di dichiarazione e global augmentation.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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.

NamespaceModulo
Unitàun blocco con nome in un fileil file stesso
Scopeglobale, se non è dentro un modulosempre il proprio
Dipendenzeimplicite, per ordine di caricamentoimport espliciti
Outputun oggetto costruito da una funzioneimport/export o require
Rimozione del codice inutilizzatoi bundler tengono ogni membroi bundler possono scartare gli export inutilizzati
Funziona con il type stripping di Nodesolo se contiene solo tipisì

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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA