Typescript: Apoie projetos de "médio porte"

Criado em 10 jun. 2015  ·  147Comentários  ·  Fonte: microsoft/TypeScript

Ouvir @nycdotnet me

A proposta aqui foi iniciada pela primeira vez em tempos pré-históricos (mesmo antes de # 11), quando os dinossauros caminharam pela terra arrasada. Embora nada nesta proposta seja novo, por si só, acredito que é hora de abordarmos o problema. A proposta do próprio Steve é ​​# 3394.

O problema

Atualmente, no TypeScript, é bastante fácil começar e seguir em frente, e estamos tornando isso mais fácil a cada dia (com a ajuda de coisas como # 2338 e o trabalho em System.js). Isso é maravilhoso. Mas há um pequeno obstáculo à medida que o tamanho do projeto aumenta. Atualmente, temos um modelo mental mais ou menos assim:

  • Projetos de pequeno porte: use tsconfig.json, mantenha a maior parte de sua fonte no diretório atual
  • Projetos de grande porte: use compilações personalizadas, coloque o código-fonte onde você precisa

Para projetos de pequeno porte, tsconfig.json oferece uma maneira fácil de configurar para trabalhar com qualquer um dos editores de uma forma multiplataforma. Para projetos de grande escala, você provavelmente acabará mudando para construir sistemas devido aos diversos requisitos de projetos de grande escala, e o resultado final será algo que funciona para seus cenários, mas é difícil de usar, porque é muito difícil de usar. a variedade de sistemas e opções de construção.

Steve, em sua entrevista, ressalta que esse não é exatamente o modelo certo de mundo, e eu tendo a concordar com ele. Em vez disso, existem três tamanhos de projeto:

  • Projetos de pequeno porte: use tsconfig.json, mantenha a maior parte de sua fonte no diretório atual
  • Projetos de médio porte: aqueles com compilações padrão e componentes compartilhados
  • Projetos de grande porte: use compilações personalizadas, coloque o código-fonte onde você precisa

Conforme você dimensiona o tamanho do projeto, Steve argumenta, você precisa ser capaz de escalar através da etapa média, ou o suporte da ferramenta cai muito rapidamente.

Proposta

Para resolver isso, proponho que apoiemos projetos de "médio porte". Esses projetos têm etapas de construção padrão que podem ser descritas em tsconfig.json hoje, com a exceção de que o projeto é construído a partir de vários componentes. A hipótese aqui é que há um grande número de projetos neste nível que poderiam ser bem atendidos por este apoio.

Metas

Fornece uma experiência fácil de usar para desenvolvedores que criam projetos de "tamanho médio" para compilação de linha de comando e ao trabalhar com esses projetos em um IDE.

Sem objetivos

Esta proposta _não_ inclui compilação opcional, ou quaisquer etapas fora do que o compilador manipula hoje. Esta proposta também não cobre o empacotamento ou embalagem, que será tratado em uma proposta separada. Em suma, como mencionado no nome, esta proposta cobre apenas os projetos de 'médio porte' e não as necessidades daqueles em grande escala.

Projeto

Para oferecer suporte a projetos de médio porte, nos concentramos no caso de uso de um tsconfig.json fazendo referência a outro.

Exemplo tsconfig.json de hoje:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Seção proposta de 'dependências' de tsconfig.json:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "dependencies": [
        "../common", 
        "../util"
    ],
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Dependências apontam para:

  • Um diretório, onde um tsconfig.json pode ser encontrado
  • Um tsconfig.json diretamente

As dependências são hierárquicas. Para editar o projeto completo, você precisa abrir o diretório correto que contém a raiz tsconfig.json. Isso significa que as dependências não podem ser cíclicas. Embora possa ser possível lidar com dependências cíclicas em alguns casos, outros casos, nomeadamente aqueles com tipos que têm dependências circulares, pode não ser possível fazer uma resolução completa.

Como funciona

As dependências são construídas primeiro, na ordem em que são listadas na seção 'dependências'. Se uma dependência falhar ao construir, o compilador sairá com um erro e não continuará a construir o resto do projeto.

À medida que cada dependência é concluída, um arquivo '.d.ts' representando as saídas é disponibilizado para a construção atual. Depois que todas as dependências forem concluídas, o projeto atual é construído.

Se o usuário especificar um subdiretório como uma dependência e também implicar em sua compilação não fornecendo uma seção de 'arquivos', a dependência é compilada durante a compilação de dependência e também é removida da compilação do projeto atual.

O serviço de idioma pode ver em cada dependência. Como cada dependência será eliminada de seu próprio tsconfig.json, isso pode significar que várias instâncias de serviço de idioma precisariam ser criadas. O resultado final seria um serviço de linguagem coordenado capaz de refatoração, navegação de código, localização de todas as referências etc. nas dependências.

Limitações

Adicionar um diretório como uma dependência sem tsconfig.json é considerado um erro.

As saídas das dependências são consideradas independentes e separadas do projeto atual. Isso significa que você não pode concatenar a saída .js de uma dependência com o projeto atual por meio de tsconfig.json. As ferramentas externas, é claro, podem fornecer essa funcionalidade.

Conforme mencionado anteriormente, as dependências circulares são consideradas um erro. No caso simples:

A - B
\ C

A é o 'projeto atual' e depende de duas dependências: B e C. Se B e C não tiverem dependências, esse caso é trivial. Se C depende de B, B é disponibilizado para C. Isso não é considerado circular. Se, entretanto, B depende de A, isso é considerado circular e seria um erro.

Se, no exemplo, B depende de C e C é independente, isso não seria considerado um ciclo. Nesse caso, a ordem de compilação seria C, B, A, que segue a lógica que temos para /// ref.

Otimizações / melhorias opcionais

Se uma dependência não precisar ser reconstruída, sua etapa de construção será ignorada e a representação '.d.ts' da construção anterior será reutilizada. Isso pode ser estendido para lidar com se a compilação de dependências tiver dependências construídas que aparecerão posteriormente na lista de 'dependências' do projeto atual (como aconteceu no exemplo dado na seção Limitações).

Em vez de tratar os diretórios passados ​​como dependências que não têm um tsconfig.json como casos de erro, podemos opcionalmente aplicar 'arquivos' padrão e as configurações do projeto atual a essa dependência.

Committed Monorepos & Cross-Project References Suggestion

Comentários muito úteis

Trabalho no documento / postagem do blog em andamento abaixo (editarei com base no feedback)

Eu encorajo qualquer pessoa que segue este tópico a tentar. Estou trabalhando no cenário monorepo agora para corrigir quaisquer últimos bugs / recursos lá e devo ter alguma orientação sobre isso em breve


Referências de Projeto

Referências de projeto são um novo recurso no TypeScript 3.0 que permite estruturar seus programas TypeScript em partes menores.

Fazendo isso, você pode melhorar muito os tempos de construção, impor a separação lógica entre os componentes e organizar seu código de maneiras novas e melhores.

Também estamos introduzindo um novo modo para tsc , o sinalizador --build , que funciona em conjunto com referências de projeto para permitir compilações TypeScript mais rápidas.

Um projeto de exemplo

Vamos examinar um programa razoavelmente normal e ver como as referências do projeto podem nos ajudar a organizá-lo melhor.
Imagine que você tenha um projeto com dois módulos, converter e units , e um arquivo de teste correspondente para cada um:

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Os arquivos de teste importam os arquivos de implementação e fazem alguns testes:

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Anteriormente, essa estrutura era um tanto estranha de se trabalhar se você usasse um único arquivo tsconfig:

  • Foi possível para os arquivos de implementação importar os arquivos de teste
  • Não foi possível construir test e src ao mesmo tempo sem ter src aparecendo no nome da pasta de saída, o que você provavelmente não quer
  • Alterar apenas os componentes internos nos arquivos de implementação exigia a verificação de tipos dos testes novamente, mesmo que isso nunca causasse novos erros
  • Alterar apenas os testes exigia a verificação do tipo de implementação novamente, mesmo que nada mudasse

Você poderia usar vários arquivos tsconfig para resolver alguns desses problemas, mas novos apareceriam:

  • Não há verificação atualizada embutida, então você acaba sempre executando tsc duas vezes
  • Invocar tsc duas vezes incorre em mais sobrecarga de tempo de inicialização
  • tsc -w não pode ser executado em vários arquivos de configuração ao mesmo tempo

As referências do projeto podem resolver todos esses problemas e muito mais.

O que é uma referência de projeto?

tsconfig.json arquivos têm uma nova propriedade de nível superior, references . É uma matriz de objetos que especifica projetos para fazer referência:

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

A propriedade path de cada referência pode apontar para um diretório contendo um arquivo tsconfig.json ou para o próprio arquivo de configuração (que pode ter qualquer nome).

Quando você faz referência a um projeto, coisas novas acontecem:

  • Importar módulos de um projeto referenciado carregará seu arquivo de declaração de saída ( .d.ts )
  • Se o projeto referenciado produzir um outFile , as declarações do arquivo de saída .d.ts serão visíveis neste projeto
  • O modo Build (veja abaixo) irá construir automaticamente o projeto referenciado, se necessário

Ao separar em vários projetos, você pode melhorar muito a velocidade de verificação de tipos e compilação, reduzir o uso de memória ao usar um editor e melhorar a aplicação dos agrupamentos lógicos de seu programa.

composite

Projetos referenciados devem ter a nova configuração composite habilitada.
Essa configuração é necessária para garantir que o TypeScript possa determinar rapidamente onde encontrar as saídas do projeto referenciado.
Ativar a sinalização composite muda algumas coisas:

  • A configuração rootDir , se não for definida explicitamente, assume como padrão o diretório que contém o arquivo tsconfig
  • Todos os arquivos de implementação devem ser correspondidos por um padrão include ou listados no array files . Se esta restrição for violada, tsc informará quais arquivos não foram especificados
  • declaration deve ser ativado

declarationMaps

Também adicionamos suporte para mapas de origem de declaração .
Se você habilitar --declarationMap , poderá usar recursos do editor como "Ir para a definição" e Renomear para navegar e editar o código de forma transparente através dos limites do projeto em editores compatíveis.

prepend com outFile

Você também pode ativar o prefixo da saída de uma dependência usando a opção prepend em uma referência:

   "references": [
       { "path": "../utils", "prepend": true }
   ]

Anexar um projeto incluirá a saída do projeto acima da saída do projeto atual.
Isso funciona para .js files e .d.ts files, e os arquivos de mapa de origem também serão emitidos corretamente.

tsc só usará arquivos existentes no disco para fazer este processo, então é possível criar um projeto onde um arquivo de saída correto não pode ser gerado porque a saída de algum projeto estaria presente mais de uma vez no arquivo resultante .
Por exemplo:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

É importante nesta situação não preceder cada referência, porque você acabará com duas cópias de A na saída de D - isso pode levar a resultados inesperados.

Advertências para referências do projeto

As referências de projetos têm algumas desvantagens que você deve conhecer.

Como os projetos dependentes usam arquivos .d.ts que são construídos a partir de suas dependências, você terá que verificar certas saídas de compilação ou construir um projeto depois de cloná-lo antes de poder navegar no projeto em um editor sem ver espúrios erros.
Estamos trabalhando em um processo de geração de .d.ts nos bastidores que deve ser capaz de mitigar isso, mas por enquanto recomendamos informar aos desenvolvedores que eles devem construir após a clonagem.

Além disso, para preservar a compatibilidade com fluxos de trabalho de construção existentes, tsc não construirá dependências automaticamente, a menos que seja invocado com a opção --build .
Vamos aprender mais sobre --build .

Modo de construção para TypeScript

Um recurso muito aguardado são as compilações incrementais inteligentes para projetos TypeScript.
No 3.0, você pode usar a bandeira --build com tsc .
Este é efetivamente um novo ponto de entrada para tsc que se comporta mais como um orquestrador de build do que um simples compilador.

Executar tsc --build ( tsc -b para breve) fará o seguinte:

  • Encontre todos os projetos referenciados
  • Detecte se eles estão atualizados
  • Crie projetos desatualizados na ordem correta

Você pode fornecer tsc -b com vários caminhos de arquivo de configuração (por exemplo, tsc -b src test ).
Assim como tsc -p , especificar o próprio nome do arquivo de configuração é desnecessário se ele for tsconfig.json .

tsc -b Linha de comando

Você pode especificar qualquer número de arquivos de configuração:

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

Não se preocupe em ordenar os arquivos que você passar na linha de comando - tsc irá reordená-los se necessário para que as dependências sejam sempre criadas primeiro.

Existem também alguns sinalizadores específicos para tsc -b :

  • --verbose : Imprime um registro detalhado para explicar o que está acontecendo (pode ser combinado com qualquer outro sinalizador)
  • --dry : Mostra o que seria feito, mas não constrói nada
  • --clean : Exclui as saídas dos projetos especificados (podem ser combinados com --dry )
  • --force : Aja como se todos os projetos estivessem desatualizados
  • --watch : modo de observação (não pode ser combinado com qualquer sinalizador, exceto --verbose )

Ressalvas

Normalmente, tsc produzirá saídas ( .js e .d.ts ) na presença de sintaxe ou erros de tipo, a menos que noEmitOnError esteja ativado.
Fazer isso em um sistema de compilação incremental seria muito ruim - se uma de suas dependências desatualizadas tivesse um novo erro, você só o veria uma vez porque uma compilação subsequente ignoraria a compilação do projeto agora atualizado.
Por esta razão, tsc -b efetivamente age como se noEmitOnError estivesse habilitado para todos os projetos.

Se você verificar qualquer saída de compilação ( .js , .d.ts , .d.ts.map , etc.), pode ser necessário executar uma compilação --force após determinado controle de origem operações dependendo se sua ferramenta de controle de origem preserva mapas de tempo entre a cópia local e a cópia remota.

msbuild

Se você tiver um projeto msbuild, pode ativar o modo de construção adicionando

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

ao seu arquivo proj. Isso habilitará a construção incremental automática, bem como a limpeza.

Observe que, como acontece com tsconfig.json / -p , as propriedades existentes do projeto TypeScript não serão respeitadas - todas as configurações devem ser gerenciadas usando seu arquivo tsconfig.

Algumas equipes configuraram fluxos de trabalho baseados em msbuild, nos quais os arquivos tsconfig têm a mesma ordem de gráfico implícita que os projetos gerenciados com os quais estão emparelhados.
Se a sua solução for assim, você pode continuar a usar msbuild com tsc -p junto com as referências do projeto; estes são totalmente interoperáveis.

Orientação

Estrutura geral

Com mais tsconfig.json arquivos, você geralmente vai querer usar a herança do arquivo de configuração para centralizar as opções comuns do compilador.
Dessa forma, você pode alterar uma configuração em um arquivo em vez de ter que editar vários arquivos.

Outra boa prática é ter um arquivo de "solução" tsconfig.json que simplesmente tenha references para todos os seus projetos de nó-folha.
Isso apresenta um ponto de entrada simples; por exemplo, no repositório TypeScript, simplesmente executamos tsc -b src para construir todos os endpoints porque listamos todos os subprojetos em src/tsconfig.json
Observe que, começando com 3.0, não é mais um erro ter um array files vazio se você tiver pelo menos um reference em um arquivo tsconfig.json .

Você pode ver esses padrões no repositório TypeScript - consulte src/tsconfig_base.json , src/tsconfig.json e src/tsc/tsconfig.json como exemplos-chave.

Estruturação para módulos relativos

Em geral, não é necessário muito para fazer a transição de um repo usando módulos relativos.
Simplesmente coloque um arquivo tsconfig.json em cada subdiretório de uma determinada pasta pai e adicione reference s a esses arquivos de configuração para corresponder à camada pretendida do programa.
Você precisará definir outDir como uma subpasta explícita da pasta de saída ou definir rootDir como a raiz comum de todas as pastas do projeto.

Estruturação para outFiles

O layout para compilações usando outFile é mais flexível porque os caminhos relativos não importam tanto.
Uma coisa a ter em mente é que geralmente você não deseja usar prepend até o "último" projeto - isso melhorará os tempos de construção e reduzirá a quantidade de E / S necessária em qualquer construção.
O repositório TypeScript em si é uma boa referência aqui - temos alguns projetos de "biblioteca" e alguns projetos de "endpoint"; Os projetos "endpoint" são mantidos tão pequenos quanto possível e obtêm apenas as bibliotecas de que precisam.

Estruturação para monorepos

TODO: Experimente mais e descubra isso. Rush e Lerna parecem ter modelos diferentes que implicam coisas diferentes do nosso lado

Todos 147 comentários

Ó meu Deus!

: +1:

Sim! Isso faz sentido para os casos de uso que você forneceu. Fornecer as ferramentas que usamos com uma visão melhor de como nosso código deve ser consumido é definitivamente a coisa certa a se fazer. Cada vez que eu F12 em um arquivo .d.ts por engano, tenho vontade de estrangular um gatinho!

Jonathan,

Muito obrigado pelo feedback amável e por decidir assumir isso. TypeScript é uma ferramenta excelente e essa funcionalidade ajudará muitas pessoas que desejam componentizar suas bases de código de tamanho médio que não podem justificar a ineficiência exigida por projetos enormes que dependem de divisão estrita de interesses (por exemplo, os portais do Azure ou o projeto Monacos de o mundo com> 100kloc e muitas equipes independentes). Em outras palavras, isso realmente ajudará "pessoas normais". Além disso, outros já propuseram coisas para isso, por exemplo @NoelAbrahams (# 2180), e outros, então não posso reivindicar originalidade aqui. Isso é algo que eu preciso há um tempo.

Acho que sua proposta é excelente. A única lacuna que vejo em relação à minha proposta (# 3394), que agora fechei, é a falta de um mecanismo de fallback para referências.

Considere o seguinte cenário do mundo real que detalhei aqui: https://github.com/Microsoft/TypeScript/issues/3394#issuecomment -109359701

Eu tenho um projeto do TypeScript grunt-ts que depende de um projeto diferente csproj2ts. Quase ninguém que trabalha com grunt-ts também vai querer trabalhar com csproj2ts, pois ele tem um escopo de funcionalidade muito limitado. No entanto, para alguém como eu - seria ótimo poder trabalhar nos dois projetos simultaneamente e refatorar / ir para a definição / encontrar todas as referências entre eles.

Quando fiz minha proposta, sugeri que o objeto dependencies fosse um objeto literal com fallbacks nomeados. Uma versão mais adequada à sua proposta seria:

"dependencies": {
   "csproj2ts": ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "SomeRequiredLibrary": "../SomeRequiredLibraryWithNoFallback"
}

Para simplificar para que ainda seja um array, sugiro a seguinte implementação alternativa de uma futura seção hipotética dependencies do arquivo grunt-ts tsconfig.json :

"dependencies": [
   ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "../SomeRequiredLibraryWithNoFallback"
]

A regra de resolução para cada item do tipo array em dependencies seria: o _primeiro_ item encontrado em cada um é o que é usado e os demais são ignorados. Os itens do tipo string são tratados exatamente como afirma a proposta de Jonathan.

Esta é uma solução um pouco mais complicada de implementar, no entanto, dá ao desenvolvedor (e aos autores da biblioteca) flexibilidade _muito maior_. Para desenvolvedores que não precisam desenvolver em csproj2ts (e, portanto, não têm um arquivo ../csproj2ts/tsconfig.json ), a dependência será apenas um arquivo de definição que é adicionado ao contexto de compilação. Para desenvolvedores que _têm_ um arquivo ../csproj2ts/tsconfig.json , a proposta funcionará exatamente como você descreveu acima.

No exemplo acima, "../SomeRequiredLibraryWithNoFallback" deveria estar lá exatamente como em sua proposta existente, e sua ausência seria um erro do compilador.

Muito obrigado por considerar isso.

Há dois problemas aqui que precisamos separar: há construir e há suporte de serviço de idioma.

Para Build, não acho que tsconfig seja o lugar certo para isso. este é claramente um problema de sistema de compilação. com esta proposta, o compilador de texto digitado precisa estar nos negócios de:

  • descobrindo a dependência
  • verificações atualizadas de declarações
  • gerenciamento de configuração (versão vs. depuração)

Tudo isso é claramente responsabilidade dos sistemas de construção; esses são problemas difíceis e já existem ferramentas que fazem isso, por exemplo, MSBuild, grunt, gulp, etc.
Uma vez que tsconfig e tsc se tornem o driver de compilação, você gostaria que eles usassem todas as CPUs para construir subárvores não relacionadas, ou ter comandos de pós e pré-compilação para cada projeto e, possivelmente, construir outros projetos também. Mais uma vez, acho que existem ferramentas de construção que são boas no que fazem, e não há necessidade de recriá-las.

Para serviços linguísticos,
Acho que é bom para o conjunto de ferramentas saber sobre vários arquivos tsconfig e pode inferir disso e ajudá-lo mais, mas isso não deve afetar sua construção. eu consideraria algo como:

"files" : [
    "file1.ts",
    {
        "path": "../projectB/out/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

Onde tsc irá apenas olhar para o "caminho", mas as ferramentas podem olhar para outras informações e tentar ser o mais úteis possível.

Reconheço a existência do problema, mas não acho que agrupar construção e ferramentas seja a solução correta. tsconfig.json deve permanecer um pacote de configuração (ou seja, uma alternativa json para arquivos de resposta) e não se tornar um sistema de construção. um tsconfig.json representa uma única invocação tsc. tsc deve permanecer como um único compilador de projeto.

Os projetos MSBuild no VS são um exemplo de uso de um sistema de construção para construir recursos IDE e agora o ppl não está satisfeito com ele porque é muito grande.

Obrigado pela sua resposta, Mohamed. Deixe-me reafirmar para ver se entendi:

  • Você acha que a tarefa de coordenar compilações de vários projetos deve permanecer o domínio de ferramentas de compilação dedicadas.
  • Você acha que pode haver algo nesta sugestão para o serviço de linguagem TypeScript.
  • Você acha que executar tsc --project neste tsconfig.json seria o mesmo que executar tsc file1.ts ../project/out/project.d.ts . No entanto, abrir tal projeto no VS ou outro editor com o serviço de linguagem TypeScript permitiria "ir para a definição" para levar o desenvolvedor ao _arquivo TypeScript real_ onde o recurso foi definido (em vez da definição em projectB.d.ts )

Eu entendi direito?

Em caso afirmativo, acho isso muito justo. Em minha proposta original (https://github.com/Microsoft/TypeScript/issues/3394), eu disse que minha ideia estava incompleta porque não incluía a etapa de copiar os resultados emitidos de onde eles seriam produzidos na biblioteca referenciada para onde a biblioteca de referência os esperaria em tempo de execução. Acho que você está dizendo "por que ir no meio do caminho para construir quando o que realmente é necessário é o suporte de serviço de idiomas".

Para mudar um pouco os dados em seu exemplo, você estaria disposto a apoiar algo assim?

"files" : [
    "file1.ts",
    {
        "path": "externalLibraries/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

A suposição é que o projeto atual seria enviado com uma definição para projeto B que seria usada por padrão, mas se a fonte real para o projeto B estiver disponível, a fonte real seria usada em seu lugar.

@nycdotnet, você resumiu bem; Eu gostaria de criar um sistema que é fracamente acoplado e permite misturar e combinar diferentes ferramentas de construção com diferentes IDEs, mas ainda assim obter uma ótima experiência de design.

Parece bom!

Eu concordo com @mhegazy e na verdade acho que é importante para o TypeScript parar de se considerar um 'compilador' e começar a se considerar um 'verificador de tipos' e 'transpiler'. Agora há suporte para transpilação de arquivo único. Não vejo nenhuma razão para que arquivos JavaScript compilados sejam criados, exceto em tempo de execução / empacotamento. Também não vejo por que é necessário gerar as definições de referência externa durante a verificação de tipo, quando a fonte de texto digitado real está disponível.

A resolução de arquivos e pacotes é de responsabilidade do sistema de compilação que você está usando (browserify, systemjs, webpack etc), portanto, para que o conjunto de ferramentas funcione, o serviço de idioma precisa ser capaz de resolver arquivos da mesma forma que qualquer sistema / plataforma de compilação que você estão usando. Isso significa implementar um LanguageServicesHost customizado para cada sistema de compilação ou fornecer uma ferramenta para cada um que gere as entradas de mapeamento corretas em tsconfig.json. Qualquer um desses é aceitável.

@nycdotnet Acho que seu caso de uso para vários caminhos alternativos seria melhor tratado usando npm link ../csproj2ts ?

Eu concordo com @mhegazy que devemos manter a compilação separada do compilador / transpilador. Eu gosto de incluir a dependência tsconfig -> tsconfig na seção de arquivos, supondo que, se a seção não listar nenhum arquivo * .ts, ela ainda os procura. Por exemplo

"arquivos" : [
{
"path": "externalLibraries / projectB.d.ts",
"sourceProject": "../projectB/"
}
]

Ainda incluirá todos os arquivos ts no diretório e subdiretório que contém o arquivo tsconfig.json.

@dbaeumer você então deseja tê-lo em uma propriedade diferente, correto? atualmente, se os arquivos são definidos, eles são sempre usados ​​e ignoramos a parte include * .ts.

@mhegazy não é necessariamente uma seção diferente, embora tornasse as coisas mais claras no final. Tudo que eu quero evitar é ser forçado a listar todos os arquivos se eu usar um tsconfig -> dependências tsconfig. No exemplo acima, eu ainda gostaria de não listar nenhum arquivo * .ts para alimentá-los no compilador.

Eu acho que isso é muito necessário. Não acho que podemos evitar a questão da construção, no entanto. Isso não significa que precisamos implementar um sistema de compilação junto com esta proposta, mas devemos ter pensado em algumas boas orientações que funcionem em uma configuração como a proposta. Não será trivial acertar (e precisamos resolvê-lo para o Visual Studio).

Na proposta acima (onde tanto o .d.ts quanto a fonte são referenciados), ele detecta se o .d.ts está desatualizado (ou seja, precisa ser reconstruído)? As operações como Refatorar / Renomear funcionam nos projetos (ou seja, atualiza o nome na origem do projeto referenciada, não apenas seu arquivo .d.ts, que será sobrescrito na próxima compilação)? GoToDef me leva ao código original no projeto referenciado (não no meio de um arquivo .d.ts gigante para todo o projeto)? Parece que isso implica a necessidade de resolver e, em alguns casos, analisar a origem dos projetos referenciados, caso em que o .d.ts é tão útil?

A solução geral, que temos hoje, você tem um .d.ts como uma saída de compilação de um projeto e, em seguida, referenciado como entrada em outro projeto. Isso funciona bem para a construção, então não há necessidade de alterar isso.

O problema é o cenário de edição. você não deseja passar por um arquivo gerado durante a edição. Minha solução proposta é fornecer uma "dica" de onde vieram os .d.ts gerados. O serviço de idioma não carregará o .d.ts e, em vez disso, carregará um "projeto" do caminho da dica. desta forma, goto def o levará ao arquivo de implementação em vez de .d.ts e erros semelhantes funcionariam sem a necessidade de uma compilação.

operações como renomear irão "propagar-se" de um projeto para outro e, da mesma forma, encontrar referências.

Hoje, fica totalmente a cargo do host (o IDE, qualquer que seja) encontrar o arquivo tsconfig.json, embora o TS então forneça APIs para lê-lo e analisá-lo. Como você imaginaria que isso funcionaria se houvesse vários tsconfig.json localizados de forma hierárquica? O host ainda seria responsável por resolver o arquivo inicial, mas não os outros, ou seria o host responsável por resolver todos os tsconfigs?

Parece que há uma compensação entre conveniência / convenção e flexibilidade.

Isso não começaria com a capacidade de construir arquivos d.ts conforme descrito em # 2568 (ou pelo menos ter importações relativas)?

@spion não tenho certeza se vejo a dependência aqui. você pode ter múltiplas saídas de um projeto, não tem que ser um único arquivo de deleção. o sistema de compilação deve ser capaz de saber disso e conectá-los como entradas a projetos dependentes.

@mhegazy Oops, desculpe. Olhando para o problema novamente, parece que isso está mais relacionado ao serviço de idiomas. Eu li o seguinte

  • Projetos de médio porte: aqueles com compilações padrão e componentes compartilhados

e assumiu automaticamente que está relacionado a um melhor suporte para npm / browserify (ou webpack) build worfklows onde partes do projeto são módulos externos.

AFAIK não há como gerar arquivo (s) .d.ts para módulos externos ainda? Nesse caso, a única maneira de um serviço de linguagem vincular projetos que importam módulos externos seria ter algo assim em tsconfig.json:

{ 
  "provides": "external-module-name"
}

que informaria ao LS quando os projetos fossem referenciados em outro tsconfig.json

AFAIK não há como gerar arquivo (s) .d.ts para módulos externos ainda?

Eu não acho que isso seja verdade. chamar tsc --m --d irá gerar um arquivo de declaração que é um módulo externo. a lógica de resolução tentará encontrar um .ts e, se não, um .d.ts com o mesmo nome,

@spion TypeScript pode gerar arquivos d.ts para módulos externos como @mhegazy disse, mas isso resulta em uma proporção de 1: 1 de definições para arquivos de origem que é diferente de como as definições de TypeScript de uma biblioteca são normalmente consumidas. Uma maneira de contornar isso é esta biblioteca TypeStrong: https://github.com/TypeStrong/dts-bundle

@mhegazy desculpe, eu quis dizer "módulos externos ambientais", ou seja, se eu escrever external-module-name no TypeScript e importar uma de suas classes de outro módulo:

import {MyClass} from 'external-module-name'

não há como fazer tsc gerar o arquivo .d.ts apropriado que declara 'external-module-name'

@nycdotnet Estou ciente de dts-bundle e dts-generator, mas ainda se o serviço de linguagem deve saber sobre a fonte do meu outro projeto, ele também deve saber qual nome de módulo ele fornece para ser capaz de rastrear as importações corretamente

Qual é o status deste recurso? parece que esta é uma opção importante para projetos de médio porte. Como você configura um projeto que tem código-fonte em pastas diferentes com uma configuração "requirejs" específica?

@llgcode , dê uma olhada em https://github.com/Microsoft/TypeScript/issues/5039 , ele deve estar disponível em typescript@next .

Eu realmente não entendo o que é tão difícil em escrever um gulpfile com tarefas que compilam os subprojetos exatamente como você precisa para projetos de médio porte. Eu até faço isso em projetos de pequeno porte. A única razão pela qual uso tsconfig.json é para o código VS

Primeiro eu não uso gole. Em segundo lugar, pode ser um grande projeto em que você não deseja recompilar todo o tempo. Mas se você tiver uma boa solução com gole, deixe-me saber como fazer isso.

@llgcode Bem, gulp é um gulp.task() . Dentro de sua tarefa, você pode pegar um fluxo de arquivos de entrada com gulp.src() e então .pipe() por meio de um pipeline de transformações, como compilação, concatenação, minificação, mapas de origem, cópia de ativos ... Você pode faça tudo o que for possível com os módulos Node e NPM.
Se você deve compilar vários projetos, apenas defina uma tarefa que faça isso. Se você quiser usar vários tsconfig.json, gulp-typescript tem suporte para isso, ou você pode apenas ler os arquivos json. Construções incrementais também são possíveis. Não sei como o seu projeto está estruturado, se você os tem em diferentes repositórios e usa submódulos, ou o que seja. Mas o gole é 100% flexível.

Ok, obrigado parece ser uma ótima ferramenta. se eu tiver requerido com mapeamento como require ("mylibs / lib") e meus arquivos estão, por exemplo, em uma pasta project / src / lib.js então a conclusão não funcionará no atom e eu não sei como o typescript ou gulp resolverão o mapeamento / configuração feito com "mylibs" e um caminho local. então eu acho que essa nova opção de caminhos em # 5039 é uma boa solução para esse problema.

@llgcode Bem, com o gulp, você pode obter todos os arquivos (incluindo arquivos .d.ts) com globs, consulte https://www.npmjs.com/package/gulp-typescript#resolving -files.

Só acho que o TypeScript está tentando fazer muito aqui, é um transpiler e não uma ferramenta de construção. Gerenciar "dependências" como mencionado na proposta é realmente a tarefa dos gerenciadores de pacotes ou sistemas de controle de versão e conectá-los é a tarefa das ferramentas de construção.

@felixfbecker não concordo. Cada compilador (com verificação de tipo) que conheço tem uma opção como esta. por exemplo:
gcc -> incluir arquivos
java -> classpath e sourcepath
vá -> GOPATH
python -> PYTHONPATH
o compilador / transpilador precisa saber quais arquivos fonte precisam ser transpilados quais arquivos fonte são apenas incluir arquivos / lib.
Uma ferramenta de construção como o gulp é necessária para saber o que fazer quando um arquivo é alterado.

Concordo com @llgcode . Além disso, além de expor como um compilador, o TypeScript também se expõe como um serviço de linguagem, que fornece destaque de sintaxe (detecção, na verdade) e funcionalidade de conclusão para IDE. E isso também precisa andar na árvore de dependência.

@llgcode @unional Pontos válidos. Uma coisa que também pode ajudar é fazer com que a propriedade files em tsconfig.json aceite globs para que você possa definir todos os arquivos em todas as pastas que deseja incluir. Mas eu vejo de onde você está vindo e por que alguém pode querer vários tsconfig.json para projetos maiores.

AFAIK para CommonJS já é suportado por node_modules e npm link ../path/to/other-project

O link npm não funciona assim que você começa a reutilizar as bibliotecas nos projetos. Se você usar uma biblioteca comum entre dois projetos separados de sua autoria, (tomando rxjs como exemplo) o texto digitado dirá que 'Observável não pode ser atribuído a Observável'. Isso porque os caminhos de inclusão seguem pastas de links simbólicos para duas pastas node_modules diferentes e apesar de serem a mesma biblioteca. As soluções alternativas resultam na construção de tarefas gulp ou repositórios npm locais / privados, basicamente de volta à opção de projeto grande.

@EricABC provavelmente porque eles estão usando declarações de módulo externo do ambiente, caso em que também devem incluir definições para os arquivos .d.ts baseados em node_modules recentemente suportados. Fora isso, não deve haver problema, pois os tipos de TS são verificados apenas estruturalmente, então não importa se eles vêm de módulos diferentes ou têm nomes diferentes, desde que as estruturas correspondam.

Obrigado @spion , apenas assumi que era baseado em arquivo, parece que você vai me salvar de algumas dores auto-infligidas.

Uma coisa que também pode ajudar é fazer com que a propriedade de arquivos em tsconfig.json aceite globs ...

Há uma propriedade include em discussão

Perguntas e observações:

  • dependencies deve permitir o caminho tsconfig.json completo porque tsc permite
  • Por que introduzir uma nova palavra-chave ( dependencies ) quando files já existe e está bom?
    Exemplo:
{
    "compilerOptions": {
        // ...
    },
    "files": [
        "../common/tsconfig.json", // <== takes the `files` part of the tsconfig.json
        "../common/tsconfig.util.json", // <==
        "core.ts",
        "sys.ts"
    ]
}
  • O que acontecerá se a dependência tsconfig.json também especificar compilerOptions ?


Vamos mais longe / wild :-) e possivelmente permitir (no futuro) compilerOptions , exclude ... fazer referência a outro tsconfig.json:

// File app/tsconfig.json
{
    "compilerOptions": "../common/tsconfig.compilerOptions.json",
    "files": [
        "../common/tsconfig.json",
        "../common/tsconfig.util.json",
        "core.ts",
        "sys.ts"
    ],
    "exclude": "../common/exclude.json"
}

// File ../common/tsconfig.compilerOptions.json
{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    }
}

// File ../common/exclude.json
{
    "exclude": [
        "node_modules",
        "wwwroot"
    ]
}

// File ../common/tsconfig.util.json
{
    "files": [
        "foo.ts",
        "bar.ts"
    ]
}

Você entendeu a lógica: files , compilerOptions , exclude ... pode fazer referência a outros arquivos tsconfig.json e só "pegará" a parte correspondente da palavra-chave do outro tsconfig arquivo .json => fácil e escalável. Você pode, portanto, dividir um tsconfig.json em vários arquivos se quiser e reutilizá-los.

Lendo parte de sua discussão, parece mais relevante obter essa coisa de definição de "serviço de linguagem" / goto direito. Os depuradores de JavaScript usam sourceMaps. Agora, se o tsc gerou dados sourceMap não apenas em arquivos .js, mas também em arquivos .d.ts ...

Além disso, eu realmente não vejo muitos benefícios em acionar compilações de projetos filhos de dentro do arquivo tsconfig.json. Se você precisar desse tipo básico de dependências de tempo de construção, um script de shell simples fará o trabalho. Se você precisa de uma construção incremental inteligente, por outro lado, a abordagem proposta parece muito simples. Em muitos cenários, o tsc acaba sendo apenas uma etapa de construção entre outras. Que estranho escrever dependências para tsc em tsconfig.json, mas algum outro arquivo para o resto? Novamente, para coisas simples, com tsc sendo a única etapa de construção que um script de shell faria.

De qualquer forma, que tal gerar mapeamento de origem em arquivos .d.ts assim como em arquivos .js?

nós simplesmente usamos módulos de nó + link npm, a única coisa que não funciona é que o móduloResolution: nó não é compatível com módulos ES6, que permitem otimizações inlining / tree-shake (ver também # 11103)

Para não sair do assunto, mas de certa forma, isso parece paralelo aos desafios de trabalhar com vários projetos de pacote de Nó localmente. Não acho que seja tão simples quanto usar o "link npm". Eles podem ter scripts de construção diferentes, você precisa executar todos eles na ordem certa, é mais difícil fazer isso de forma incremental, é mais difícil usar o modo de observação, é mais difícil depurar coisas e é mais difícil resolver mapas de origem. Dependendo do editor de sua escolha, isso pode ser ainda mais desafiador.

Geralmente, eu simplesmente desisto, coloco tudo em um projeto gigante e, em seguida, divido em pacotes separados quando eles se estabilizam, mas isso só ajuda porque o desafio está sendo enfrentado com menos frequência. Eu acho toda a experiência muito, muito chata. Estou esquecendo de algo?

Então, seja o que for, eu só espero poder finalmente ter uma solução elegante para toda essa experiência de desenvolvimento.

Basta dizer que isso seria ótimo para o nosso trabalho do dia-a-dia!

Devido à natureza do desenvolvimento web, temos vários projetos ts com cada projeto contendo vários arquivos ts compilados em um único arquivo js ( --outFile ). Esses são projetos do tipo aplicativo (eles fazem uma coisa específica ou adicionam um recurso específico) ou projetos do tipo lib (código reutilizável para os aplicativos). Freqüentemente, trabalhamos em vários desses projetos ts ao mesmo tempo, aprimorando as bibliotecas para facilitar o desenvolvimento nos aplicativos. Mas eu não acho que nenhum de meus colegas desenvolvedores tenha todos os nossos projetos ts em seus ambientes locais em nenhum momento.

Para melhorar nosso fluxo de trabalho, nossas opções atuais são

  • Jogue tudo em um projeto ts

    • Podemos fazer referência a arquivos .ts diretamente, o que é muito mais agradável do que usar os arquivos d.ts

    • Mas precisamos de uma ferramenta externa para mapear e concatenar conjuntos de arquivos, uma vez que precisamos limitar os arquivos js solicitados nas interwebs enquanto mantemos a modularidade dos aplicativos (que são específicos para recursos ou páginas).

    • Como mencionado, nem todos esses projetos ts são necessários o tempo todo para todos, então isso aumentaria muito o sistema de arquivos. Alguém que trabalha no projeto X pode precisar dos projetos A, B e D, mas alguém que trabalha no Y pode precisar de A e C.

  • Mantenha os projetos separados (nossa situação atual)

    • Precisamos fazer referência aos arquivos d.ts compilados de outros projetos ts, pois incluir um arquivo .ts diretamente o adicionaria à saída. Seria muito mais rápido se pudéssemos apenas f12 direto em nosso próprio código-fonte.

    • E precisamos adicionar ferramentas / scripts para compilar vários desses projetos ao mesmo tempo. Atualmente, iniciamos os comandos tsc -d -w em vários terminais ou acionamos um script que faz isso para todos os subdiretórios onde um tsconfig é encontrado.

    • Eu entendo que outras ferramentas podem ajudar com isso (por exemplo, gulp) ou talvez possamos descobrir algo com globs de arquivo nos tsconfigs, no entanto, isso não ajudaria com a primeira edição dos arquivos d.ts.

Nossos projetos simplesmente não são grandes o suficiente para manter o desenvolvimento das bibliotecas estritamente separado dos aplicativos, mas não são pequenos o suficiente para que possamos simplesmente juntar tudo. Percebemos que faltam opções elegantes com o texto datilografado.

Se dependencies pode ser a melhor opção de dois mundos, isso seria incrível; a funcionalidade detags para todos os arquivos ts de uma dependência, mas compilar a saída de acordo com o tsconfig da própria dependência.

Existe alguma atualização sobre este tópico. Como podemos ter vários projetos de texto datilografado que compilam independentemente uns dos outros? Como podemos descrever essa dependência em tsconfig.json?

Isso agora está incluído no roteiro na seção futura com o título "Suporte para referências de projeto". Então, eu acho que um arquivo .tsconfig será capaz de vincular outro arquivo .tsconfig como uma dependência.

Existe alguma atualização sobre este tópico. Como podemos ter vários projetos de texto datilografado que compilam independentemente uns dos outros? Como podemos descrever essa dependência em tsconfig.json?

A dependência de construção deve ser codificada em seu sistema de construção. sistemas de construção como gulp, grunt, brócolis, msbuild, basal, etc. são construídos para lidar com tais casos.
Para informações de tipo, a saída de um projeto deve incluir um .d.ts e isso deve ser passado como entrada para o outro.

@mhegazy Nosso projeto funciona assim. Temos vários pacotes em uma lerna monorepo, cada pacote tem suas próprias dependências em package.json e essas mesmas dependências na propriedade "types" em seu tsconfig.json . Cada projeto é compilado com --outFile (é um projeto mais antigo que não foi movido para os módulos ES ainda) e os pontos-chave "typings" package.json para o .d.ts empacotado

Usamos gole para o empacotar / assistir.

Funciona na maior parte, mas existem alguns problemas:

  • Devido a problemas como # 15488 e # 15487, precisamos ter um link explícito para que as referências funcionem corretamente.
  • Ir para a definição o levará a um arquivo .d.ts empacotado. Idealmente, isso o levaria à fonte em outro projeto.
  • A maneira mais rápida de fazer uma compilação completa é lerna run build --sort (efetivamente tsc em cada diretório), que tem sobrecarga adicional porque vai gerar um processo de compilador TypeScript para cada pacote, realizando muito trabalho repetido .

Estou acompanhando esse problema de perto, pois também estamos na mesma situação que outras pessoas descreveram.
Vários "projetos", cada um com seu arquivo tsconfig.json.

Temos o processo de construção funcionando como @mhegazy apontado: cada projeto emite um arquivo .d.ts e que é usado como entrada para os projetos dependentes.

O verdadeiro problema é o suporte IDE: ao procurar referências, elas são encontradas apenas no escopo de um único tsconfig.json . Pior ainda, os efeitos em cascata de um arquivo alterado não se propagam pelos projetos porque os arquivos dependentes fora do escopo tsconfig.json não são recompilados. Isso é muito ruim para a manutenção de nossos projetos e às vezes causa erros de construção que podem ter sido detectados no IDE.

It's happening

Ó MEU DEUS

Um cenário atualizado no qual eu adoraria ter isso envolve os componentes React. Temos um repositório de componentes que contém módulos JSX (átomos, moléculas e organismos) que tornam os componentes da IU apropriados em todos os aplicativos de nossa empresa. Este repo de componentes é usado por todos os desenvolvedores front-end enquanto trabalham em seus aplicativos individuais. Seria TÃO BOM se eu pudesse ter uma experiência de serviço de linguagem TypeScript que me permitisse editar a IU do meu aplicativo específico e "Ir para a definição" no repositório de componentes de IU comum. Hoje temos que agrupar esses componentes individualmente e copiá-los. Este é o problema de "faça seu próprio encanamento de projeto" que eu adoraria ver resolvido (para o qual há uma história muito boa no mundo .NET com projetos sob uma solução).

Referências do projeto: Escalabilidade integrada para TypeScript

Introdução

O TypeScript foi ampliado para projetos de centenas de milhares de linhas, mas não oferece suporte nativo a esse tipo de escala como um comportamento interno. As equipes desenvolveram soluções alternativas de eficácia variável e não há uma maneira padronizada de representar grandes projetos. Embora tenhamos melhorado muito o desempenho do typechecker ao longo do tempo, ainda existem limites rígidos em termos de quão rápido o TS pode ser razoavelmente
e restrições como o espaço de endereço de 32 bits que evita que o serviço de linguagem seja ampliado "infinitamente" em cenários interativos.

Intuitivamente, a alteração de uma linha de JSX em um componente de front-end não deve exigir uma nova verificação de todo o componente principal da lógica de negócios de um projeto de 500.000 LOC. O objetivo das referências do particionar seu código em blocos menores. Ao permitir que as ferramentas operem em pedaços menores de trabalho por vez, podemos melhorar a capacidade de resposta e estreitar o ciclo de desenvolvimento principal.

Acreditamos que nossa arquitetura atual tenha muito poucos "almoços grátis" restantes em termos de melhorias drásticas no desempenho ou no consumo de memória. Em vez disso, esse particionamento é uma troca explícita que aumenta a velocidade às custas de algum trabalho inicial. Os desenvolvedores terão que gastar algum tempo raciocinando sobre o gráfico de dependência de seu sistema, e certos recursos interativos (por exemplo, renomeações entre projetos) podem estar indisponíveis até que aprimoremos ainda mais as ferramentas.

Identificaremos as principais restrições impostas por este sistema e estabeleceremos diretrizes para dimensionamento de projetos, estrutura de diretórios e padrões de construção.

Cenários

Existem três cenários principais a serem considerados.

Módulos Relativos

Alguns projetos usam extensivamente as importações relativas . Essas importações resolvem inequivocamente para outro arquivo no disco. Caminhos como ../../core/utils/otherMod seriam comuns de encontrar, embora estruturas de diretório mais planas sejam geralmente preferidas nesses repositórios.

Exemplo

Aqui está um exemplo do projeto perseus da Khan Academy:

Adaptado de https://github.com/Khan/perseus/blob/master/src/components/graph.jsx

const Util = require("../util.js");
const GraphUtils = require("../util/graph-utils.js");
const {interactiveSizes} = require("../styles/constants.js");
const SvgImage = require("../components/svg-image.jsx");

Observações

Embora a estrutura de diretório implique uma estrutura de projeto, não é necessariamente definitiva. No exemplo da Khan Academy acima, pode-se inferir que util , styles e components provavelmente seriam seus próprios projetos. Mas também é possível que esses diretórios sejam bem pequenos e, na verdade, sejam agrupados em uma unidade de construção.

Mono-repo

Um mono-repo consiste em vários módulos que são importados por caminhos não relativos . As importações de submódulos (por exemplo, import * as C from 'core/thing ) podem ser comuns. Normalmente, mas nem sempre, cada módulo raiz é publicado no NPM.

Exemplo

Adaptado de https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts

import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {forkJoin} from 'rxjs/observable/forkJoin';
import {map} from 'rxjs/operator/map';
import {AbstractControl, FormControl} from './model';

Observações

A unidade de divisão não é necessariamente a parte inicial do nome do módulo. rxjs , por exemplo, realmente compila suas subpartes ( observable , operator ) separadamente, assim como qualquer pacote com escopo definido (por exemplo, @angular/core ).

Outfile

O TypeScript pode concatenar seus arquivos de entrada em um único arquivo JavaScript de saída. As diretivas de referência ou ordenação de arquivos em tsconfig.json criam uma ordem de saída determinística para o arquivo resultante. Isso raramente é usado para novos projetos, mas ainda prevalece entre as bases de código mais antigas (incluindo o próprio TypeScript).

Exemplo

https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts

/// <reference path="program.ts"/>
/// <reference path="watch.ts"/>
/// <reference path="commandLineParser.ts"/>

https://github.com/Microsoft/TypeScript/blob/master/src/harness/unittests/customTransforms.ts

/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\harness.ts" />

Observações

Algumas soluções que usam esta configuração carregarão cada outFile por meio de uma tag script separada (ou equivalente), mas outras (por exemplo, o próprio TypeScript) requerem concatenação dos arquivos anteriores porque estão construindo saídas monolíticas .

Referências do projeto: uma nova unidade de isolamento

Algumas observações críticas da interação com projetos reais:

  • TypeScript é geralmente "rápido" (<5-10s) ao verificar projetos abaixo de 50.000 LOC de código de implementação (não .d.ts)
  • Arquivos .d.ts, especialmente em skipLibCheck , são quase "gratuitos" em termos de verificação de tipos e custo de memória
  • Quase todo software pode ser subdividido em componentes menores que 50.000 LOC
  • Quase todos os grandes projetos já impõem alguma estruturação de seus arquivos por diretório de uma forma que produz subcomponentes de tamanho moderado
  • A maioria das edições ocorre em componentes de nó-folha ou de nó-folha próximo que não requerem nova verificação ou reemissão de toda a solução

Juntando tudo isso, se fosse possível apenas verificar um bloco de código de implementação de 50.000 LOC de uma vez, quase não haveria interações "lentas" em um cenário interativo e quase nunca ficaríamos sem memória.

Apresentamos um novo conceito, uma referência de projeto , que declara um novo tipo de dependência entre duas unidades de compilação TypeScript onde o código de implementação da unidade dependente não é verificado; em vez disso, simplesmente carregamos sua saída .d.ts de uma localização determinística.

Sintaxe

Uma nova opção references (TODO: Bikeshed!) É adicionada a tsconfig.json :

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "outDir": "../bin",
    "references": [
      { "path": "../otherProject" }
    ]
  }
}

O array references especifica um conjunto de outros projetos para fazer referência a partir deste projeto.
Cada references object's path aponta para um arquivo tsconfig.json ou uma pasta contendo um arquivo tsconfig.json .
Outras opções podem ser adicionadas a este objeto conforme descobrimos suas necessidades.

Semântica

As referências do projeto mudam o seguinte comportamento:

  • Quando a resolução do módulo resolveria para um arquivo .ts em um subdiretório de rootDir de um projeto, em vez disso seria resolvido para um arquivo .d.ts no outDir desse projeto

    • Se esta resolução falhar, provavelmente podemos detectá-lo e emitir um erro mais inteligente, por exemplo, Referenced project "../otherProject" is not built vez de um simples "arquivo não encontrado"

  • Nada mais (TODO: até agora?)

Restrições para desempenho de projetos referenciados

Para melhorar significativamente o desempenho de construção, precisamos ter certeza de restringir o comportamento do TypeScript quando ele vê uma referência de projeto.

Especificamente, as seguintes coisas devem ser verdadeiras:

  • Nunca leia ou analise os arquivos .ts de entrada de um projeto referenciado
  • Apenas o tsconfig.json de um projeto referenciado deve ser lido do disco
  • A verificação atualizada não deve exigir a violação das restrições acima

Para cumprir essas promessas, precisamos impor algumas restrições aos projetos que você faz referência.

  • declaration é automaticamente definido como true . É um erro tentar substituir esta configuração
  • rootDir assume "." (o diretório que contém o arquivo tsconfig.json ), em vez de ser inferido do conjunto de arquivos de entrada
  • Se um array files for fornecido, ele deve fornecer os nomes de todos os arquivos de entrada

    • Exceção: os arquivos incluídos como parte das referências de tipo (por exemplo, aqueles em node_modules/@types ) não precisam ser especificados

  • Qualquer projeto referenciado deve ter um array references (que pode estar vazio).

Por que "declaration": true ?

As referências de projeto aumentam a velocidade de construção usando arquivos de declaração (.d.ts) no lugar de seus arquivos de implementação (.ts).
Portanto, naturalmente, qualquer projeto referenciado deve ter a configuração declaration ativada.
Isso está implícito em "project": true

Por que alterar rootDir ?

O rootDir controla como os arquivos de entrada são mapeados para os nomes dos arquivos de saída. O comportamento padrão do TypeScript é calcular o diretório de ["src/a.ts", "src/b.ts"] produzirá os arquivos de saída ["a.js", "b.js"] ,
mas o conjunto de arquivos de entrada ["src/a.ts", "b.ts"] produzirá os arquivos de saída ["src/a.js", "b.js"] .

O cálculo do conjunto de arquivos de entrada requer a análise de cada arquivo raiz e todas as suas referências recursivamente,
o que é caro em um grande projeto. Mas não podemos mudar esse comportamento hoje sem quebrar projetos existentes de uma maneira ruim, então essa mudança só ocorre quando o array references é fornecido.

Sem Circularidade

Naturalmente, os projetos não podem formar um gráfico com nenhuma circularidade. (TODO: Que problemas isso realmente causa, além de criar pesadelos de orquestração?) Se isso ocorrer, você verá uma mensagem de erro que indica o caminho circular que foi formado:

TS6187: Project references may not form a circular graph. Cycle detected:
    C:/github/project-references-demo/core/tsconfig.json ->
    C:/github/project-references-demo/zoo/tsconfig.json ->
    C:/github/project-references-demo/animals/tsconfig.json ->
    C:/github/project-references-demo/core/tsconfig.json

tsbuild

Esta proposta é intencionalmente vaga sobre como seria usada em um sistema de construção "real". Poucos projetos ultrapassam o limite "rápido" de 50.000 LOC sem introduzir algo diferente de tsc para compilar o código .ts.

O cenário do usuário de "Você não pode construir foo porque bar ainda não foi construído" é um tipo de tarefa óbvio "Vá buscar aquele" que um computador deveria cuidar, em vez disso do que um fardo mental para os desenvolvedores.

Esperamos que ferramentas como gulp , webpack , etc, (ou seus respectivos plug-ins TS) aumentem a compreensão das referências do projeto e lidem corretamente com essas dependências de construção, incluindo a verificação atualizada.

Para garantir que isso seja possível , forneceremos uma implementação de referência para uma ferramenta de orquestração de construção do TypeScript que demonstra os seguintes comportamentos:

  • Verificação rápida e atualizada
  • Ordenação do gráfico do projeto
  • Construir paralelização
  • (TODO: outros?)

Essa ferramenta deve usar apenas APIs públicas e ser bem documentada para ajudar a criar os autores da ferramenta a entender a maneira correta de implementar as referências do projeto.

PENDÊNCIA

Seções a serem preenchidas para completar totalmente esta proposta

  • Como você faria a transição de um projeto existente

    • Basicamente, basta inserir tsconfig.json arquivos e, em seguida, adicionar as referências necessárias para corrigir os erros de compilação

  • Impacto em baseUrl

    • Torna a implementação difícil, mas efetivamente sem impacto para o usuário final

  • Breve discussão sobre projetos de aninhamento (TL; DR, é necessário permitir)
  • Descreva o cenário de subdividir um projeto que se tornou "muito grande" em projetos menores sem quebrar os consumidores
  • Descubra o cenário Lerna

    • O ponto de dados disponível (N = 1) diz que eles não precisariam disso porque sua construção já está efetivamente estruturada desta forma

    • Encontre mais exemplos ou contra-exemplos para entender melhor como as pessoas fazem isso

  • Precisamos de uma configuração dtsEmitOnly para pessoas que estão canalizando seu JS por exemplo, webpack / babel / rollup?

    • Talvez references + noEmit implique nisso

Fantástico!

Descubra o cenário Lerna

  • O ponto de dados disponível (N = 1) diz que eles não precisariam disso porque sua construção já está efetivamente estruturada desta forma

"Isso" se refere à proposta ou à implementação de compilação de referência? Embora você possa usar o lerna para fazer a compilação (minha equipe faz), é complicado e seria muito mais eficiente se o TS (ou uma ferramenta construída a partir desta proposta) cuidasse de si mesmo.

A seção TODO é TODOs para toda a proposta

Agradável!

Qualquer projeto referenciado deve ter uma matriz de referências (que pode estar vazia).

Isso é realmente necessário? Não seria suficiente se tal pacote tivesse .d.ts arquivos?
(Nesse caso, pode nem ser necessário que haja um tsconfig.json também?)

Meu caso de uso: considere um projeto (por exemplo, de terceiros) que não usa outDir , então .ts , .js e .d.ts estarão próximos a uns aos outros, e o TS irá atualmente tentar compilar o .ts vez de usar o .d.ts .

A razão para não usar outDir para mim é permitir mais facilmente importações no estilo import "package/subthing" , que de outra forma teriam que ser, por exemplo, import "package/dist/subthing" com outDir: "dist" .
E ser capaz de usar o pacote NPM ou seu repositório de origem diretamente (por exemplo, com npm link ).

(Seria útil se package.json permitisse especificar um diretório em main , mas, infelizmente ...)

Precisamos de uma configuração dtsEmitOnly para pessoas que estão canalizando seu JS por meio de, por exemplo, webpack / babel / rollup?

Absolutamente! Esta é uma grande peça que faltava no momento. Atualmente você pode obter um único arquivo d.ts ao usar outFile , mas quando você muda para módulos e usa um empacotador, você perde isso. Ser capaz de emitir um único arquivo d.ts para o ponto de entrada de um módulo (com export as namespace MyLib ) seria incrível. Sei que ferramentas externas podem fazer isso, mas seria realmente ótimo se integrassem ao emissor e aos serviços de linguagem.

Isso é realmente necessário? Não seria suficiente se esse pacote tivesse arquivos .d.ts?

Precisamos de algo no tsconfig de destino que nos diga onde esperar que os arquivos de saída estejam. Uma versão anterior desta proposta tinha "Você deve especificar um rootDir explícito", o que era bastante complicado (você tinha que escrever "rootDir": "." em cada tsconfig). Uma vez que queremos inverter uma variedade de comportamentos neste mundo, faz mais sentido apenas dizer que você obtém o comportamento de "projeto" se você tem um array de referências e tem que ser o que está desligado, em vez de especificar um monte de sinalizadores que você teria que declarar explicitamente.

Esta proposta se alinha de perto com a forma como já estruturamos nossos projetos TypeScript. Nós subdividimos em unidades menores em que cada uma tem um tsconfig.json e são construídas de forma independente por meio do gole. Os projetos fazem referência uns aos outros referenciando os arquivos d.ts.

Em um mundo ideal, o projeto referenciado não precisaria ser pré-construído. ou seja, O TypeScript faz uma "construção" do projeto referenciado e mantém o equivalente "d.ts" na memória no serviço de linguagem. isso permitiria que as alterações feitas no projeto "fonte" aparecessem no projeto "dependente" sem a necessidade de reconstrução.

Precisamos de algo no tsconfig de destino que nos diga onde esperar que os arquivos de saída estejam.

Isso só é verdade quando outDir é usado, não é?

Como em: se eu tiver um tsconfig que:

  • NÃO usa outDir (mas tem declaration: true , é claro), então não precisamos de rootDir , nem references
  • tem outDir , então você precisaria de references e / ou rootDir (e declaration: true ) para ser definido

O motivo da pergunta é que eu poderia habilitar o 'modo de projeto' para qualquer pacote TS apenas fazendo referência a ele, ou seja, ele está sob meu controle.

Nesse caso, também seria bom se ele também funcionasse assim que encontrar o arquivo .d.ts que está procurando (ou seja, não reclamará se não houver arquivos .ts ou tsconfig). Porque isso permitirá outro caso de 'substituição' de uma versão do NPM (que pode ter apenas arquivos .d.ts) por sua versão de origem, quando necessário.

Por exemplo, considere os pacotes NPM MyApp e SomeLib.
SomeLib poderia ter tsconfig: declaration: true .

Repositório como:

package.json
tsconfig.json
index.ts
sub.ts

Compilado, torna-se:

package.json
tsconfig.json
index.ts
index.d.ts
index.js
sub.ts
sub.d.ts
sub.js

Esta estrutura permite, por exemplo

// somewhere in MyApp
import something from "SomeLib/sub";

No pacote NPM publicado, atualmente sempre tenho que remover os arquivos .ts, caso contrário, todas as fontes serão recompiladas por TS se MyApp usar SomeLib:

Então, no NPM, isso se torna:

package.json
index.d.ts
index.js
sub.d.ts
sub.js

Agora, se eu colocar references: ["SomeLib"] no tsconfig do MyApp, seria bom se ele funcionasse "como está" para a versão NPM e a versão fonte de SomeLib , ou seja, não reclamará, por exemplo, de tsconfig ausente, contanto que encontre sub.d.ts no lugar certo.


Questão relacionada, mas diferente:

Agora percebo que SE o autor de SomeLib colocar references em seu tsconfig, isso permitirá publicar pacotes NPM COM os arquivos .ts, no futuro. Mas então, suponho que o TS sempre recompilaria estes quando qualquer pacote dependente não colocasse explicitamente references: ["SomeLib"] em seu tsconfig.

Ou a intenção também é que references em MyLib também irá introduzir automaticamente um 'limite de projeto' quando apenas import 'ing it (ou seja, não references ' ing it)?

IIRC, uma das idéias iniciais era que se um módulo fosse, por exemplo, localizado através de node_modules , então .d.ts arquivos teriam preferência sobre .ts arquivos, mas isso foi alterado posteriormente , porque a heurística ("por meio de node_modules ") era muito problemática em geral. Pode ser que ter um 'limite do projeto' explícito resolveria isso (por exemplo, um projectRoot: true , em vez de ou além de ter references )?

Para o caso da lerna, esperava uma solução mais simples.

  1. Nos diretórios de pacotes individuais, os pacotes não devem saber de nada a respeito da estrutura monorepo. Os arquivos tsconfig json individuais não devem conter nenhuma referência.

    • isso permite que você divida pacotes individuais em repositórios separados e simplesmente tenha o conjunto de ferramentas de repositório principal clonando-os, por exemplo, ProseMirror: https://github.com/ProseMirror/prosemirror

  2. No repositório "espaço de trabalho" raiz (que pode conter todo o código, mas também pode simplesmente clonar outros repositórios), tenha as referências em seu tsconfig.json

    • isso é feito apenas para que o conjunto de ferramentas possa reconhecer e seguir referências até a origem, em vez de entrar em arquivos .d.ts.

Minha preocupação é que, depois de adicionar uma referência usando um caminho descendente relativo "../xx" para os arquivos de configuração do projeto individual, eles não podem

Adicionar o novo conceito de "espaço de trabalho" tsconfig.json resolve esse problema. Dessa forma, se você, por exemplo, "git clone" o pacote individual, instalar suas dependências da maneira normal (por exemplo, usando npm ou yarn) deve permitir que você trabalhe nisso separadamente, já que as dependências compiladas trariam seus arquivos de definição. Se você clonar todo o espaço de trabalho e executar o comando para trazer todos os pacotes, a configuração do espaço de trabalho permitirá que você navegue por todas as fontes.

Observe que um espaço de trabalho tsconfig.json também se alinha perfeitamente com o espaço de trabalho do Yarn package.json https://yarnpkg.com/lang/en/docs/workspaces/

Eu fiz uma pequena prova de conceito aqui

https://github.com/spion/typescript-workspace-plugin

Basta adicionar o plug-in a todos os seus tsconfig.json arquivos dos repositórios individuais

{
  "plugins": [{"name": "typescript-workspace-plugin"}]
}

Em seguida, no nível superior package.json ao lado da entrada "workspaces" do yarn, adicione uma entrada "workspace-sources":

{
  "workspaces": ["packages/*"],
  "workspace-sources": {
    "*": ["packages/*/src"]
  }
}

O campo funciona exatamente como o campo "caminhos" em tsconfig.json, mas afeta apenas o serviço de linguagem dos projetos individuais, apontando-os para as fontes do pacote. Restaura a funcionalidade "ir para definição / tipo" adequada e semelhante.

Isso só é verdade quando outDir é usado, não é?

Correto. Tínhamos a hipótese de que quase todos com um grande projeto estão usando outDir . Eu estaria interessado em ouvir sobre projetos que não

Agora, se eu colocar as referências: ["SomeLib"] no tsconfig do MyApp, seria bom se ele funcionasse 'como está' tanto para a versão NPM quanto para a versão fonte de SomeLib

Grande fã, gosto muito dessa ideia. Preciso pensar se é realmente necessário ou não.

Uma ressalva aqui é que eu acho que os autores do pacote precisam a) publicar os arquivos .ts e tsconfig juntos em um lugar onde o TS os encontre, ou b) publicar nenhum e ter apenas os arquivos .d.ts acessíveis. No caso (a), seguiríamos as referências do projeto recursivamente e a coisa certa aconteceria e em (b) não iríamos navegar para os lugares errados.

Ou a intenção também é que as referências no MyLib também apresentem automaticamente um 'limite do projeto' ao apenas importá-lo (ou seja, não fazendo referência a ele)?

Falei com references nunca "vê" um arquivo .ts fora da pasta do projeto - isso inclui arquivos nos diretórios exclude d. Essa mudança sozinha faz com que o cenário lerna funcione ("trabalho" significando "referências de módulo sempre resolvem para .d.ts") fora da caixa, assim como outros.

Preciso examinar mais o modelo de "espaço de trabalho".

Isso só é verdade quando outDir é usado, não é?

Correto. Tínhamos a hipótese de que quase todo mundo com um grande projeto está usando outDir. Eu estaria interessado em ouvir sobre projetos que não

Temos 67 projetos TS na solução do Visual Studio que são compilados sem outdir e tarefas de pós-construção para criar a estrutura do diretório de saída (e uglify e outros pós-processamento).

A maioria dos projetos tem um tal tsconfig.json

 "include": [
    "../baseProj/Lib/jquery.d.ts",
    "../baseProj/baseProj.d.ts"
  ]

Levei algum tempo para ler a proposta de referências e corrigir - AFAICT lerna e os usuários do espaço de trabalho do yarn não precisam de nenhuma das funcionalidades do espaço de trabalho propostas aqui:

  1. Já temos um gráfico de dependência baseado em package.json, portanto sabemos a ordem em que executar a compilação. Na verdade, lerna tem um comando de execução genérico que pode executá-lo em ordem, e não é difícil escrever uma ferramenta que também adiciona paralelismo quando aplicável. skipLibCheck deve tornar o impacto no desempenho insignificante, mas não verifiquei.
  2. Lerna e Yarn já criam links simbólicos para os outros módulos na localização node_modules apropriada. Como resultado, todos os dependentes podem seguir para o package.json do outro módulo, ler o campo de tipos / tipificações e encontrar o arquivo de definição de tipo module.d.ts referenciado.

O que não temos, e o que o plugin que escrevi fornece, é uma maneira de carregar todos os fontes ao mesmo tempo. Quando preciso fazer alterações em dois ou mais módulos ao mesmo tempo, não quero "ir para a definição" e "ir para a definição do tipo" para me enviar para o arquivo .d.ts. Quero que ele me envie para o local do código-fonte original, para que talvez eu possa editá-lo. Caso contrário, eu apenas carregaria o diretório do projeto individual e os links simbólicos node_modules criados por lerna / yarn funcionariam.

A mesma coisa para nós. Em vez de Lerna, usamos Rush para calcular nosso gráfico de dependência, mas o efeito é o mesmo. Quando construímos projetos, tsc é apenas uma das muitas tarefas que precisam ser executadas. Nossas opções de compilador são calculadas por um sistema de construção maior e estamos mudando para um modelo em que tsconfig.json não é um arquivo de entrada, mas sim uma saída gerada (principalmente para o benefício do VS Code).

O que não temos, e o que o plugin que escrevi fornece, é uma maneira de carregar todos os fontes ao mesmo tempo. Quando preciso fazer alterações em dois ou mais módulos ao mesmo tempo, não quero "ir para a definição" e "ir para a definição do tipo" para me enviar para o arquivo .d.ts. Quero que ele me envie para o local do código-fonte original, para que talvez eu possa editá-lo.

+1 isso seria incrível.

Se estivermos sonhando com um melhor suporte a vários projetos, minha primeira solicitação seria um serviço de compilador, algo como funciona o VS Code IntelliSense. Nossas compilações seriam significativamente mais rápidas se o Rush pudesse invocar tsc 100 vezes sem ter que girar o mecanismo do compilador 100 vezes. O compilador é uma das etapas de construção mais caras. Em um monorepo, os tempos de construção são muito importantes.

@iclanton @ nickpape-msft @ qz2017

Sim por favor!

Acho que um dos resultados mais úteis do sistema de projeto seria se
'ir para a definição' foi para o arquivo de origem em vez do arquivo d.ts e
'localizar todas as referências' pesquisado na árvore de referência do projeto.

Presumivelmente, isso também desbloquearia refatorações de tipo de 'renomeação global'.

Em Qui, 9 de novembro de 2017 às 21h30 Salvatore Previti [email protected]
escreveu:

Sim por favor!

-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-343356868 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANX6d19Zz7TCd_GsP7Kzb-9XJAisG6Hks5s07VXgaJpZM4E-oPT
.

Falei com

Bom, e nesse caso, por que alguém teria que especificar quaisquer referências específicas?
Parece que seria suficiente ter uma bandeira (como projectRoot: true ).
Por exemplo, qual seria a diferença entre references: ["foo"] e apenas references: [] ?
Porque se eu import "foo" ou import "bar" , ambos irão ignorar quaisquer .ts arquivos.

Então, nesse caso, a proposta se torna:

Dado este tsconfig.json (TODO bikeshed em projectRoot ):

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "projectRoot": true
   }
}

Quando tsc precisa resolver algo fora da pasta do projeto (incluindo arquivos em diretórios excluídos), ele só vai olhar para .d.ts arquivos (alternativamente, pode apenas _prefer_ .d.ts arquivos e voltar para tsconfig e / ou .ts se ele só vê isso).

Isso torna a resolução durante a compilação rápida e simples.
Funciona para referências monorepo (ou seja, import "../foo" ) e referências baseadas em pacote (ou seja, import "foo" ).
Ele funciona para pacotes NPM e sua representação de código-fonte.
E elimina a necessidade de resolver tsconfig.json maquinaria durante a compilação, embora a mensagem de erro se não conseguir encontrar .d.ts seja menos útil.

Parece bom demais para ser verdade, se é realmente tão simples, então provavelmente estou esquecendo algo importante :)


Como outros também apontam, ainda é muito importante que o 'IntelliSense' continue trabalhando com os arquivos .ts.

Então, se uma solicitação de 'ir para a definição', 'encontrar referências' etc. for feita, ele deve usar algum mapeamento reverso para localizar o arquivo .ts arquivo .d.ts que ele usou. longe.

Esse mapeamento pode ser feito usando, por exemplo:

  • usando uma string de comentário incorporada como //# sourceURL = ../src/foo.ts no .d.ts

    • um mapa de origem mais elaborado poderia ser usado para mapear de um .d.ts 'enrolado' de volta ao .ts

  • resolvendo o arquivo .js , e usando seu mapa de origem para localizar o arquivo `.ts

Isso introduz o tópico de reconstrução de .d.ts quando aquele .ts é alterado, mas não tenho certeza se deve ser resolvido por esta proposta. Por exemplo, já é o caso hoje que precisa haver algum processo para reconstruir os .js arquivos, mesmo a partir do próprio projeto raiz. Portanto, suponho que seria seguro assumir que, se isso estiver presente, também haverá algo para reconstruir as dependências. Pode ser um monte de tsc paralelos para cada pacote, pode ser tsbuild , pode ser um IDE inteligente com comportamento semelhante a compileOnSave , etc.

@pgonzal @michaelaird https://github.com/spion/typescript-workspace-plugin faz só isso - ele restaura ir para a definição e encontrar todas as referências para a TSconfig multi-fio projecto / lerna / etc espaço de trabalho.

Interessante ... Eu me pergunto se poderíamos fazer isso funcionar com Rush. Vamos dar uma olhada.

Para sua informação, outra ferramenta simples que construímos como parte desse esforço foi wsrun

Semelhante a lerna run , ele executa um comando para todos os pacotes na área de trabalho.

Uma vez que as dependências de typecript precisam ser compiladas em ordem, wsrun é capaz de executar um comando de pacote em uma ordem topológica baseada em suas dependências package.json . Suporta paralelismo durante a construção. Não é perfeitamente paralelo, mas pode ser melhorado mais tarde.

Apenas uma nota, oao é outra ferramenta monorepo para fios. Recentemente, ele também adicionou suporte para ordenação 'topológica' de comandos.

Gostaria apenas de postar a palavra "refatoração" aqui, pois é uma meta importante para nós, embora provavelmente tangencial à proposta atual (https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-341317069) e não mencionado nesta edição com muita frequência.

Nosso caso de uso é um monorepo com vários projetos de TS, ele pode basicamente ser reduzido a common-library mais alguns aplicativos: app1 e app2 . Ao trabalhar no IDE, eles devem ser tratados como um único projeto, por exemplo, a refatoração de renomeação deve funcionar em todos os três módulos, mas app1 e app2 também são dois destinos de construção separados. Embora eu concorde que a construção geralmente é uma preocupação separada, a verdade é que nossos aplicativos são muito pequenos e fazer algo como cd app1 && tsc seria perfeitamente adequado para nós.

Se o TypeScript vier com uma boa maneira de oferecer suporte a isso, seria incrível.

Para refatoração / referências de projeto cruzado monorepo, achei esta configuração funcionando para mim se você estiver trabalhando em vscode:

root tsconfig:

"compilerOptions": {
   "baseUrl": ".",
   // global types are different per project
   "types": [],
   "paths": {
      "lib": ["packages/lib/src"],
      "xyz1": ["packages/xyz1/src"],
      "xyz2": ["packages/xyz2/src"],
   }
},
"include": ["./stub.ts"], // empty file with export {} to stop vscode complaining about no input files
"exclude": ["node_modules"]

packages / xyz1 / tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["node"],
   },
   "include": ["src/**/*"]
}

packages / xyz2 / tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["webpack-env"]
   },
  "include": ["src/**/*"]
} 

packages / lib / tsconfig.json

{
   "extends": "../../tsconfig",
    "compilerOptions": { ... },
    "include": ["src/**/*"],
    // special file to load referenced projects when inside in lib package, without it they won't be 
    // visible until you open some file in these projects 
    "files": ["./references.ts"],
}

packages / lib / tsconfig-build.json

{
   "extends": "./tsconfig",
    // exclude referenced projects when building
   "files": []
}

pacotes / lib / referências.ts

import "xyz1";
import "xyz2";

export {};

Você precisa corrigir a propriedade main no pacote package.json e pode ser types mesmo para pacotes sem lib, por exemplo:

  "main": "src/main.tsx",
  "types": "src/main.tsx",

Desta forma, refatorar / renomear algo em lib também refatorará as referências em xyz1 e xyz2 . Além disso, os projetos podem ter diferentes bibliotecas globais / de destino dessa maneira.

No Gradle, eles simplesmente o chamam de compilação composta .

A propósito, alguém pode me indicar onde começar se eu quiser contribuir para o compilador TypeScript? Eu clonei o repo e não é um negócio pequeno (eu leio a fonte: angular, iônico, expresso quando não consigo pegar nos documentos ou estou longe da internet ...) Eu realmente preciso da ajuda de um para me apontar o caminho a seguir, por favor.

Obrigado!
Futuro brilhante para TypeScript.

Eu tenho um protótipo que gostaria que as pessoas experimentassem se estiverem usando caminhos de módulo relativos. Há um repositório de amostra em https://github.com/RyanCavanaugh/project-references-demo que descreve o cenário básico e mostra como ele funciona.

Para tentar localmente:

git clone https://github.com/RyanCavanaugh/TypeScript
git checkout pr-lkg
npm install
npm run build
npm link

Em seguida, em sua outra pasta:

npm link typescript

Vou manter a tag pr-lkg apontando para o último commit de trabalho conforme as coisas mudam. A seguir em minhas tarefas:

  • [x] Adicionar melhores mensagens de erro quando projetos dependentes não são construídos
  • [] Aplique o comportamento de redirecionamento a caminhos de módulo não relativos
  • [] Expor uma API para ferramentas de construção para consumir

@RyanCavanaugh Eu realmente não posso construí-lo devido a erros como o módulo del ausente ou Error: Cannot find module 'C:\github\TypeScript\built\local\tsc.js' (talvez seu caminho local?), Mas no geral, a estrutura parece ótima.

Isso é tsc -apenas ou também levará em conta as refatorações de todo o projeto no VSCode com o servidor de linguagem?

... também está faltando a tag pr-lkg .

A tag está lá (não é um galho). Veja https://github.com/RyanCavanaugh/TypeScript/tree/pr-lkg

O references em tsconfig.json é opt-in por dependência, não faria mais sentido aplicá-lo a tudo que é resolvido fora de rootDir ?

Estou imaginando algo como uma propriedade sandbox que poderia ser semelhante a:

# tsconfig.json
{
  "compilerOptions": {
    "outDir": "lib",
    "sandbox": "."
  },
  "include": ["src/index.ts"]
}

A sandbox também definiria rootDir com o mesmo valor. Em vez de fornecer explicitamente caminhos para diretórios que contêm tsconfig.json , a resolução de módulo normal se aplicaria e você poderia pesquisar a árvore FS para encontrar tsconfig.json automaticamente.

# package.json
{
  "name": "animals",
  "module": "src",
  "typings": "lib",
  "dependencies": {
    "core": "*"
  }
}

Por aqui:

  • Não precisa manter duas listas sincronizadas ( references e dependencies ).
  • O pacote não tem conhecimento do sistema de arquivos fora do seu.
  • Use a estratégia de resolução de módulo de nó em vez de caminhos personalizados.

@RyanCavanaugh , pelo que eu entendi, só posso trabalhar localmente com essas mudanças e não poderei enviar esses projetos para teste, por exemplo, em travis-ci.org. Direito?

Notas de uma reunião hoje com @billti / @mhegazy


  • Encontrar referências / renomear deve funcionar, pelo menos para cenários onde o contexto de compilação necessário para determinar o encerramento de projetos cabe na memória
  • A renomeação de toda a solução caminhará para encontrar um arquivo de "solução", retrocederá para encontrar projetos de referência e, em seguida , carregará a
  • tsbuild precisa lidar com um modo -w
  • As soluções não podem ter subprojetos originados fora da pasta de soluções
  • Precisa de documentação clara para, por exemplo, gulp, webpack

Barra lateral: esta renomeação não funciona hoje

function f() {
  if (Math.random() > 0.5) {
    return { foo: 10 };
  } else {
    return { foo: 20 };
}
// rename foo here doesn't rename *both* instances in the function body
f().foo;

Obrigado Ryan, Mohamed e Bill. Fazer com que o cenário Find References / Rename funcione entre os projetos foi um dos principais casos de uso que tive em mente quando fiz a reclamação original sobre o TypeScript não oferecer suporte a projetos de médio porte. Projetos de médio porte são modulares, mas não grandes. A proposta e o trabalho que vi aqui até agora parecem mais um jogo de escalabilidade. Isso é superimportante para a saúde a longo prazo do TypeScript, mas é principalmente benéfico para projetos grandes, não médios. As coisas que ouço neste comentário de Ryan parecem mais com o que é necessário para melhorar a ergonomia do desenvolvimento de projetos de médio porte em TypeScript.

Como sempre, muito obrigado a toda a equipe do TypeScript por seus esforços! Você está fazendo um trabalho incrível.

Existe um truque com a área de trabalho lerna / yarn que tornará sua vida muito mais fácil.
Aponte as entradas main e types em seu package.json dos subprojetos para seu src / index. arquivo ts e o cenário Find References / Rename irá simplesmente funcionar.
E você poderá compilar todo o seu projeto com um único tsc rodando.
Você pode fazer isso para alguns de seus pacotes, pode fazer para todos. sua chamada.

Existem algumas desvantagens e armadilhas (se você tiver aumento em um pacote ou importação de qualquer símbolo global, isso poluirá todo o seu programa), mas em geral funciona muito bem.
Quando você deseja publicar para o NPM, você apenas define os principais & tipos para os valores apropriados (como parte de sua construção, ou assim)

Com a configuração acima, obtive mais ou menos todos os recursos esperados

aqui está um truque com o espaço de trabalho lerna / yarn que tornará sua vida muito mais fácil.
Aponte as entradas main e types em seu package.json dos subprojetos para seu arquivo src / index.ts e o cenário Find References / Rename simplesmente funcionará.

Em minha experiência, o problema com essa configuração é que o TypeScript começará a tratar os arquivos ts de pacotes _external_ como se fossem _sources_ do pacote que os requer, não como bibliotecas externas. Isso causa vários problemas.

  • Os pacotes externos são compilados várias vezes, cada vez usando o tsconfig do pacote _requiring_. Se os pacotes requeridos tiverem tsconfigs diferentes (por exemplo, libs diferentes), isso pode fazer com que erros de compilação falsos apareçam no pacote requerido até que ele seja compilado novamente.

  • Os pacotes requeridos também compilam mais devagar porque incluem mais arquivos do que o necessário.

  • O rootDir de todos os pacotes torna-se o diretório de nível superior, potencialmente permitindo a inclusão direta de qualquer arquivo TS de qualquer pacote, em vez de apenas incluir de index . Se os desenvolvedores não forem cuidadosos, eles podem ignorar a API do pacote necessária. Além disso, se o processo de construção não for robusto, os pacotes de requerimentos podem acabar contendo código duplicado do pacote requerido que deveria ser externo.

Em nossos projetos, descartamos a dependência de arquivos TS devido às desvantagens. Todas as dependências entre pacotes estão nos arquivos index.d.ts , então o compilador os trata como externos e está tudo

Claro, dependendo de .d.ts tem o problema de exigir uma compilação de várias etapas (não é possível out-of-the-box com ferramentas como o webpack) e o problema de má experiência de IDE (renomeações, referências que não ultrapassam os limites do pacote )

Eu concordo com alguns dos outros sentimentos - o texto digitado é um compilador, não um sistema de construção. Precisamos de nossas ferramentas para oferecer suporte a melhores construções de vários projetos. Eu sei que existem algumas opções na comunidade começando a fazer isso. Como exemplo, C # tem um compilador chamado Roslyn e uma ferramenta de compilação chamada MSBuild.

Discussão hoje com @mhegazy sobre como fazer a renomeação funcionar com o mínimo de esforço possível.

O pior caso não degenerado para renomear é assim:

// alpha.ts
const v = { a: 1 };
export function f() { return v; }
export function g() { return v; }

// alpha.d.ts (generated)
export function f(): { a: number };
export function g(): { a: number };

// beta.ts (in another project)
import { f } from '../etc/alpha';
f().a;

// gamma.ts (in yet another project)
import { g } from '../etc/alpha';
g().a;

A observação principal é que é impossível saber que renomear f().a deve renomear g().a menos que você possa ver alpha.ts para correlacionar os dois.

Um esboço do plano de implementação:

  • Tenha representações SourceFile "ricas" e "enxutas" de arquivos .d.ts na memória. Arquivos "enxutos" são lidos do disco; os "ricos" são produzidos como resultado da geração .d.ts na memória
  • "Rich" .d.ts SourceFiles tem referências de seus nós identificadores aos nomes de origem no arquivo de origem original
  • Durante a renomeação, primeiro vamos para def no símbolo em questão, como de costume. Se isso se originar em um .d.ts referenciado pelo projeto, nós o carregamos com a implementação e criamos seu arquivo .d.ts "Rich"

    • Observação: este processo é iterativo, ou seja, pode exigir o rastreamento de vários níveis até chegar a um arquivo de implementação real (um que não seja um redirecionamento .d.ts devido a uma referência do projeto)

  • Agora use os ponteiros "ricos" para descobrir quais outros identificadores no .d.ts do projeto são originados do mesmo identificador de arquivo de origem
  • Em cada projeto downstream, verifique o texto do identificador renomeado e veja se o resultado "go-to-def" é algum dos locais "iniciado com o mesmo símbolo" no arquivo .d.ts
  • Realize a renomeação no arquivo de implementação

@RyanCavanaugh irá definir / encontrar todas as referências que irão funcionar com este modelo?

Notas de uma discussão anterior com Anders e Mohamed em torno de um grande número de questões abertas

  • prepend também se aplica a .d.ts ? sim
  • O que fazemos com @internal no branch dogfood? Precisamos manter as declarações internas nos arquivos .d.ts locais, mas não queremos que apareçam nas versões de saída

    • Remover --stripInternal

    • Mas não o desative (ainda ...?)

    • Ryan escreverá ferramenta remove-internal (concluído)

    • built -> processo LKG remove as declarações @internal

  • O que acontece se você alterar um arquivo .d.ts de, por exemplo, @types ?

    • Precisa forçar manualmente uma reconstrução se você quiser ver possíveis novos erros ☹️

    • Eventualmente, poderia gravar um arquivo "arquivos que eu olhei em.txt" na pasta de saída se isso se tornar realmente problemático

  • noEmitOnError obrigatório? sim.

    • Caso contrário, as reconstruções ocultariam os erros!

  • referenceTarget -> composable ✨ 🚲 🏡 ✨
  • E quanto aos projetos de nó-folha que não desejam a emissão de declaração, mas desejam uma reconstrução rápida?

    • tsbuild ou equivalente pode verificar se está em conformidade com os requisitos não relevantes para o upstream de composable

  • Referências circulares, (como) funcionam?

    • Por padrão, não

    • Você pode especificar, por exemplo, { path: "../blah", circular: true } se quiser fazer isso

    • Se o fizer, cabe a você garantir que sua construção seja determinística e sempre alcance um ponto fixo (possivelmente não ?!)

    • O remapeamento de uma circular: a importação opcional (mas priorizado)

Miscelânea

  • @weswigham tem uma ideia alternativa para renomear que precisamos discutir com @mhegazy

Eu já perdi. Na maioria das vezes, eu só queria manter a interpretação dos mapas de origem fora do compilador (responsabilidades separadas para ferramentas separadas), mas enquanto você estava fora, trabalhei para adicioná-lo de qualquer maneira (porque o go-to def aparentemente perfeito é desejável).

@RyanCavanaugh
Deve renomear / localizar todas as referências funcionar em projetos referenciados após a fusão # 23944? Também devemos usar composite: true e projectReferences: [] no caso de serem necessários apenas serviços de linguagem (mas não tsbuild)?

Deve renomear / localizar todas as referências funcionar em projetos referenciados após a fusão # 23944?

ainda não. mas estamos trabalhando nisso a seguir.

Também devemos usar composite: true e projectReferences: [] no caso de serem necessários apenas serviços de linguagem (mas não tsbuild)?

não tenho certeza se entendi a pergunta .. o que quer dizer "serviço de linguagem" e não "construir"?

não tenho certeza se entendi a pergunta .. o que quer dizer "serviço de linguagem" e não "construir"?

Estou interessado apenas no suporte do editor (renomear / localizar todas as referências / etc ...) em vários projetos no monorepo, não na nova ferramenta de compilação (também conhecida build mode ) (# 22997) já que estou usando babel para minha compilação.

Isso deve funcionar. build é um recurso opcional, você não é obrigado a usá-lo se não quiser .. semelhante a como tsc não é necessário para sua experiência de serviço de idioma no VSCode, por exemplo.

Você provavelmente precisará construir com declarações e mapas de declaração, entretanto, para produzir os metadados necessários para que as referências de projetos cruzados funcionem.

Não tenho certeza se entendi todos os aspectos da proposta corretamente, mas seria possível não ter projetos individuais referenciando outros por caminho, mas sim por nome? O projeto do espaço de trabalho deve ter uma maneira de especificar cada caminho do projeto, semelhante aos espaços de trabalho do yarn via globs, ou talvez listando cada nome de projeto individual:

Basicamente, em vez de:

"dependencies": [
    "../common", 
    "../util"
],

Podemos por favor ter

"dependencies": [
    "common", 
    "util"
],

e ter um espaço de trabalho tsconfig.json

"workspace": {
  "common": "packages/common",
  "util": "packages/util"
}

Ou melhor ainda, sintaxe de caminhos:

"workspace": {
  "*":"packages/*"
}

Benefícios:

  • capacidade de especificar regras de pesquisa diferentes dependendo do sistema do módulo
  • capacidade de, por exemplo, fallback para node_modules quando o pacote é baixado fora do espaço de trabalho
  • capacidade de agregar livremente vários espaços de trabalho para trabalhar, clonando vários repositórios e tendo uma configuração diferente para os caminhos, para que você possa trabalhar em ainda mais pacotes em paralelo.

Ou, pelo menos, podemos ter os nomes que não sejam de caminho (aqueles que não começam com './' ou '../') reservados para uso futuro ...

Não tenho certeza do quanto isso está relacionado, mas o Yarn 1.7 introduziu um conceito de "espaços de trabalho focados" recentemente, veja esta postagem do blog .

Alguém aqui está familiarizado o suficiente com ambos os espaços de trabalho e o trabalho que @RyanCavanaugh está fazendo em torno de referências de projeto TypeScript / modo de construção para talvez deixar um comentário explicando se eles se relacionam de alguma forma? Minha intuição é que _algum lugar_ entre os espaços de trabalho do Yarn (o npm também os receberá este ano) e as futuras versões do TypeScript, encontra-se um bom suporte para monorepos com vários projetos e bibliotecas compartilhadas. (Sentimos a dor, atualmente.)

Eu realmente adoraria receber uma atualização sobre o progresso desse recurso. Estamos planejando mover Aurelia vNext para um monorepo no próximo mês ou assim. Nossa nova versão é 100% TypeScript e adoraríamos usar um sistema de projeto oficial TS em vez do Lerna, se pudermos. Também estamos felizes em ser os primeiros a adotar / testar os novos recursos :)

Suporte de núcleo e goto def usando suporte de tsc --b para suporte de compilação já está disponível e está vinculado ao TS 3.0. A base de código datilografada mudou para usá-lo. No momento, estamos testando esse suporte usando o repositório typescript.

O que ainda precisa ser feito neste ponto: 1. encontre todas as referências / renomeie para trabalhar em cenários de projetos múltiplos. 2. abordando a atualização de arquivos .d.ts em segundo plano no editor e 3. --watch suporte para cenários de multiprojetos. também muitos e muitos testes.

Este bilhete está nos livros há 3 anos. Ainda 3/6 itens pendentes?

@claudeduguay Esta é uma mudança fundamental nos projetos que o TypeScript suporta, é hora de comemorar, não acha? Estou extremamente feliz por isso!

@mhegazy Esta é uma ótima notícia. Estou muito feliz em saber que a equipe do TS também está testando o produto em seu próprio projeto. Ansioso para que as últimas coisas sejam concluídas e ter isso como uma opção para Aurelia :) Assim que houver alguma documentação sobre como configurá-lo, vamos mover nosso vNext para o novo sistema de projeto. Mal posso esperar!

@mhegazy Você pode esclarecer como tudo isso funcionaria com arquivos e projetos package.json baseados em módulos ES2015? Por exemplo, no Aurelia vNext, temos pacotes como @aurelia/kernel , @aurelia/runtime , @aurelia/jit , etc. Esses são os nomes dos módulos que serão usados ​​em import declarações ao longo dos vários projectos. Como o compilador TS entenderá que esses nomes de módulo são mapeados para as várias pastas referenciadas? Ele coletará os arquivos package.json colocados em cada pasta referenciada? Como isso será diferente do Lerna ou do Yarn Workspaces? Meu olhar inicial nos links acima me faz pensar que eu precisaria usar projetos TS (compilar) em combinação com Lerna (link de depuração e publicação) para obter uma solução de trabalho, mas não estou vendo como o TS vai construir corretamente se não puder se integrar com package.json e node_modules. O código-fonte do repositório TS é bem diferente do seu projeto Lerna comum (nada parecido com ele), então estou começando a me perguntar se ele será capaz de atender às nossas necessidades. Qualquer outra informação que você possa fornecer, e esp. uma configuração de solução de demonstração funcional semelhante à que descrevi aqui seria muito útil. Obrigado!

Eu compartilho as mesmas perguntas que @EisenbergEffect. Em particular, também espero que isso funcione bem com um monorepo gerenciado lerna .

Dois cenários monorepo a considerar

Vamos começar com uma configuração onde você criou um link simbólico com lerna:

/packages
  /a
    /node_modules
      /b -> symlink to b with package.json "types" pointing to dist/index.d.ts
  /b
    /dist
      /index.d.ts -> built output of entry point declaration file

O que queremos que aconteça aqui é reconstruir b que construímos a iff a está desatualizado. Portanto, adicionaríamos "references": [ { "path": "../b" } ] a a 's tsconfig.json e executaríamos tsc --build em a para obter compilações upstream corretas de b . Neste mundo, as referências do projeto simplesmente servem como uma representação do gráfico de construção e permitem reconstruções de incremento mais inteligentes. Idealmente, lerna e TS poderiam cooperar aqui e espelhar as dependências de package.json em tsconfig.json quando apropriado.

Outro cenário (provavelmente menos comum) seria se você não tivesse um link simbólico, mas ainda quisesse "agir como se" estivesse trabalhando em um conjunto ativo de pacotes. Você pode fazer isso hoje com um mapeamento de caminho bastante tedioso, e algumas pessoas fazem. As referências de projeto aqui ajudariam da mesma forma na ordem de construção, mas seria muito desejável ter suporte para uma propriedade no arquivo tsconfig referente para criar automaticamente um mapeamento de caminho sempre que fosse referenciado; por exemplo, se você tivesse

{
  "compilerOptions": { "outDir": "bin" },
  "package": "@RyanCavanaugh/coolstuff"
}

então, adicionar "references": [{ "path": "../cool" }] adicionaria automaticamente um mapeamento de caminho de @RyanCavanaugh/coolstuff -> ../cool/bin/ . Ainda não adicionamos isso, mas podemos dar uma olhada se for um cenário mais comum.

Idealmente, lerna e TS poderiam cooperar aqui e espelhar as dependências de package.json em tsconfig.json quando apropriado

Em vez de depender de ferramentas externas, poderíamos optar por ler seu package.json (desde que esteja ao lado de seu tsconfig) como referências potenciais se composite: true estiver definido (verifique se cada pacote resolvido tem um tsconfig.json , se houver um, considere-o um nó de projeto compilável e volte a ocorrer). Uma vez que tudo está conectado simbolicamente, não devemos nem mesmo precisar alterar a resolução (muito? Qualquer?) Para lidar com o espaço de trabalho. O Lerna não configura nenhum material específico de ts (ou específico de construção), como é o caso, ele apenas vincula tudo no lugar e gerencia o controle de versão. Isso seria uma otimização sobre o que fazemos hoje, que é carregar os arquivos ts (já que preferimos esses a declarações) e recompilar tudo independentemente de estar desatualizado.

@RyanCavanaugh Isso parece muito emocionante. Alguma ideia se funcionará com a estratégia de links simbólicos common / temp / package.json que contém o superconjunto de todas as dependências para todos os pacotes no repo. Em seguida, usamos o pnpm para realizar uma única operação de instalação para este pacote sintético. (PNPM usa links simbólicos para criar um gráfico acíclico direcionado em vez da estrutura de árvore do NPM, que elimina instâncias de biblioteca duplicadas). Em seguida, o Rush cria uma pasta node_modules para cada projeto no repo, feita de links simbólicos apontando para as pastas apropriadas em common / temp . O resultado é totalmente compatível com TypeScript e o algoritmo de resolução NodeJS padrão. É muito rápido porque há um arquivo shrinkwrap e uma equação de versionamento para todo o repo, enquanto ainda permite que cada pacote especifique suas próprias dependências.

Não colocamos nada de especial em tsconfig.json para este modelo. Se alguma configuração especial do TypeScript for necessária para o recurso de definição de goto, o ideal seria gerá-la automaticamente durante a instalação, em vez de armazená-la no Git.

@pgonzal Obrigado pelo link para o Rush! Eu não tinha visto isso ainda. Vou dar uma olhada hoje à noite.

@RyanCavanaugh Obrigado pela explicação. Seu primeiro cenário com lerna é o mais próximo do que teríamos. Aqui está nosso repositório UX com TS e lerna como um exemplo de algo que gostaríamos de usar o novo suporte de projeto em https://github.com/aurelia/ux

@weswigham Parece que o que você está descrevendo se encaixa em nosso cenário também. Repo de exemplo acima.

Apenas uma observação que, no caso de espaços de trabalho do yarn, os módulos não são vinculados simbolicamente no diretório de cada pacote individual, em vez disso, eles são vinculados ao espaço de trabalho de nível superior node_modules .

É por isso que eu acho que as referências que não começam com um ponto ('./' ou '../') devem ser reservadas para o futuro. Esperançosamente, elas acabarão sendo "referências nomeadas", tratadas por meio da estratégia de resolução do módulo ativo em vez de tratadas como caminhos relativos.

@spion usaremos apenas um nome de propriedade diferente de path para isso, se necessário (por exemplo, "references": [ { "module": "@foo/baz" } ] ); Não quero causar confusão em que "bar" e "./bar" significam a mesma coisa em files mas uma coisa diferente em references

Trabalho no documento / postagem do blog em andamento abaixo (editarei com base no feedback)

Eu encorajo qualquer pessoa que segue este tópico a tentar. Estou trabalhando no cenário monorepo agora para corrigir quaisquer últimos bugs / recursos lá e devo ter alguma orientação sobre isso em breve


Referências de Projeto

Referências de projeto são um novo recurso no TypeScript 3.0 que permite estruturar seus programas TypeScript em partes menores.

Fazendo isso, você pode melhorar muito os tempos de construção, impor a separação lógica entre os componentes e organizar seu código de maneiras novas e melhores.

Também estamos introduzindo um novo modo para tsc , o sinalizador --build , que funciona em conjunto com referências de projeto para permitir compilações TypeScript mais rápidas.

Um projeto de exemplo

Vamos examinar um programa razoavelmente normal e ver como as referências do projeto podem nos ajudar a organizá-lo melhor.
Imagine que você tenha um projeto com dois módulos, converter e units , e um arquivo de teste correspondente para cada um:

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Os arquivos de teste importam os arquivos de implementação e fazem alguns testes:

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Anteriormente, essa estrutura era um tanto estranha de se trabalhar se você usasse um único arquivo tsconfig:

  • Foi possível para os arquivos de implementação importar os arquivos de teste
  • Não foi possível construir test e src ao mesmo tempo sem ter src aparecendo no nome da pasta de saída, o que você provavelmente não quer
  • Alterar apenas os componentes internos nos arquivos de implementação exigia a verificação de tipos dos testes novamente, mesmo que isso nunca causasse novos erros
  • Alterar apenas os testes exigia a verificação do tipo de implementação novamente, mesmo que nada mudasse

Você poderia usar vários arquivos tsconfig para resolver alguns desses problemas, mas novos apareceriam:

  • Não há verificação atualizada embutida, então você acaba sempre executando tsc duas vezes
  • Invocar tsc duas vezes incorre em mais sobrecarga de tempo de inicialização
  • tsc -w não pode ser executado em vários arquivos de configuração ao mesmo tempo

As referências do projeto podem resolver todos esses problemas e muito mais.

O que é uma referência de projeto?

tsconfig.json arquivos têm uma nova propriedade de nível superior, references . É uma matriz de objetos que especifica projetos para fazer referência:

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

A propriedade path de cada referência pode apontar para um diretório contendo um arquivo tsconfig.json ou para o próprio arquivo de configuração (que pode ter qualquer nome).

Quando você faz referência a um projeto, coisas novas acontecem:

  • Importar módulos de um projeto referenciado carregará seu arquivo de declaração de saída ( .d.ts )
  • Se o projeto referenciado produzir um outFile , as declarações do arquivo de saída .d.ts serão visíveis neste projeto
  • O modo Build (veja abaixo) irá construir automaticamente o projeto referenciado, se necessário

Ao separar em vários projetos, você pode melhorar muito a velocidade de verificação de tipos e compilação, reduzir o uso de memória ao usar um editor e melhorar a aplicação dos agrupamentos lógicos de seu programa.

composite

Projetos referenciados devem ter a nova configuração composite habilitada.
Essa configuração é necessária para garantir que o TypeScript possa determinar rapidamente onde encontrar as saídas do projeto referenciado.
Ativar a sinalização composite muda algumas coisas:

  • A configuração rootDir , se não for definida explicitamente, assume como padrão o diretório que contém o arquivo tsconfig
  • Todos os arquivos de implementação devem ser correspondidos por um padrão include ou listados no array files . Se esta restrição for violada, tsc informará quais arquivos não foram especificados
  • declaration deve ser ativado

declarationMaps

Também adicionamos suporte para mapas de origem de declaração .
Se você habilitar --declarationMap , poderá usar recursos do editor como "Ir para a definição" e Renomear para navegar e editar o código de forma transparente através dos limites do projeto em editores compatíveis.

prepend com outFile

Você também pode ativar o prefixo da saída de uma dependência usando a opção prepend em uma referência:

   "references": [
       { "path": "../utils", "prepend": true }
   ]

Anexar um projeto incluirá a saída do projeto acima da saída do projeto atual.
Isso funciona para .js files e .d.ts files, e os arquivos de mapa de origem também serão emitidos corretamente.

tsc só usará arquivos existentes no disco para fazer este processo, então é possível criar um projeto onde um arquivo de saída correto não pode ser gerado porque a saída de algum projeto estaria presente mais de uma vez no arquivo resultante .
Por exemplo:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

É importante nesta situação não preceder cada referência, porque você acabará com duas cópias de A na saída de D - isso pode levar a resultados inesperados.

Advertências para referências do projeto

As referências de projetos têm algumas desvantagens que você deve conhecer.

Como os projetos dependentes usam arquivos .d.ts que são construídos a partir de suas dependências, você terá que verificar certas saídas de compilação ou construir um projeto depois de cloná-lo antes de poder navegar no projeto em um editor sem ver espúrios erros.
Estamos trabalhando em um processo de geração de .d.ts nos bastidores que deve ser capaz de mitigar isso, mas por enquanto recomendamos informar aos desenvolvedores que eles devem construir após a clonagem.

Além disso, para preservar a compatibilidade com fluxos de trabalho de construção existentes, tsc não construirá dependências automaticamente, a menos que seja invocado com a opção --build .
Vamos aprender mais sobre --build .

Modo de construção para TypeScript

Um recurso muito aguardado são as compilações incrementais inteligentes para projetos TypeScript.
No 3.0, você pode usar a bandeira --build com tsc .
Este é efetivamente um novo ponto de entrada para tsc que se comporta mais como um orquestrador de build do que um simples compilador.

Executar tsc --build ( tsc -b para breve) fará o seguinte:

  • Encontre todos os projetos referenciados
  • Detecte se eles estão atualizados
  • Crie projetos desatualizados na ordem correta

Você pode fornecer tsc -b com vários caminhos de arquivo de configuração (por exemplo, tsc -b src test ).
Assim como tsc -p , especificar o próprio nome do arquivo de configuração é desnecessário se ele for tsconfig.json .

tsc -b Linha de comando

Você pode especificar qualquer número de arquivos de configuração:

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

Não se preocupe em ordenar os arquivos que você passar na linha de comando - tsc irá reordená-los se necessário para que as dependências sejam sempre criadas primeiro.

Existem também alguns sinalizadores específicos para tsc -b :

  • --verbose : Imprime um registro detalhado para explicar o que está acontecendo (pode ser combinado com qualquer outro sinalizador)
  • --dry : Mostra o que seria feito, mas não constrói nada
  • --clean : Exclui as saídas dos projetos especificados (podem ser combinados com --dry )
  • --force : Aja como se todos os projetos estivessem desatualizados
  • --watch : modo de observação (não pode ser combinado com qualquer sinalizador, exceto --verbose )

Ressalvas

Normalmente, tsc produzirá saídas ( .js e .d.ts ) na presença de sintaxe ou erros de tipo, a menos que noEmitOnError esteja ativado.
Fazer isso em um sistema de compilação incremental seria muito ruim - se uma de suas dependências desatualizadas tivesse um novo erro, você só o veria uma vez porque uma compilação subsequente ignoraria a compilação do projeto agora atualizado.
Por esta razão, tsc -b efetivamente age como se noEmitOnError estivesse habilitado para todos os projetos.

Se você verificar qualquer saída de compilação ( .js , .d.ts , .d.ts.map , etc.), pode ser necessário executar uma compilação --force após determinado controle de origem operações dependendo se sua ferramenta de controle de origem preserva mapas de tempo entre a cópia local e a cópia remota.

msbuild

Se você tiver um projeto msbuild, pode ativar o modo de construção adicionando

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

ao seu arquivo proj. Isso habilitará a construção incremental automática, bem como a limpeza.

Observe que, como acontece com tsconfig.json / -p , as propriedades existentes do projeto TypeScript não serão respeitadas - todas as configurações devem ser gerenciadas usando seu arquivo tsconfig.

Algumas equipes configuraram fluxos de trabalho baseados em msbuild, nos quais os arquivos tsconfig têm a mesma ordem de gráfico implícita que os projetos gerenciados com os quais estão emparelhados.
Se a sua solução for assim, você pode continuar a usar msbuild com tsc -p junto com as referências do projeto; estes são totalmente interoperáveis.

Orientação

Estrutura geral

Com mais tsconfig.json arquivos, você geralmente vai querer usar a herança do arquivo de configuração para centralizar as opções comuns do compilador.
Dessa forma, você pode alterar uma configuração em um arquivo em vez de ter que editar vários arquivos.

Outra boa prática é ter um arquivo de "solução" tsconfig.json que simplesmente tenha references para todos os seus projetos de nó-folha.
Isso apresenta um ponto de entrada simples; por exemplo, no repositório TypeScript, simplesmente executamos tsc -b src para construir todos os endpoints porque listamos todos os subprojetos em src/tsconfig.json
Observe que, começando com 3.0, não é mais um erro ter um array files vazio se você tiver pelo menos um reference em um arquivo tsconfig.json .

Você pode ver esses padrões no repositório TypeScript - consulte src/tsconfig_base.json , src/tsconfig.json e src/tsc/tsconfig.json como exemplos-chave.

Estruturação para módulos relativos

Em geral, não é necessário muito para fazer a transição de um repo usando módulos relativos.
Simplesmente coloque um arquivo tsconfig.json em cada subdiretório de uma determinada pasta pai e adicione reference s a esses arquivos de configuração para corresponder à camada pretendida do programa.
Você precisará definir outDir como uma subpasta explícita da pasta de saída ou definir rootDir como a raiz comum de todas as pastas do projeto.

Estruturação para outFiles

O layout para compilações usando outFile é mais flexível porque os caminhos relativos não importam tanto.
Uma coisa a ter em mente é que geralmente você não deseja usar prepend até o "último" projeto - isso melhorará os tempos de construção e reduzirá a quantidade de E / S necessária em qualquer construção.
O repositório TypeScript em si é uma boa referência aqui - temos alguns projetos de "biblioteca" e alguns projetos de "endpoint"; Os projetos "endpoint" são mantidos tão pequenos quanto possível e obtêm apenas as bibliotecas de que precisam.

Estruturação para monorepos

TODO: Experimente mais e descubra isso. Rush e Lerna parecem ter modelos diferentes que implicam coisas diferentes do nosso lado

Também estou procurando feedback sobre # 25164

@RyanCavanaugh Muito bom artigo, e o excelente recurso, seria muito bom experimentá-lo, esp. depois de passar dias organizando nosso grande projeto em subprojetos com referências de arquivo de configuração.

Tenho algumas notas:

  1. O que é um monorepo? Seria bom descrever este caso de uso um pouco mais.
  2. Na maioria dos casos (especialmente para grandes projetos), existem muitos artefatos adicionais gerados durante a construção. No nosso caso, são CSS, HTML, arquivos de imagem, etc, via gulp. Eu me pergunto como o uso de tais ferramentas de construção se adaptaria a essa nova maneira de fazer as coisas. Digamos que eu gostaria de executar o relógio não apenas em arquivos * .ts, mas em todos os nossos outros arquivos (estilos, marcação, etc.). Como fazer isso? Precisa ser executado, digamos gulp watch e tsc -b -w em paralelo?

@vvs um monorepo é uma coleção de pacotes NPM geralmente gerenciados por uma ferramenta como Rush ou Lerna

Se você estiver usando o gulp, convém usar um carregador que entende as referências do projeto nativamente para obter a melhor experiência. @rbuckton fez algum trabalho aqui, pois alguns desenvolvedores usam um gulpfile internamente; talvez ele possa avaliar como são os bons padrões lá

@RyanCavanaugh Isso está parecendo bom. Estou muito interessado na orientação do Lerna :)

@RyanCavanaugh, isso parece ótimo. No momento, estou trabalhando em testá-lo com nossa lerna monorepo.

A única coisa pouco clara para mim em sua escrita foi a opção prepend . Não entendi muito bem qual é o problema que ele está abordando, em que situação você gostaria de usá-lo e o que acontece se você não usá-lo.

Isso é incrível! Eu trabalho em ts-loader e projetos relacionados. É provável que as alterações sejam necessárias para dar suporte a isso em projetos que usam LanguageServiceHost / WatchHost /

(Veja https://github.com/TypeStrong/ts-loader/blob/master/src/servicesHost.ts para um exemplo do que quero dizer.)

Em caso afirmativo, todas as orientações / PRs são recebidos com gratidão! Na verdade, se você quiser que isso seja testado no mundo do webpack, eu ficaria feliz em ajudar a lançar uma versão do ts-loader que suporte isso.

Claro, se "simplesmente funcionar", é ainda melhor: sorria:

Ótimo trabalho!

@yortus @EisenbergEffect Eu configurei um repositório lerna de amostra em https://github.com/RyanCavanaugh/learn-a com um README descrevendo as etapas que executei para fazê-lo funcionar.

Se estou entendendo corretamente, tsc -b X não fará nada se tudo (X e todas as suas dependências e dependências transitivas) estiver atualizado? Quer saber se isso é algo que poderíamos obter mesmo sem o sinalizador -b para projetos individuais sem referências? (menos dependências nesse caso, é claro)

Isso é bem legal. Eu tendo a usar um Lerna com uma configuração como esta (para separar o repo mono por função). Presumo que funcionaria muito bem.

{
"lerna": "2.11.0",
"pacotes": [
"pacotes / componentes / ","pacotes / bibliotecas / ",
"pacotes / frameworks / ","pacotes / aplicativos / ",
"pacotes / ferramentas / *"
],
"versão": "0.0.0"
}

Então, isso está disponível em typescript@next ?

Vou testar isso com nosso repositório de espaço de trabalho de fios. Temos que usar nohoist para alguns módulos que ainda não suportam espaços de trabalho, então será bom ver como isso funciona.

@RyanCavanaugh Eu fiz o repo para um teste esta noite. Abri um problema no repo para relatar alguns problemas que tive. Obrigado novamente por colocar isso junto. Estou ansioso para usá-lo em breve.

Muito interessante! Atualmente, na minha empresa, usamos minha própria ferramenta chamada mtsc para oferecer suporte ao modo de observação de vários projetos ao mesmo tempo. Temos cerca de 5 projetos que precisam ser compilados e assistidos no mesmo repo.

Os projetos têm configurações diferentes, como; Direcionamento ECMA (es5, es6), tipos (nó, jest, DOM etc), emit (alguns usam webpack e alguns compilam para si próprios). Todos eles compartilham uma coisa, que é o plugin tslint , o resto pode ser diferente. Minha ferramenta também executa o tslint após a compilação do projeto (por projeto e é interrompida se um projeto for recompilado antes da conclusão do tslint).

Minha principal preocupação com a proposta atual é que você não pode dizer quais projetos compartilham quais recursos. Temos um projeto servidor e um cliente, que usam uma pasta de utilitários especial, mas não queremos ver os erros de compilação de duas vezes. Mas isso pode ser corrigido com um filtro, então não é grande coisa :)

Experimentei o novo modo --build com nosso lerna monorepo, que atualmente consiste em 17 pacotes interdependentes. Demorou um pouco para fazer tudo funcionar, mas agora tudo funciona, e poder construir de forma incremental é uma grande melhoria para nós.

Encontrei alguns problemas que descrevo abaixo. Espero que este seja um feedback útil para a equipe de TS e possa ajudar outras pessoas a fazer com que o modo --build funcione em seus projetos.

Feedback para o modo tsc --build

1. Espúrio "\está desatualizado porque o arquivo de saída '2.map' não existe "

Recebi esta mensagem para cada pacote em cada construção, então cada construção se tornou uma reconstrução completa, mesmo quando nada foi alterado. Percebi que @RyanCavanaugh já corrigiu isso no # 25281, então não será mais um problema se você atualizar para 20180628 ou mais tarde todas as noites. Os problemas a seguir pressupõem que você atualizou para pelo menos 20180628 todas as noites.

2. "\está atualizado com os arquivos .d.ts de suas dependências "quando não está

EDIT: relatado em # 25337.

Para reproduzir este problema, configure o repositório de learn-a @RyanCavanaugh de acordo com suas instruções . Execute tsc -b packages --verbose para ver que tudo é construído pela primeira vez. Agora mude a linha 1 em pkg1/src/index.ts para import {} from "./foo"; e salve. Execute tsc -b packages --verbose novamente. A compilação de pkg2 é ignorada, embora pkg1 sido alterado de uma forma que quebra o código-fonte de pkg2 . Agora você pode ver um rabisco vermelho em pkg2/src/index.ts . Construa novamente com tsc -b packages --force e o erro de construção é mostrado. Os problemas a seguir pressupõem a construção com --force para contornar isso.

3. Gerou .d.ts arquivos causando erros de compilação de 'Identificador duplicado' em pacotes downstream

EDITAR: relatado em # 25338.

Para reproduzir este problema, configure o repositório de learn-a @RyanCavanaugh de acordo com suas instruções . Agora execute lerna add @types/node para adicionar tipificações Node.js a todos os três pacotes. Execute tsc -b packages --force para confirmar que ainda está funcionando corretamente. Agora adicione o seguinte código a pkg1/src/index.ts :

// CASE1 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// export const bar = () => parse('bar');

// CASE2 - no build errors in pkg1 or in downstream packages
// import {parse, UrlWithStringQuery} from 'url';
// export const bar = (): UrlWithStringQuery => parse('bar');

// CASE3 - no build errors in pkg1 or in downstream packages
// export declare const bar: () => import("url").UrlWithStringQuery;

// CASE4 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// type UrlWithStringQuery = import("url").UrlWithStringQuery;
// export const bar = (): UrlWithStringQuery => parse('bar');

Remova um caso de cada vez e execute tsc -b packages --force . Os casos 1 e 4 causam um dilúvio de erros de compilação em pkg2 . Com os casos 2 e 3, não há erros de compilação. A diferença importante com os casos 1 e 4 parece ser a primeira linha no pkg1/lib/index.d.ts gerado:

/// <reference path="../node_modules/@types/node/index.d.ts" />

Os casos 2 e 3 não geram esta linha. Quando pkg2 é construído nos casos 1 e 4, ele inclui duas cópias idênticas de @types/node declarações em caminhos diferentes, e isso causa os erros de 'identificador duplicado'.

Talvez seja por design, uma vez que os casos 2 e 3 funcionam. No entanto, parece muito confuso. Não há erros de compilação ou avisos em pkg1 para qualquer um desses 4 casos, mas o comportamento de compilação downstream é muito sensível ao estilo exato das declarações exportadas. Acho que (a) pkg1 deve errar para os casos 1 e 4, ou (b) todos os quatro casos devem ter o mesmo comportamento de compilação downstream, ou (c) deve haver alguma orientação clara da equipe de TS sobre como escrever declarações para evitar este problema.

4. Problemas com import tipos em gerados .d.ts arquivos quando usando espaços de trabalho de fios

Ao tentar fazer o modo de compilação funcionar com nosso monorepo de 17 pacotes, trabalhei em uma série de erros de compilação causados ​​pelos caminhos relativos em import tipos em .d.ts arquivos gerados. Finalmente descobri que o problema estava relacionado ao içamento do módulo. Ou seja, ao usar espaços de trabalho do yarn, todos os módulos instalados são içados para o diretório monorepo-root node_modules , incluindo os links simbólicos para todos os pacotes no monorepo. Mudei o monorepo para usar a propriedade packages em lerna.json , o que faz com que lerna use seu próprio algoritmo de bootstrapping sem içamento, e isso resolveu o problema. É uma abordagem melhor / mais segura de qualquer maneira, embora mais lenta.

Não tenho certeza se o TS pretende oferecer suporte a configurações içadas por módulo, então não desenvolvi uma reprodução para os problemas que encontrei, mas poderia tentar fazer uma se houver interesse. Acho que o problema pode ser que algumas compilações estão obtendo o mesmo tipo por meio do diretório packages de nível superior (de acordo com as configurações do tsconfig) e do diretório de nível superior node_modules (de acordo com import tipos em .d.ts arquivos gerados). Isso às vezes funciona devido à tipagem estrutural, mas falha para coisas como símbolos exclusivos exportados.

5. Configuração repetitiva

Configurar um monorepo para usar lerna basicamente requer apenas colocar algo como "packages": ["packages/*"] em lerna.json . Lerna calcula a lista exata de pacotes expandindo globstars, e então calcula o gráfico de dependências exato olhando as dependências declaradas em package.json cada pacote. Você pode adicionar e remover pacotes e dependências à vontade, e lerna continua sem a necessidade de alterar sua configuração.

O modo TypeScript --build envolve um pouco mais de cerimônia. Os padrões Glob não são reconhecidos, então todos os pacotes devem ser listados e mantidos explicitamente (por exemplo, em packages/tsconfig.json ) no repositório de learn-a @RyanCavanaugh. O modo de compilação não olha para as dependências de package.json , então cada pacote deve manter a lista de outros pacotes dos quais depende em seu arquivo package.json (em "dependencies" ), bem como é o arquivo tsconfig.json (em "references" ).

Este é um pequeno inconveniente, mas eu o incluo aqui, pois achei a besteira perceptível em comparação com a abordagem lerna's .

6. tsc falha com aumentos do módulo global

EDITAR: relatado em # 25339.

Para reproduzir este problema, configure o repositório de learn-a @RyanCavanaugh de acordo com suas instruções . Agora execute lerna add @types/multer para adicionar multer tipificações a todos os três pacotes. Execute tsc -b packages --force para confirmar que ainda está funcionando corretamente. Agora adicione a seguinte linha a pkg1/src/index.ts :

export {Options} from 'multer';

Execute tsc -b packages --force novamente. O compilador falha devido a uma declaração violada. Eu olhei rapidamente para o rastreamento de pilha e asserção, e parece ter algo a ver com o aumento global do namespace Express .

obrigado @yortus pelo feedback. confiar aprecio isso. para 3, acho que é https://github.com/Microsoft/TypeScript/issues/25278.

Para 4, não estou familiarizado com o conceito de içamento de módulo. você pode elaborar e / ou compartilhar uma reprodução?

@mhegazy muitos que usam lerna e yarn usam espaços de trabalho (inclusive eu). Mais informações aqui: https://yarnpkg.com/lang/en/docs/workspaces/

No momento, estou usando espaços de trabalho do yarn, lerna, tsconfigs estendidos, onde o tsconfig básico declara paths compartilhado para todos os pacotes com módulo içado encontrado em root/node_modules . Quando ouço yarn e monorepo , penso workspaces porque essa é a própria intenção do recurso - facilitar o uso e reduzir a duplicação. Eu esperava que essa mudança simplesmente removeria meu longo / doloroso paths declarado em meu tsconfig base.

Aqui está um exemplo de nosso monorepo tsconfig raiz (se for de alguma ajuda):

{
  "extends": "./packages/build/tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": "./packages",
    "paths": {
      "@alienfast/build/*": ["./build/src/*"],
      "@alienfast/common-node/*": ["./common-node/src/*"],
      "@alienfast/common/*": ["./common/src/*"],
      "@alienfast/concepts/*": ["./concepts/src/*"],
      "@alienfast/faas/*": ["./faas/src/*"],
      "@alienfast/math/*": ["./math/src/*"],
      "@alienfast/notifications/*": ["./notifications/src/*"],
      "@alienfast/ui/*": ["./ui/src/*"],
      "@alienfast/build": ["./build/src"],
      "@alienfast/common-node": ["./common-node/src"],
      "@alienfast/common": ["./common/src"],
      "@alienfast/concepts": ["./concepts/src"],
      "@alienfast/faas": ["./faas/src"],
      "@alienfast/math": ["./math/src"],
      "@alienfast/notifications": ["./notifications/src"],
      "@alienfast/ui": ["./ui/src"],
    }
  },
  "include": ["./typings/**/*", "./packages/*/src/**/*"],
  "exclude": ["node_modules", "./packages/*/node_modules"]
}

Vou tentar bifurcar para uma amostra:
https://github.com/RyanCavanaugh/learn-a

Aqui está um PR que não pode ser mesclado com o @RyanCavanaugh com espaços de trabalho do yarn:
https://github.com/RyanCavanaugh/learn-a/pull/3/files

Também usamos o içamento de módulo em Jupyterlab , com lerna e fio. Isso nos permite basicamente compartilhar nossas dependências instaladas entre todos os nossos pacotes, para que eles existam apenas uma vez no sistema de arquivos, no projeto raiz.

Eu entendo que os espaços de trabalho são um pouco mais limpos do que ter que usar o comando link entre todos os pacotes para que eles possam acessar uns aos outros (ou pelo menos acessar suas dependências).

Como acima, o levantamento de módulo move todas as dependências para um diretório raiz node_modules . Isso tira vantagem do fato de que a resolução do módulo do nó sempre percorrerá a árvore de diretórios e pesquisará todos os diretórios node_modules até encontrar o módulo necessário. Os módulos individuais em seu monorepo são, então, linkados simbolicamente nesta raiz node_modules e tudo funciona. A postagem no blog do Yarn provavelmente explica isso melhor do que eu.

Não é garantido que ocorra o içamento. Se você tiver versões incompatíveis do mesmo pacote, elas não serão içadas. Além disso, muitas ferramentas existentes não suportam içamento porque fazem suposições sobre onde node_modules estará ou não seguem corretamente a resolução do módulo de nó. Por causa disso, há uma configuração nohoist que pode desabilitar o içamento para módulos ou dependências específicas.

Eu adicionei um sexto item ao meu feedback anterior . tsc falha no cenário descrito lá.

@mhegazy Não tenho certeza se o item 3 está relacionado a # 25278. # 25278 descreve a emissão de declaração inválida. Meus arquivos de declaração gerados eram sintaticamente e semanticamente válidos, mas fizeram com que os projetos downstream fossem construídos com duas cópias de tipificações de nós, resultando em erros de 'identificador duplicado'.

Como acima, o levantamento de módulo move todas as dependências para um diretório root node_modules. Isso tira vantagem do fato de que a resolução do módulo de nó sempre percorrerá a árvore de diretórios e pesquisará todos os diretórios node_modules até encontrar o módulo necessário.

A propósito, há uma desvantagem neste modelo, que leva a "dependências fantasmas", onde um projeto pode importar uma dependência que não foi declarada explicitamente em seu arquivo package.json. Quando você publica sua biblioteca, isso pode causar problemas como (1) uma versão diferente da dependência sendo instalada do que a que foi testada / esperada ou (2) a dependência completamente ausente porque foi retirada de um projeto não relacionado que não está instalado nesse contexto. O PNPM e o Rush têm opções de arquitetura destinadas a proteger contra dependências fantasmas.

Tenho uma pergunta geral sobre tsc --build : o compilador TypeScript está tentando assumir a função de orquestrar a construção de projetos em um monorepo? Normalmente, o conjunto de ferramentas terá todo um pipeline de tarefas, coisas como:

  • pré-processando
  • gerando strings localizadas
  • conversão de ativos para JavaScript (css, imagens, etc)
  • compilar (verificação de tipo / transpilação)
  • enrolando os arquivos .js (por exemplo, webpack)
  • acumulando os arquivos .d.ts (por exemplo, API Extractor)
  • pós-processamento, incluindo testes de unidade e geração de documentação

Normalmente, um sistema como Gulp ou Webpack gerencia esse pipeline, e o compilador é apenas uma etapa no meio da cadeia. Às vezes, uma ferramenta secundária também executa a compilação de outra maneira, por exemplo, Jest + ts-jest por jest --watch .

O objetivo de tsc gerenciar essas coisas sozinho? E se não, existe uma maneira de um orquestrador de construção convencional resolver o próprio gráfico de dependência e, por exemplo, chamar repetidamente o tsc em cada pasta do projeto na ordem certa (após o pré-processamento ter sido atualizado)?

Ou, se o design é para processar um monorepo inteiro em uma única passagem (considerando que hoje construímos cada projeto em um processo NodeJs separado), também estou curioso para saber como as outras tarefas de construção participarão: Por exemplo, vamos executar o webpack em todos os projetos de uma vez? (No passado, isso levava a problemas de falta de memória.) Vamos perder a capacidade de explorar a simultaneidade de vários processos?

Estas não são críticas BTW. Estou apenas tentando entender o quadro geral e o uso pretendido.

@pgonzal certo, existem muitas partes não-tsc para construir um monorepo do mundo real. Para a nossa lerna monorepo, optei pela seguinte abordagem:

  • cada pacote no monorepo define opcionalmente um prebuild script e / ou um postbuild script em seu package.json . Eles contêm os aspectos não-tsc da construção.
  • no package.json do monorepo, existem estes scripts:
    "prebuild": "lerna run prebuild", "build": "tsc --build monorepo.tsconfig.json --verbose", "postbuild": "lerna run postbuild",
  • É isso. Executar yarn build no nível do monorepo executa os scripts prebuild para cada pacote que os define, depois executa a etapa tsc --build , em seguida, executa todos os postbuild scripts. (Por convenção em npm e yarn, executar npm run foo é quase o mesmo que npm run prefoo && npm run foo && npm run postfoo .)

Como você lida com jest --watch ou webpack-dev-server ? Por exemplo, quando um arquivo de origem é modificado, as etapas de pré-construção / pós-construção são executadas novamente?

Isso tem alguma implicação em ts-node e fluxos de trabalho relacionados? Alguns de nossos aplicativos auxiliares são executados "diretamente" no TypeScript, como "start": "ts-node ./src/app.ts" ou "start:debug": "node -r ts-node/register --inspect-brk ./src/app.ts" .

Relatou outro problema com o modo de compilação em # 25355.

Obrigado por todos os ótimos comentários e investigações até agora. Agradeço muito a todos que dedicaram seu tempo para experimentar e chutar os pneus.

@yortus re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520

Ótimo artigo, obrigado novamente por fornecer isso. Seus problemas em ordem -

  1. Fixo
  2. PR até # 25370
  3. Discutindo o problema registrado - não é imediatamente aparente qual é a correção certa, mas faremos algo
  4. Investigando (abaixo)
  5. Logged # 25376
  6. Tecnicamente não relacionado a --build AFAICT. Esta é uma nova afirmação que adicionamos recentemente; Nathan está investigando

@rosskevin 🎉 pelo PR no repo learn-a ! Vou mesclar isso em um branch para que possamos comparar e contrastar melhor.

@pgonzal re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -401577442

Tenho uma pergunta geral sobre tsc --build: O compilador TypeScript está tentando assumir a função de orquestrar a construção de projetos em um monorepo?

Ótima pergunta; Quero responder a isso com muita clareza: definitivamente não .

Se você está feliz hoje usando tsc para construir seu projeto, queremos que seja feliz amanhã usando tsc -b para construir seu projeto de várias partes. Se você está feliz hoje usando gulp para construir seu projeto, queremos que seja feliz amanhã usando gulp para construir seu projeto de várias partes. Temos controle sobre o primeiro cenário, mas precisamos de autores de ferramentas e plugins para nos ajudar com o segundo, e é por isso que mesmo tsc -b é apenas um invólucro fino sobre APIs expostas que os autores de ferramentas podem usar para ajudar as referências do projeto a funcionar bem em seus modelos de construção.

O contexto mais amplo é que houve um debate interno bastante robusto sobre se tsc -b deveria mesmo existir ou, em vez disso, ser uma ferramenta / ponto de entrada separado - construir um orquestrador de construção de uso geral é uma tarefa enorme, e não aquela que somos inscrevendo-se. Para nosso próprio repo, usamos tsc com uma estrutura de execução de tarefa leve e agora usamos tsc -b com o mesmo executor de tarefa, e eu esperaria que qualquer outra pessoa que esteja migrando também mantenha sua cadeia de criação existente no lugar com apenas pequenos ajustes.

@borekb re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -401593804

Isso tem alguma implicação no nó ts e fluxos de trabalho relacionados? Alguns de nossos aplicativos auxiliares são executados "diretamente" no TypeScript

Para scripts de arquivo único, que implicitamente não podem ter referências de projeto, o impacto é zero.

@EisenbergEffect tinha algumas perguntas no repo learn-a sobre renomeação entre projetos e outros recursos de serviço de linguagem. A grande questão em aberto aqui é se seremos capazes de colocar esse recurso em um estado utilizável para 3.0 ou não. Nesse caso, a renomeação entre projetos "simplesmente funcionará", com a ressalva de que é obviamente impossível para nós encontrar de forma conclusiva todos os projetos downstream e atualizá-los - este será um "melhor esforço" com base em algumas heurísticas para procurar outros projetos.

Se não acharmos que a renomeação entre projetos é aceitavelmente estável + completa para 3.0, provavelmente bloquearemos as operações de renomeação apenas quando o símbolo renomeado estiver no arquivo de saída .d.ts de outro projeto - permitindo que você faça isso seria muito confuso porque o arquivo .d.ts seria atualizado em uma compilação subsequente do projeto upstream após o projeto upstream ter sido modificado, o que significa que pode facilmente levar dias entre quando você faz uma renomeação local e quando você percebe que o código de declaração não foi realmente atualizado.

Para recursos como Go to Definition, eles estão funcionando hoje no VS Code e funcionarão imediatamente em versões futuras do Visual Studio. Todos esses recursos exigem que os arquivos .d.ts.map sejam ativados (ligue declarationMap ). Há algum trabalho por recurso para iluminar isso, então se você notar que algo não está funcionando como esperado, registre um bug, pois podemos ter perdido alguns casos.

Problemas abertos que estou rastreando neste momento:

  • Renomeação entre projetos - @ andy-ms está implementando
  • Preciso analisar as configurações do módulo içado e entender suas implicações - em mim
  • Deve haver uma versão da amostra learn-a repo que usa yarn , e outra que usa pnpm , e outra que usa um daqueles no modo içado

Perguntas abertas

  • Devemos implementar a lógica para ler package.json s para inferir as referências do projeto? Logged # 25376
  • Deve .d.ts.map emitir estar implicitamente ativado para composite projetos?

@RyanCavanaugh para adicionar a

Problemas abertos que estou rastreando neste momento

Também mencionamos ter um cache de saída incremental, separado do local de saída do projeto real, para lidar com coisas como atualizar declarações em segundo plano no LS (hoje, as alterações não se propagam pelos limites do projeto no editor até que você construa), stripInternal e processos de construção em mutação (onde nossas saídas de construção são alteradas no local e, portanto, não são adequadas para operações LS).

desculpe por uma pergunta idiota, uma vez que é verificado no roteiro, como faço para habilitar esse recurso?

@ aleksey-bykov você pode usá-lo no texto datilografado @ a seguir.

Acabei de experimentar isso em nosso monorepo alimentado por área de trabalho de fios e funciona bem.

Uma coisa que notei foi que tsc --build --watch relata erros, mas não mostra nada que diga que a compilação foi corrigida. O modo de observação tsc padrão em 2.9 começou a dar uma contagem de erros e é bom ver um zero ali para que você saiba que a construção foi concluída.

tenho uma pasta cheia de * .d.ts e nada mais do que devo fazer sobre isso:

  • torná-lo um projeto e referenciá-lo (tentei, não funcionou)
  • use "incluir" para isso

@timfish esse feedback corresponde a algum outro que ouvi; registrado # 25562

@ aleksey-bykov https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520 deve ajudar a explicar alguns conceitos

@RyanCavanaugh parece que a referência de projeto só funciona para commonjs e resolução de módulo de nó, não é?

no seu exemplo:

import * as p1 from "@ryancavanaugh/pkg1";
import * as p2 from "@ryancavanaugh/pkg2";

p1.fn();
p2.fn4();
  1. o que é o módulo @ryancavanaugh , isso tem algo a ver com a forma como o TS resolve os módulos?
  2. este exemplo deveria funcionar com AMD (resolução de módulo clássico)?
  3. é outFile necessário para que as definições sejam encontradas?
  4. onde os arquivos d.ts devem estar para fazer referência ao projeto para encontrá-los (ainda posso usar outDir? O TS os encontrará lá?)

eu tenho 2 projetos simples essentials e common e coisas em comum não podem resolver coisas compiladas no essencial:

image

@ aleksey-bykov

  1. É apenas um nome de módulo com escopo, resolvido sob o algoritmo de resolução de módulo de nó usual
  2. Você pode usar referências de projeto com qualquer sistema de módulo, incluindo clássico, mas os nomes de exemplo (módulos com escopo) não são muito amigáveis ​​para usar fora do nó
  3. Não
  4. O TypeScript procura os arquivos .d.ts no local onde o projeto referenciado os constrói

Se você tiver um repositório de amostra ou algo assim, posso diagnosticar por que você está recebendo esse erro

@RyanCavanaugh por favor faça
exemplo.zip

@RyanCavanaugh , também parece que tsc --build --watch não exibe nenhum arquivo inicialmente até que veja uma modificação em um arquivo de origem.

Tópico muito longo (no tempo e no espaço); vamos retomar a discussão na sorte número número 100 * 2 ^ 8: # 25600

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

fwanicka picture fwanicka  ·  3Comentários

seanzer picture seanzer  ·  3Comentários

weswigham picture weswigham  ·  3Comentários

bgrieder picture bgrieder  ·  3Comentários

MartynasZilinskas picture MartynasZilinskas  ·  3Comentários