Come utilizzare gli spazi dei nomi con i moduli esterni TypeScript?

Ho del codice:

baseTypes.ts

export module Living.Things { export class Animal { move() { /* ... */ } } export class Plant { photosynthesize() { /* ... */ } } } 

dog.ts

 import b = require('./baseTypes'); export module Living.Things { // Error, can't find name 'Animal', ?? export class Dog extends Animal { woof() { } } } 

tree.ts

 // Error, can't use the same name twice, ?? import b = require('./baseTypes'); import b = require('./dogs'); module Living.Things { // Why do I have to write b.Living.Things.Plant instead of b.Plant?? class Tree extends b.Living.Things.Plant { } } 

Questo è tutto molto confuso. Voglio avere un gruppo di moduli esterni che contribuiscono tutti i tipi allo stesso spazio dei nomi, Living.Things . Sembra che questo non funzioni affatto – non riesco a vedere Animal in dogs.ts Devo scrivere il nome completo dello spazio dei nomi b.Living.Things.Plant in tree.ts Non funziona per combinare più oggetti nello stesso spazio dei nomi su file. Come faccio a fare questo?

Candy Cup Analogy

Versione 1: una tazza per ogni caramella

Diciamo che hai scritto un codice come questo:

Mod1.ts

 export namespace A { export class Twix { ... } } 

Mod2.ts

 export namespace A { export class PeanutButterCup { ... } } 

Mod3.ts

 export namespace A { export class KitKat { ... } } 

Hai creato questa configurazione: inserisci la descrizione dell'immagine qui

Ogni modulo (foglio di carta) ottiene il proprio calice denominato A Questo è inutile – in realtà non stai organizzando le tue caramelle, stai solo aggiungendo un ulteriore passaggio (portandolo fuori dalla tazza) tra te e le delizie.


Versione 2: una tazza nell’ambito globale

Se non stai usando i moduli, potresti scrivere un codice come questo (notare la mancanza di dichiarazioni di export ):

global1.ts

 namespace A { export class Twix { ... } } 

global2.ts

 namespace A { export class PeanutButterCup { ... } } 

global3.ts

 namespace A { export class KitKat { ... } } 

Questo codice crea uno spazio dei nomi unito A nell’ambito globale:

inserisci la descrizione dell'immagine qui

Questa configurazione è utile, ma non si applica ai moduli (poiché i moduli non inquinano l’ambito globale).


Versione 3: andare senza coppe

Tornando all’esempio originale, le coppe A , A e A non ti stanno facendo alcun favore. Invece, potresti scrivere il codice come:

Mod1.ts

 export class Twix { ... } 

Mod2.ts

 export class PeanutButterCup { ... } 

Mod3.ts

 export class KitKat { ... } 

per creare un’immagine simile a questa:

inserisci la descrizione dell'immagine qui

Molto meglio!

Ora, se stai ancora pensando a quanto vuoi veramente usare lo spazio dei nomi con i tuoi moduli, continua a leggere …


Questi non sono i concetti che stai cercando

Dobbiamo risalire alle origini del motivo per cui gli spazi dei nomi esistono in primo luogo ed esaminare se tali motivi hanno un senso per i moduli esterni.

Organizzazione : i Namespace sono utili per raggruppare oggetti e tipi correlati alla logica. Ad esempio, in C #, troverai tutti i tipi di raccolta in System.Collections . Organizzando i nostri tipi in namespace gerarchici, forniamo una buona esperienza di “scoperta” per gli utenti di questi tipi.

Conflitti di nome : gli spazi dei nomi sono importanti per evitare conflitti di denominazione. Ad esempio, potresti avere My.Application.Customer.AddForm e My.Application.Order.AddForm – due tipi con lo stesso nome, ma uno spazio dei nomi diverso. In una lingua in cui tutti gli identificatori esistono nello stesso ambito di root e tutti gli assembly caricano tutti i tipi, è fondamentale avere tutto in uno spazio dei nomi.

Queste ragioni hanno senso nei moduli esterni?

Organizzazione : i moduli esterni sono già presenti in un file system, necessariamente. Dobbiamo risolverli per path e nomefile, quindi c’è uno schema logico di organizzazione da usare. Possiamo avere un /collections/generic/ cartella con un modulo di list in esso.

Conflitti di nome : questo non si applica affatto nei moduli esterni. All’interno di un modulo, non esiste una ragione plausibile per avere due oggetti con lo stesso nome. Dal lato del consumo, il consumatore di un dato modulo deve scegliere il nome che useranno per fare riferimento al modulo, quindi i conflitti di denominazione accidentale sono impossibili.


Anche se non credi che tali ragioni siano adeguatamente trattate dal modo in cui i moduli funzionano, la “soluzione” di cercare di usare spazi dei nomi in moduli esterni non funziona nemmeno.

Scatole in scatole in scatole

Una storia:

Il tuo amico Bob ti chiama. “Ho un ottimo nuovo schema organizzativo a casa mia”, dice, “dai un’occhiata!”. Niente, andiamo a vedere cosa ha scoperto Bob.

Inizi nella cucina e apri la dispensa. Ci sono 60 scatole diverse, ciascuna etichettata “Pantry”. Scegli una scatola a caso e aprila. All’interno c’è una singola scatola con l’etichetta “Grains”. Apri la casella “Grani” e trovi una singola casella con l’etichetta “Pasta”. Apri la casella “Pasta” e trovi una singola casella con l’etichetta “Penne”. Apri questa scatola e trovi, come ti aspetti, un sacchetto di pasta di penne.

Leggermente confuso, raccogli una scatola adiacente, anch’essa etichettata come “Pantry”. Dentro c’è una scatola singola, ancora etichettata come “chicchi”. Apri la casella “Grani” e, ancora, trovi una singola casella con l’etichetta “Pasta”. Apri la casella “Pasta” e trovi una scatola singola, questa è etichettata come “Rigatoni”. Apri questa scatola e trovi … un sacchetto di pasta rigatoni.

“È ottimo!” dice Bob. “Tutto è in un namespace!”.

“Ma Bob …” rispondi. “Il tuo schema organizzativo è inutile, devi aprire un sacco di scatole per arrivare a qualsiasi cosa, e in realtà non è più conveniente trovare qualcosa che se avessi messo tutto in una scatola anziché in tre . la dispensa è già in ordine, scaffale per scaffale, non servono affatto le scatole, perché non mettere la pasta sul ripiano e prenderla quando ne hai bisogno? ”

“Non capisci – devo assicurarmi che nessun altro metta qualcosa che non appartiene al namespace” Pantry “e ho organizzato in modo sicuro tutta la mia pasta nel namespace Pantry.Grains.Pasta così io può facilmente trovarlo ”

Bob è un uomo molto confuso.

I moduli sono la loro propria scatola

Probabilmente è accaduto qualcosa di simile nella vita reale: ordini alcune cose su Amazon, e ogni object si presenta nella sua scatola, con una scatola più piccola all’interno, con l’object avvolto nella sua confezione. Anche se le scatole interne sono simili, le spedizioni non sono utilmente “combinate”.

Andando con l’analogia della scatola, l’osservazione chiave è che i moduli esterni sono la propria scatola . Potrebbe essere un elemento molto complesso con molte funzionalità, ma ogni dato modulo esterno è la sua casella.


Guida per moduli esterni

Ora che abbiamo capito che non abbiamo bisogno di usare “namespace”, come dovremmo organizzare i nostri moduli? Seguono alcuni principi guida ed esempi.

Esportare il più vicino ansible al livello più alto

  • Se esporti solo una singola class o funzione, utilizza l’ export default :

MyClass.ts

 export default class SomeType { constructor() { ... } } 

MyFunc.ts

 function getThing() { return 'thing'; } export default getThing; 

Consumo

 import t from './MyClass'; import f from './MyFunc'; var x = new t(); console.log(f()); 

Questo è ottimale per i consumatori. Possono nominare il tuo tipo come preferiscono ( t in questo caso) e non devono fare alcuna punteggiatura estranea per trovare i tuoi oggetti.

  • Se esporti più oggetti, mettili tutti in alto livello:

MyThings.ts

 export class SomeType { ... } export function someFunc() { ... } 

Consumo

 import * as m from './MyThings'; var x = new m.SomeType(); var y = m.someFunc(); 
  • Se stai esportando un gran numero di cose, solo allora dovresti usare la parola chiave module / namespace :

MyLargeModule.ts

 export namespace Animals { export class Dog { ... } export class Cat { ... } } export namespace Plants { export class Tree { ... } } 

Consumo

 import { Animals, Plants} from './MyLargeModule'; var x = new Animals.Dog(); 

Bandiere rosse

Tutte le seguenti sono bandiere rosse per la strutturazione dei moduli. Verifica che non stia cercando di creare un namespace per i moduli esterni se uno di questi si applica ai tuoi file:

  • Un file la cui unica dichiarazione di primo livello è il export module Foo { ... } (rimuovi Foo e sposta tutto su un livello)
  • Un file che ha una singola export class export function o una export function che non è export default
  • File multipli con lo stesso export module Foo { al primo livello (non pensare che questi si combinino in un unico Foo !)

Non c’è niente di sbagliato nella risposta di Ryan, ma per le persone che sono venute qui cercando come mantenere una struttura di una class per file pur utilizzando correttamente gli spazi dei nomi ES6, fare riferimento a questa utile risorsa di Microsoft.

Una cosa che non mi è chiara dopo aver letto il documento è: come importare l’intero modulo (unito) con una singola import .

Modifica Indietro indietro per aggiornare questa risposta. Alcuni approcci al namespacing emergono in TS.

Tutte le classi di moduli in un unico file.

 export namespace Shapes { export class Triangle {} export class Square {} } 

Importa i file nello spazio dei nomi e riassegna

 import { Triangle as _Triangle } from './triangle'; import { Square as _Square } from './square'; export namespace Shapes { export const Triangle = _Triangle; export const Square = _Square; } 

botti

 // ./shapes/index.ts export { Triangle } from './triangle'; export { Square } from './square'; // in importing file: import * as Shapes from './shapes/index.ts'; // by node module convention, you can ignore '/index.ts': import * as Shapes from './shapes'; let myTriangle = new Shapes.Triangle(); 

Un’ultima considerazione. Si potrebbe namespace ogni file

 // triangle.ts export namespace Shapes { export class Triangle {} } // square.ts export namespace Shapes { export class Square {} } 

Ma siccome si importano due classi dallo stesso spazio dei nomi, TS si lamenterà di avere un identificatore duplicato. L’unica soluzione come questa volta è quella di creare lo pseudonimo dello spazio dei nomi.

 import { Shapes } from './square'; import { Shapes as _Shapes } from './triangle'; // ugh let myTriangle = new _Shapes.Shapes.Triangle(); 

Questo aliasing è assolutamente aberrante, quindi non farlo. Stai meglio con un approccio sopra. Personalmente, preferisco il “barile”.

Prova ad organizzare per cartella:

baseTypes.ts

 export class Animal { move() { /* ... */ } } export class Plant { photosynthesize() { /* ... */ } } 

dog.ts

 import b = require('./baseTypes'); export class Dog extends b.Animal { woof() { } } 

tree.ts

 import b = require('./baseTypes'); class Tree extends b.Plant { } 

LivingThings.ts

 import dog = require('./dog') import tree = require('./tree') export = { dog: dog, tree: tree } 

main.ts

 import LivingThings = require('./LivingThings'); console.log(LivingThings.Tree) console.log(LivingThings.Dog) 

L’idea è che il tuo modulo non dovrebbe preoccuparsi / sapere che stanno partecipando a uno spazio dei nomi, ma questo espone la tua API al consumatore in un modo compatto e ragionevole che è agnostico a quale tipo di sistema di moduli stai usando per il progetto.

Piccolo miglioramento della risposta Albinofrenchy:

base.ts

 export class Animal { move() { /* ... */ } } export class Plant { photosynthesize() { /* ... */ } } 

dog.ts

 import * as b from './base'; export class Dog extends b.Animal { woof() { } } 

things.ts

 import { Dog } from './dog' namespace things { export const dog = Dog; } export = things; 

main.ts

 import * as things from './things'; console.log(things.dog); 

OP Sono con te uomo. anche in questo caso, non c’è nulla di sbagliato in questa risposta con oltre 300 voti, ma la mia opinione è:

  1. cosa c’è di sbagliato nel mettere le classi nei loro file caldi e accoglienti individualmente? Voglio dire, questo renderà le cose molto più belle, vero? (o qualcuno come un file di 1000 righe per tutti i modelli)

  2. quindi, se il primo verrà raggiunto, dobbiamo importare import import … importare solo in ciascuno dei file di modello come man, srsly, un file di modello, un file .d.ts, perché ce ne sono così tanti * c’è dentro? dovrebbe essere semplice, in ordine, e basta. Perché ho bisogno delle importazioni lì? perché? C # ha gli spazi dei nomi per un motivo.

  3. E a quel punto, stai letteralmente usando “filenames.ts” come identificatori. Come identificatori … Vieni 2017 e lo facciamo ancora? Ima torna su Marte e dormi per altri 1000 anni.

Quindi, purtroppo, la mia risposta è: nop, non è ansible rendere funzionale la funzione “namespace” se non si utilizzano tutte quelle importazioni o si utilizzano quei nomi di file come identificatori (che penso sia davvero sciocco). Un’altra opzione è: mettere tutte queste dipendenze in una scatola chiamata filenameasidentifier.ts e usare

 export namespace(or module) boxInBox {} . 

avvolgili in modo che non provino ad accedere ad altre classi con lo stesso nome quando stanno semplicemente cercando di ottenere un riferimento dal sit di class proprio sopra di loro.

Molte delle domande / commenti che ho visto intorno a questo argomento mi sembrano come se la persona stia usando Namespace dove intendono “alias del modulo”. Come Ryan Cavanaugh ha menzionato in uno dei suoi commenti, è ansible che un modulo ‘Wrapper’ riesportasse diversi moduli.

Se vuoi davvero importarlo tutto dallo stesso nome / alias del modulo, combina un modulo wrapper con una mapping dei percorsi nel tuo tsconfig.json .

Esempio:

./path/to/CompanyName.Products/Foo.ts

 export class Foo { ... } 

./path/to/CompanyName.Products/Bar.ts

 export class Bar { ... } 

./path/to/CompanyName.Products/index.ts

 export { Foo } from './Foo'; export { Bar } from './Bar'; 

tsconfig.json

 { "compilerOptions": { ... paths: { ... "CompanyName.Products": ["./path/to/CompanyName.Products/index"], ... } ... } ... } 

main.ts

 import { Foo, Bar } from 'CompanyName.Products' 

Nota : la risoluzione del modulo nei file .js di output dovrà essere gestita in qualche modo, ad esempio con questo https://github.com/tleunen/babel-plugin-module-resolver

Esempio .babelrc per gestire la risoluzione .babelrc :

 { "plugins": [ [ "module-resolver", { "cwd": "babelrc", "alias": { "CompanyName.Products": "./path/to/typescript/build/output/CompanyName.Products/index.js" } }], ... other plugins ... ] } 

dog.ts

 import b = require('./baseTypes'); export module Living.Things { // Error, can't find name 'Animal', ?? // Solved: can find, if properly referenced; exporting modules is useless, anyhow export class Dog extends b.Living.Things.Animal { public woof(): void { return; } } } 

tree.ts

 // Error, can't use the same name twice, ?? // Solved: cannot declare let or const variable twice in same scope either: just use a different name import b = require('./baseTypes'); import d = require('./dog'); module Living.Things { // Why do I have to write b.Living.Things.Plant instead of b.Plant?? class Tree extends b.Living.Things.Plant { } } 

Necromancing.
Se ho capito bene, mi stai chiedendo come avere tutte le tue classi in un file separato pur preservando un singolo spazio dei nomi per tutti loro.

Dal momento che nessuno sembra avere una buona soluzione – ecco un’idea per una soluzione semplice che non coinvolge nemmeno il typescript: quella soluzione si chiama Gulp .

Basta mettere tutte le classi che devono essere in uno spazio dei nomi nella stessa cartella (utile comunque per l’organizzazione del codice). Quindi aggiungi un task gulp che unisce tutti i file in questa directory in un file (gulp-concat). Quindi, aggiungi uno spazio dei nomi con lo stesso nome della directory principale, quindi aggiungi i file concatenati, quindi aggiungi una parentesi di chiusura e salva in un unico file.
Fatto.
Quindi aggiungi un task-gulp che controlla le modifiche (e le aggiunte / le rimozioni) nella stessa directory. In caso di modifica / aggiunta, triggers la funzione concat.

Ora hai tutte le classi in un unico file e un file che contiene tutte le classi in uno spazio dei nomi.
Codice di esempio – sulla falsariga di:

 gulp.task("js:min:all", function () { return gulp.src(["./wwwroot/app/**/*.js", "!" + "./wwwroot/app/**/*.min.js" , "./wwwroot/GeneratedScripts/**/*.js", "!" + "./wwwroot/GeneratedScripts/**/*.min.js"], { base: "." }) .pipe(concat("./wwwroot/js/myscripts.min.js")) .pipe(uglify()) .pipe(gulp.dest(".")); }); gulp.task('watch:js', function () { gulp.watch('js/**/*.js', ['js:min:all']); }); 

C’è un modulo append-prepend di gulp qui: https://www.npmjs.com/package/gulp-append-prepend

 var gap = require('gulp-append-prepend'); gulp.task('myawesometask', function(){ gulp.src('index.html') .pipe(gap.prependFile('header.html')) .pipe(gap.prependText('')) .pipe(gap.appendText('')) .pipe(gap.appendFile('footer.html')) .pipe(gulp.dest('www/')); }); 

Infine, imposta l’osservatore all’avvio del caricamento della soluzione e il gioco è fatto.