Como uso namespaces com módulos externos do TypeScript?

Eu tenho algum código:

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 { } } 

Isto é tudo muito confuso. Eu quero ter um monte de módulos externos todos os tipos de contribuição para o mesmo namespace, Living.Things . Parece que isso não funciona de todo – não consigo ver Animal em dogs.ts Eu tenho que escrever o nome do namespace completo b.Living.Things.Plant em tree.ts Não funciona para combinar vários objects no mesmo namespace em todo o arquivo. Como eu faço isso?

Analogia da xícara de doces

Versão 1: Um copo para cada doce

Vamos dizer que você escreveu algum código como este:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Você criou esta configuração: insira a descrição da imagem aqui

Cada módulo (folha de papel) recebe seu próprio copo chamado A Isso é inútil – você não está realmente organizando seu doce aqui, você está apenas adicionando um passo adicional (tirando-o da xícara) entre você e as guloseimas.


Versão 2: uma xícara no escopo global

Se você não estava usando módulos, você pode escrever um código como este (observe a falta de declarações de export ):

global1.ts

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

global2.ts

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

global3.ts

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

Esse código cria um espaço para nome mesclado A no escopo global:

insira a descrição da imagem aqui

Esta configuração é útil, mas não se aplica no caso de módulos (porque os módulos não poluem o escopo global).


Versão 3: Indo sem manga

Voltando ao exemplo original, as xícaras A , A e A não estão te favorecendo. Em vez disso, você poderia escrever o código como:

Mod1.ts

 export class Twix { ... } 

Mod2.ts

 export class PeanutButterCup { ... } 

Mod3.ts

 export class KitKat { ... } 

para criar uma imagem parecida com esta:

insira a descrição da imagem aqui

Muito melhor!

Agora, se você ainda está pensando em quanto você realmente quer usar o namespace com seus módulos, continue lendo …


Estes não são os conceitos que você está procurando

Precisamos voltar às origens de porque os namespaces existem em primeiro lugar e examinar se essas razões fazem sentido para os módulos externos.

Organização : Os namespaces são úteis para agrupar objects e tipos relacionados a logicamente. Por exemplo, em C #, você encontrará todos os tipos de coleção em System.Collections . Ao organizar nossos tipos em namespaces hierárquicos, fornecemos uma boa experiência de “descoberta” para usuários desses tipos.

Conflitos de nomes: Os espaços de nomes são importantes para evitar colisões de nomenclatura. Por exemplo, você pode ter My.Application.Customer.AddForm e My.Application.Order.AddForm – dois tipos com o mesmo nome, mas um namespace diferente. Em uma linguagem em que todos os identificadores existem no mesmo escopo raiz e todos os assemblies carregam todos os tipos, é essencial ter tudo em um namespace.

Essas razões fazem sentido em módulos externos?

Organização : Módulos externos já estão presentes em um sistema de arquivos, necessariamente. Temos que resolvê-los por caminho e nome de arquivo, portanto, há um esquema de organização lógica para usarmos. Podemos ter uma pasta /collections/generic/ com um módulo de list .

Conflitos de nome : isso não se aplica em todos os módulos externos. Dentro de um módulo, não há razão plausível para ter dois objects com o mesmo nome. Do lado do consumo, o consumidor de qualquer módulo determinado escolhe o nome que usará para se referir ao módulo, portanto, conflitos de nomenclatura acidentais são impossíveis.


Mesmo que você não acredite que essas razões sejam adequadamente tratadas pelo modo como os módulos funcionam, a “solução” de tentar usar namespaces em módulos externos nem funciona.

Caixas em checkboxs em checkboxs

Uma história:

Seu amigo Bob liga para você. “Eu tenho um ótimo esquema de organização na minha casa”, ele diz, “venha conferir!”. Legal, vamos ver o que o Bob inventou.

