- O PR #53725 do Node.js foi mesclado, adicionando suporte experimental para executar arquivos TypeScript com a flag
--experimental-strip-types
- Esse recurso remove a sintaxe de tipos do código-fonte TypeScript para convertê-lo em código-fonte JavaScript, sem realizar verificação de tipos durante a conversão
- A implementação inicial não converte recursos exclusivos do TypeScript, como
Enum e namespaces, portanto fica limitada a código que se torna JavaScript apenas com a remoção de tipos
- A implementação escolheu
@swc/wasm-typescript, pelo motivo de simplicidade, já que não exige adicionar Rust ou Go ao toolchain do Node.js
- Na discussão, as principais limitações abordadas foram a regra de extensões de arquivo obrigatórias do ESM, a prática do TypeScript em que imports
.js apontam para arquivos .ts reais, e o custo de sourcemaps e posições em stacks de erro
Como funciona o --experimental-strip-types
- Ao definir
--experimental-strip-types, o Node.js consegue executar arquivos TypeScript
- O Node.js transpila o código-fonte TypeScript para código-fonte JavaScript
- Durante a transpilação, a verificação de tipos não é realizada, e as informações de tipo são descartadas
- Um exemplo é o seguinte
const foo: string = "foo";
const foo = "foo";
Por que escolher apenas a remoção de tipos e limitações iniciais
- O ponto central deste PR é remover a sintaxe de tipos da sintaxe TypeScript
- Outros runtimes também convertem para JavaScript recursos exclusivos do TypeScript que não existem em JavaScript, como
enum
- A implementação inicial deste PR não realiza esse tipo de conversão
- Não é possível usar
Enum
- Não é possível usar
namespaces
- Outros recursos exclusivos do TypeScript também não são alvo de conversão
- Há uma orientação para consultar
typescript.md nas alterações do PR para detalhes de implementação e limitações
Escolha de @swc/wasm-typescript como ferramenta de implementação
@swc/wasm-typescript é um pacote pequeno, composto por um arquivo wasm e um arquivo JavaScript que faz seu binding
- Outras ferramentas tinham o requisito de adicionar Rust ou Go ao toolchain do Node.js
- O SWC também é usado no Deno para o mesmo propósito, e o autor do PR o vê como uma opção validada
- No futuro, esse recurso poderia ser implementado na camada nativa
- Este PR faz referência a nodejs/loaders#217
Discussão sobre resolução de imports e extensões
- Imports sem extensão, como
import foo from "./foo", não são compatíveis
- O ESM do Node.js tem a regra de extensões de arquivo obrigatórias, então a falta de suporte a imports sem extensão é tratada como comportamento intencional
- Foi levantado que, em codebases de módulos TypeScript, é comum usar a extensão
.js na instrução de import mesmo quando o arquivo real é .ts
- Exemplo:
import foo from './foo.js'
- Caso em que não existe
./foo.js no sistema de arquivos, apenas ./foo.ts
- O autor do PR vê que esse problema pode ser tratado em iterações futuras, e que inferir extensões exigiria verificações adicionais no sistema de arquivos e heurísticas
- Se
./foo.js não existir, verificar ./foo.ts
- Se
./foo.ts não existir, verificar ./foo.js
- Essa discussão é acompanhada em nodejs/loaders#208
Posições de origem, sourcemaps e stacks de erro
- Na revisão, foi levantada a opinião de que posições de origem precisas em stacks de erro são necessárias no curto prazo
- Uma possível abordagem é não alterar as posições de origem durante a remoção de tipos, evitando a geração e o mapeamento de sourcemaps
- Foi mencionada a limitação de que sourcemaps têm custo em termos de uso de memória e desempenho, além de não serem simples
- O autor do PR vê que, como o SWC já oferece suporte a sourcemaps, seria possível adicionar uma flag para habilitá-los
- Também seria possível pedir ao SWC uma opção para substituir a sintaxe removida por espaços, de modo a não alterar a stack de erro original
Questões de design restantes antes da estabilização
- O suporte a TypeScript se divide em dois eixos
- Remoção da sintaxe de tipos
- Ajuste das regras de resolução para aceitar arquivos
.ts
- Foi levantada a preocupação de que mudanças nas regras de resolução possam conflitar com loaders TypeScript existentes
- O autor do PR respondeu que este PR não contém override das regras de resolução e que a extensão do arquivo é necessária
- Foi sugerido que, por meio de
allowImportingTsExtensions, é possível oferecer suporte ao formato import './file.ts', e que essa abordagem também é compatível com o Deno
- Para executar diretamente o mesmo arquivo-fonte e também gerar saída JavaScript com uma ferramenta de build, considera-se que a responsabilidade de trocar
file.ts por file.js pode ficar com a ferramenta de build
Mesclagem e trabalhos posteriores
- O PR foi mesclado em
nodejs:main em 24 de julho de 2024
- O tamanho da alteração é de 8 commits, 89 arquivos, 2.190 linhas adicionadas e 25 linhas removidas
- Foram aplicados rótulos como
semver-minor, experimental, module, esm e notable-change
- Houve discussão sobre mencionar esse recurso na página de orientação sobre TypeScript do site, e um PR relacionado de atualização também foi vinculado
- Também foi discutida em uma issue posterior a possibilidade de incluir nos metadados de distribuição a versão do TypeScript usada pelo binário do Node para remoção de tipos
1 comentários
Opiniões do Hacker News
A remoção de tipos do TypeScript é impossível sem a sintaxe do TypeScript. A remoção de tipos não é uma operação em nível de tokens, e a sintaxe do TypeScript continua mudando.
Por exemplo,
foo < bar & baz > ( x )no TypeScript 1.5 era parseado como(foo (x)), porquebar&baznão era uma expressão de tipo válida, mas depois que o operador de tipos de interseção foi adicionado, passou a ser parseado comofoo<(bar & baz)>(x)e desambiguado parafoo(x).Para continuar usando recursos recentes do TypeScript, será preciso compilar para JS ou manter a versão do Node sempre atualizada, e isso pode ser um compromisso difícil de aceitar para equipes que preferem ficar no Node LTS.
A ideia seria manter uma versão estável no core, mas permitir uma atualização separada via NPM quando o TypeScript lançar novos recursos ou mudanças incompatíveis, ou quando o usuário quiser usar novos recursos experimentais.
Assim, seria possível dar suporte a uma versão específica do TypeScript durante o ciclo de vida de 3 anos de uma versão do Node.js sem prender os usuários a ela: https://github.com/nodejs/loaders/issues/217
A última grande mudança de sintaxe provavelmente foi os tipos condicionais da 2.8, quando uma sintaxe parecida com o operador ternário
ifentrou em posições de tipo. Para fazer verificação de tipos, o modelo de tipos mudou muito desde a 2.x, mas a sintaxe em geral ficou estável, e a maior parte da inovação do TypeScript também esteve no modelo de tipos e na inferência de tipos.Ainda está em estágio inicial, Stage 1, mas também existe uma tentativa de padronizar uma parte considerável da sintaxe de tipos do TypeScript para fins de remoção de tipos, não de verificação de tipos: https://github.com/tc39/proposal-type-annotations
Equipes que não têm muito interesse no V8 ou em recursos recentes do Node e ficam no LTS provavelmente também terão menos interesse nos recursos mais recentes do TypeScript.
Se o Node.js puder executar arquivos TypeScript diretamente, o compilador TypeScript poderá ser usado apenas como verificador de tipos, em vez de remover tipos e transformar JavaScript. É parecido com o modo como, em Python, o verificador de tipos verifica os tipos e os deixa como estão, enquanto o interpretador ignora as dicas de tipo.
Curiosamente, em Python surgiram vários verificadores de tipos populares que usam a mesma sintaxe de dicas de tipo, mas têm semânticas diferentes. Já em JavaScript, o TypeScript parece ter se tornado praticamente o único verificador de tipos popular.
Também já ouvi falar de casos em Python em que as pessoas não verificam os tipos, apenas escrevem tipos no código-fonte como uma sintaxe de comentários mais conveniente. Se o Node.js der suporte a ignorar tipos, esse estilo também se torna possível em JavaScript.
Pessoalmente, o maior incômodo de usar JSDoc era a sintaxe de importação, mas isso mudou a partir do TypeScript 5.5 e agora deixou de ser um problema.
Ainda assim, alguns bundlers já fazem isso usando compiladores não oficiais, como o SWC, e o Node também está testando o SWC nesse recurso.
Dicas de tipo não são apenas simples comentários; como o nome sugere, também são dicas para a IDE mostrar um autocompletar melhor.
Acho que isso deveria fazer parte da especificação da linguagem.
Fico curioso para ver como o ecossistema NPM vai reagir quando esse recurso deixar de ficar atrás de uma flag e passar a ser o padrão. Não sei se os contribuidores ainda vão compilar versões CJS e ESM ao publicar módulos NPM, ou se simplesmente vão colocar
engine: nodejs >= 25nopackage.jsone pular a etapa de buildPessoalmente, se módulos NPM que têm o código-fonte em TS pararem de distribuir
dist/.cjs, eu acho melhor, porque fica claro onde colocar o depurador e oconsole.log. Do ponto de vista de quem contribui para o NPM, a tentação de eliminar a etapa de build também deve ser grandeMas, se até uma única dependência distribuir arquivos TS, a premissa de que “todo mundo recebe arquivos TS” pode se espalhar rapidamente. No momento em que o Node.js tirar isso de uma flag experimental opt-in, parece que toda a comunidade passará a esperar implicitamente que os consumidores aceitem arquivos TS
Se isso acontecer, em poucos meses Firefox e Safari também serão pressionados a aceitar isso, e todos os compiladores JS não terão escolha a não ser descartar anotações de tipo TS
Pessoalmente, também acho isso aceitável. Afinal, quem usa TS também coloca nos módulos NPM uma etapa de transformação que converte código TS em JS e
.d.tspara dar suporte a algum usuário hipotético de JS. Mas, se o Node aceitar arquivos.ts, dá para remover essa etapa de transformação sem perceber; então fico curioso sobre que mecanismo impediria publicadores do NPM de pararem de distribuir arquivosjs/d.tse nem perceberem que quebraram algoA ideia é permitir publicar código TypeScript sem etapa de build e, quando outro desenvolvedor instalar, ele poder ver o código-fonte TypeScript original, não JavaScript transformado
.tsdentro de node_modules não é suportada. Essa é uma das principais restrições para não quebrar o ecossistema.cjse.mjspara npm, e provavelmente vou acabar distribuindo as três versõesSe fosse um projeto novo escrito do zero, talvez eu distribuísse só TypeScript e não fizesse a transformação
Correção: segundo um comentário irmão de satanacchio, que parece ser o autor do PR e membro do Comitê Técnico de Direção do Node.js, o suporte a TS é para itens de nível superior e não se aplica a bibliotecas dentro de
node_modules.tsno npm, mas a equipe do TypeScript foi muito contra isso por causa de problemas comtsconfige desempenho. Ainda assim, estou esperançosoTalvez um dia o Node permita fazer reflexão em tempo de execução desses tipos no JS
Isso seria um ganho enorme. Em Python, como é possível refletir tipos, ferramentas excelentes como pydantic conseguem gerar validações a partir dos tipos
Com uma única notação padrão, seria possível obter verificação de tipos, validação de dados em tempo de execução, geração de API e geração de documentação de API
Hoje, em JS, ferramentas como Zod precisam praticamente reinventar o que o TypeScript já faz, com coisas como
const mySchema = z.string();z.string()do Zod não fornece apenas segurança de tipos como o TypeScript. O TypeScript oferece verificação de tipos em tempo de compilação, enquanto o Zod acrescenta validação e parsing em tempo de execuçãoz.string()transformamySchemaem um schema funcional que pode fazer parsing e validação.mySchema.parse("some data")passa, emySchema.parse(321)lança uma exceçãoUsei em lugares que precisavam de validação em tempo de execução e checagens dentro do processo, e funcionou bem; também é possível extrair tipos, como em
const A = z.string(); type A = z.infer; // stringOu seja, se você define primeiro o tipo no Zod e infere o tipo a partir dele, obtém tanto verificação de tipos em tempo de compilação quanto em tempo de execução. Pode ser exagero para uma base de código pequena e que muda rápido, mas em situações que exigem validação dentro do processo é bem útil e não dá tanto trabalho
Ainda assim, o TypeScript permite plugins. Eles não são muito usados, mas permitem adicionar à linguagem outros recursos que são transformados no JavaScript resultante
Um plugin relacionado a tipos em tempo de execução é o Typia, que permite usar assinaturas de tipo do TypeScript em tempo de execução em guards como
assert(myValue). Ele intercepta essa chamada de função e gera, no JavaScript transformado, instruçõesifexaustivas que verificam as características da variável passadaNão acho que isso vá se tornar parte da linguagem nos próximos 4 a 6 anos, mas já existem bibliotecas que permitem fazer esse tipo de coisa hoje
Precisamos de um sistema de tipos sólido de verdade, mas o TS é intencionalmente não sólido. Precisamos de um sistema de tipos que não permita más práticas de codificação como o TS permite, e que force um design capaz de tornar os programas rápidos. Precisamos de um sistema de tipos Hindley-Milner
Se você quiser transformar um módulo em código tipado, bastaria adicionar
"use type". Esse modo deveria proibir partes ruins, como coerção de tipos, e impedir elementos que prejudicam desempenho, como mudar o formato de objetos ou tipos de valores, ou criar arrays misturando valores quaisquerDados vindos de módulos sem tipos seriam convertidos ou, se não pudessem ser convertidos, gerariam erro; assim o compilador poderia otimizar profundamente o código tipado com garantias de tipo mais fortes e sem risco de bailout no meio do caminho
ValueError, ou se um objeto é uma instância deSomeClass. Sou iniciante em TS, então eu achava que, fora arrays, objetos, números e strings, não havia tipos em JS, e que Zod ou funções de type guard retornavam objetos comuns no nível de “confia em mim”A experiência de desenvolvimento do Bun é algo bastante sem precedentes nessa área, e agora a maioria dos casos de uso está coberta, ou o Bun não trava ao usar scripts de execução com
bun runPor outro lado, no Node não dá para configurar os imports para não exigirem extensão, nem fazer o
tscacrescentar automaticamente a extensão.jsao resultado da compilação, então é preciso adicionar um bundlerO suporte nativo a TypeScript deve corrigir bastante esse pequeno incômodo, mas, mesmo quando estiver estável, é difícil imaginar que vá igualar o Bun em experiência de usuário ou desempenho
A experiência de desenvolvimento e as ferramentas também são de altíssimo nível. O Deno também consegue fazer verificação de tipos e, se me lembro bem, inclui o TSC
O Bun gera muito burburinho, mas, para trabalho sério, hoje o Deno é claramente uma opção melhor. Ainda assim, a visão e a execução do Bun são impressionantes e isso é bom para os desenvolvedores
bun installfalhou e nunca consegui fazê-lo funcionar direito: https://github.com/search?q=repo%3Aoven-sh%2Fbun+bun+install...Há muito tempo eu queria um runtime TypeScript e também gosto muito de TypeScript, mas é engraçado que, depois de ter deixado Java há tanto tempo, acabei procurando algo muito mais próximo de Java
No fim, parece que o que queríamos era Java com JIT, um sistema de tipos mais rico e tipagem gradual. O ecossistema npm também tem muitos pontos negativos, mas usar bibliotecas nesse ecossistema é muito menos trabalhoso e mais divertido
Surpreendentemente, Rust está na outra ponta do espectro das linguagens, mas às vezes passa uma sensação parecida
Correção: a expressão JIT não foi adequada. Escrevi JIT por preguiça, mas queria me referir à diferença de tempo de inicialização e de runtime ao executar algo na JVM e no V8. Java parece pesado, enquanto o ecossistema JavaScript parece muito mais ágil
Também não sei de jeito nenhum se TypeScript é mais próximo de Java. Acho que a única coisa que os dois compartilham é o nome incorreto “Javascript” e suporte a programação imperativa; o restante é muito diferente
Porém, algo que existe em Java e de que sinto falta em TypeScript é reflexão de tipos em runtime. No ecossistema TypeScript há inúmeras formas de contornar isso, mas todas me parecem um pouco feias
AoT também estaria bom, e seria ótimo ter uma opção de binário único. Idealmente, sem runtime e com eliminação inteligente. Seria ainda melhor se também rodasse no navegador com JIT
Também quero conveniências de compilador e verificador de tipos, como pattern matching exaustivo
Um recurso do Deno de que gosto está chegando diretamente ao Node. Muito bom
Talvez agora eu não precise sempre instalar esbuild para remover tipos. Estou animado porque parece que vai ficar muito mais fácil escrever e usar scripts em TypeScript
Recentemente tenho preferido Python para scripts descartáveis, mas, em termos de tipos, pessoalmente acho TypeScript melhor que Python. Especialmente scripts grandes revisitados alguns meses depois se beneficiam muito dos tipos
ts-node, mas pessoalmente prefiro otsxhttps://github.com/privatenumber/tsx
Foi um mês realmente movimentado para o Node. Primeiro, na v22.5.0, foi adicionado o node:sqlite, e agora o suporte a TypeScript também está chegando. Gosto da direção que o Node está tomando
Sou o autor do PR. Podem perguntar qualquer coisa
Comecei a usar Node.js em trabalhos de backend muito tempo atrás, e ele parecia ter muitas vantagens em relação a escrever código em PHP, sem trazer muitos dos problemas do Java
Mas o Node era um tanto bruto, e parecia uma linguagem em que era preciso acoplar um monte de coisas para criar a linguagem que eu queria. No fim, comecei a usar Go, que era muito mais fácil de usar e, embora às vezes fosse muito mais verboso, simplificava a programação graças à segurança de tipos
TypeScript parecia uma boa opção, mas no fim era mais um remendo. Não sei bem que valor se ganha usando TypeScript em vez de Go. Ter tipos explícitos é bom, mas não resolve outros problemas da linguagem que já foram resolvidos em Go. O Deno também resolve parte deles
A grande vantagem do Node sobre Go é a possibilidade de prototipar rapidamente, mas usar TypeScript parece reduzir bastante essa vantagem. Por isso é difícil julgar se isso é um bom avanço ou se faz o Node perder as características que o tornavam uma boa opção
Pelo uso da expressão “remendo”, talvez o problema seja menos a linguagem em si e mais a ausência de um framework opinativo como o Django, que gerencia tudo por padrão. Gosto de usar Django, mas é um pouco mais difícil sair do caminho prescrito
cdktf; pareceu uma escolha mais lógica do que Go