Você começa na cozinha e abre a despensa. Existem 60 checkboxs diferentes, cada uma etiquetada como “Despensa”. Você escolhe uma checkbox aleatoriamente e a abre. Dentro há uma única checkbox chamada “Grãos”. Você abre a checkbox “Grãos” e encontra uma única checkbox com o nome “Pasta”. Você abre a checkbox “Pasta” e encontra uma única checkbox chamada “Penne”. Você abre esta checkbox e encontra, como você esperava, uma sacola de macarrão penne.

Um pouco confuso, você pega uma checkbox adjacente, também chamada “Pantry”. Dentro há uma única checkbox, novamente rotulada “Grãos”. Você abre a checkbox “Grãos” e, novamente, encontra uma única checkbox com o nome “Pasta”. Você abre a checkbox “Pasta” e encontra uma única checkbox, esta é rotulada “Rigatoni”. Você abre esta checkbox e encontra … um saco de macarrão rigatoni.

“É ótimo!” diz Bob. “Tudo está em um namespace!”

“Mas Bob …” você responde. “Seu esquema de organização é inútil. Você tem que abrir um monte de checkboxs para chegar a qualquer coisa, e não é realmente mais conveniente encontrar nada do que se você tivesse colocado tudo em uma checkbox em vez de três . Na verdade, desde o seu a despensa já está ordenada prateleira a prateleira, você não precisa das checkboxs. Por que não apenas colocar a massa na prateleira e pegá-la quando precisar? ”

“Você não entende – eu preciso ter certeza de que ninguém mais coloque algo que não pertença ao namespace ‘Pantry’. E eu organizei com segurança todo o meu macarrão no namespace Pantry.Grains.Pasta então eu pode facilmente encontrá-lo ”

Bob é um homem muito confuso.

Módulos são sua própria checkbox

Você provavelmente teve algo parecido acontecer na vida real: você pede algumas coisas na Amazon, e cada item aparece em sua própria checkbox, com uma checkbox menor dentro, com o item embrulhado em sua própria embalagem. Mesmo que as checkboxs interiores sejam semelhantes, as remessas não são “combinadas”.

Indo com a analogia da checkbox, a principal observação é que os módulos externos são sua própria checkbox . Pode ser um item muito complexo com muita funcionalidade, mas qualquer módulo externo é sua própria checkbox.


Orientação para módulos externos

Agora que descobrimos que não precisamos usar namespaces, como devemos organizar nossos módulos? Seguem-se alguns princípios orientadores e exemplos.

Exportar o mais próximo possível do nível mais alto

  • Se você estiver exportando apenas uma class ou function, use 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()); 

Isso é ótimo para os consumidores. Eles podem nomear o seu tipo o que eles quiserem (neste caso) e não precisam fazer nenhum ponto estranho para encontrar seus objects.

  • Se você estiver exportando vários objects, coloque-os todos no nível superior:

MyThings.ts

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

Consumo

 import * as m from './MyThings'; var x = new m.SomeType(); var y = m.someFunc(); 
  • Se você está exportando um grande número de coisas, só então você deve usar a palavra-chave 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(); 

Bandeiras vermelhas

Todos os itens a seguir são bandeiras vermelhas para a estruturação do módulo. Verifique se você não está tentando namespace dos seus módulos externos se algum deles se aplicar aos seus arquivos:

  • Um arquivo cuja única declaração de nível superior é export module Foo { ... } (remova Foo e mova tudo ‘up’ um nível)
  • Um arquivo que possui uma única export class export function ou export function que não é export default
  • Vários arquivos que possuem o mesmo export module Foo { no nível superior (não pense que eles serão combinados em um Foo !)

Nada de errado com a resposta de Ryan, mas para as pessoas que vieram aqui procurando como manter uma estrutura de uma class por arquivo enquanto ainda usam namespaces ES6 corretamente, consulte este recurso útil da Microsoft.

Uma coisa que não está clara para mim depois de ler o documento é: como importar o módulo inteiro (mesclado) com uma única import .

Editar Voltando a circular para atualizar esta resposta. Algumas abordagens para o namespace surgem no TS.

Todas as classs do módulo em um arquivo.

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

Importar arquivos para o espaço de nomes e reatribuir

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

Barris

 // ./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(); 

Uma consideração final. Você poderia namespace cada arquivo

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

Mas como se importa duas classs do mesmo namespace, o TS irá reclamar que existe um identificador duplicado. A única solução desta vez é, então, aliasar o namespace.

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

Esse aliasing é absolutamente repugnante, então não faça isso. Você está melhor com uma abordagem acima. Pessoalmente, eu prefiro o ‘barril’.

Tente organizar por pasta:

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) 

A idéia é que o próprio módulo não se importe / saiba que eles estão participando de um namespace, mas isso expõe sua API ao consumidor de uma maneira compacta e sensata, que é agnóstica para qual tipo de sistema de módulo você está usando para o projeto.

Pequeno impato de resposta 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() { } } 

coisas.

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

main.ts

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

OP estou com você homem. novamente também, não há nada de errado com essa resposta com mais de 300 votos, mas minha opinião é:

  1. o que há de errado em colocar as aulas em seus próprios arquivos aconchegantes individualmente? Quero dizer, isso vai fazer as coisas parecerem muito melhores, certo? (ou alguém apenas como um arquivo de 1000 linhas para todos os modelos)

  2. então, se o primeiro for alcançado, nós temos que importar import import … import apenas em cada um dos arquivos de modelo como man, srsly, um arquivo de modelo, um arquivo .d.ts, por que existem tantos * está lá? deve ser simples, arrumado e pronto. Por que eu preciso de importações lá? porque? C # ganhou namespaces por um motivo.

  3. E então, você está literalmente usando “filenames.ts” como identificadores. Como identificadores … Vamos 2017 agora e ainda fazemos isso? Ima voltar a Marte e dormir por mais 1000 anos.

Então, infelizmente, minha resposta é: nop, você não pode tornar o “espaço de nomes” funcional se você não usar todas as importações ou usar esses nomes de arquivos como identificadores (o que eu acho realmente bobo). Outra opção é: colocar todas essas dependencies em uma checkbox chamada filenameasidentifier.ts e usar

 export namespace(or module) boxInBox {} . 

envolva-os para que eles não tentem acessar outras classs com o mesmo nome quando estiverem simplesmente tentando fazer com que uma referência da turma fique em cima deles.

Várias das perguntas / comentários que tenho visto em torno deste assunto soam para mim como se a pessoa estivesse usando Namespace onde eles significam ‘alias de módulo’. Como Ryan Cavanaugh mencionou em um de seus comentários, você pode ter um módulo ‘Wrapper’ reexportando vários módulos.

Se você realmente quiser importar tudo a partir do mesmo nome de módulo / alias, combine um módulo de wrapper com um mapeamento de caminhos em seu tsconfig.json .

Exemplo:

./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 : A resolução do módulo nos arquivos .js de saída precisará ser tratada de alguma forma, como com este https://github.com/tleunen/babel-plugin-module-resolver

Exemplo .babelrc para lidar com a resolução de alias:

 { "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 bem entendi, você está perguntando como ter todas as suas aulas em um arquivo separado, preservando um único namespace para todas elas.

Já que ninguém parece ter uma boa solução – aqui está uma idéia para uma solução simples que não envolve nem mesmo o typescript: essa solução é chamada Gulp .

Basta colocar todas as suas classs que precisam estar em um namespace na mesma pasta (útil para organização de código). Em seguida, adicione uma tarefa gulp que mescla todos os arquivos desse diretório em um arquivo (gulp-concat). Em seguida, adicione um namespace com o mesmo nome do diretório principal, adicione os arquivos concatenados, adicione uma chave de fechamento e salve em um arquivo.
Feito.
Em seguida, adicione uma tarefa gulp que atente para alterações (e adições / remoções) no mesmo diretório. Em change / add, acione a function concat.

Agora você tem todas as classs em um arquivo e um arquivo que contém todas as classs em um namespace.
Exemplo de código – ao longo das linhas de:

 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']); }); 

Há um módulo de acréscimo-prepend gulp aqui: 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/')); }); 

Por fim, defina o inspetor para iniciar a carga da solução e pronto.