TypeScript 7.1 Beta: o que muda e como testar

O TypeScript 7.1 Beta chegou em 20 de outubro de 2026 e entrega a peça que faltava no compilador nativo escrito em Go: uma API pública para ferramentas. Além disso, a versão traz import attributes em módulos ambientes, source phase imports, o target es2026, ganhos de memória de até 49,6% e uma checagem mais rígida em tipos condicionais distributivos.
O que é o TypeScript 7.1 Beta
O TypeScript 7.1 é a primeira versão de recursos depois do TypeScript 7.0, que reescreveu o compilador inteiro em Go. Essa reescrita trouxe velocidade nativa e paralelismo com memória compartilhada, com ganhos de desempenho que a Microsoft descreve como próximos de 10 vezes em relação ao TypeScript 6.
No entanto, o 7.0 chegou sem uma API programática. Isso significa que ferramentas como o Volar do Vue, o language server do Svelte e o typescript-eslint não conseguiam usar o novo compilador como biblioteca.
Por isso, muitos times ficaram com uma configuração dividida. O tsc rodava a versão 7 no CI, enquanto o editor, o lint e o type checker do framework continuavam presos ao TypeScript 6 por meio do pacote de compatibilidade @typescript/typescript6.
O TypeScript 7.1 ataca exatamente esse ponto. Em outras palavras, ele entrega a nova API, o modelo de snapshots, sistemas de arquivos virtuais e os content mappers, que tornam o modelo do Volar um cidadão de primeira classe.
Como instalar o TypeScript 7.1 Beta
A instalação segue o mesmo caminho das versões anteriores. Basta adicionar o pacote com a tag beta como dependência de desenvolvimento do projeto.
# instala a versão beta como dependência de desenvolvimento
npm install -D typescript@beta
Como se trata de uma beta, o ideal é testar em um branch separado ou em um projeto de estudo. Dessa forma, você mede o impacto das mudanças sem afetar o pipeline principal.
Quem usa o Visual Studio também já convive com o compilador nativo. Segundo a documentação de JavaScript e TypeScript no Visual Studio, o SDK embutido do Visual Studio 2026 usa o compilador nativo do TypeScript 7, e um projeto com versão local instalada passa a usar essa versão no lugar do SDK.
Se você está começando no ecossistema e quer praticar TypeScript em um projeto real, o curso Começando com Angular do balta.io cobre tipos, variáveis e a organização de um projeto front-end.
Import attributes em módulos ambientes
Import attributes são metadados anexados a uma instrução de importação. Na prática, eles dizem ao runtime ou ao bundler como interpretar o conteúdo importado e qual forma o módulo resultante terá.
// tudo depois de "with" é um import attribute
import myData from "./data/someData.json" with { type: "json" };
O atributo type virou o caminho padrão para customizar esse comportamento. Além do JSON, existe uma proposta do ECMAScript para type: "text", que devolve o conteúdo como string, e outra para type: "bytes", que devolve um Uint8Array.
// proposta "text": o conteúdo chega como string
import readme from "../README.md" with { type: "text" };
// proposta "bytes": o conteúdo chega como Uint8Array
import photo from "./myPhoto.png" with { type: "bytes" };
// suportado pelos navegadores há alguns anos
import someTheme from "./themes/someCoolTheme.css" with { type: "css" };
document.adoptedStyleSheets.push(someTheme);
O problema das declarações por extensão
Até agora, a forma comum de tipar esses arquivos era declarar módulos ambientes com um padrão curinga por extensão, como *.css ou *.md. Essa abordagem funciona, mas ignora completamente os import attributes.
// abordagem antiga: um padrão por extensão, sem olhar os atributos
declare module "*.md" {
const content: string;
export default content;
}
declare module "*.txt" {
const content: string;
export default content;
}
O primeiro efeito colateral é que nada impede alguém de esquecer o atributo na importação. Além disso, a declaração fica repetitiva, porque .txt e .md precisam de dois padrões mesmo usando o mesmo type: "text".
O problema mais sério, porém, é que cada ambiente pode reagir de forma diferente à presença do atributo. Um bundler pode aplicar CSS Modules por padrão em arquivos .css, enquanto type: "css" força a importação como CSSStyleSheet.
Como o TypeScript 7.1 resolve com import attributes
O TypeScript 7.1 passa a aceitar import attributes diretamente na declaração do módulo ambiente. Assim, o tipo depende do atributo usado, e não da extensão do arquivo.
// o tipo agora depende do atributo, não da extensão
declare module "*" with { type: "css" } {
const content: CSSStyleSheet;
export default content;
}
declare module "*" with { type: "text" } {
const content: string;
export default content;
}
declare module "*" with { type: "bytes" } {
const content: Uint8Array;
export default content;
}
Com isso, o compilador acusa erro quando a importação não traz o atributo exigido. Ao mesmo tempo, ele entrega o tipo correto para cada atributo.
Vale notar que essas importações não são validadas contra arquivos no disco. Portanto, uma busca que falhar só aparece em tempo de execução ou no build do bundler.
Essas declarações também não fazem parte das bibliotecas embutidas do TypeScript. A recomendação oficial é que bibliotecas publicadas não as incluam, deixando cada projeto final declarar o que precisa e evitando conflitos futuros.
Source phase imports
Source phase imports são uma proposta do ECMAScript que adiciona a palavra source logo depois de import. Somente importações padrão podem usar essa forma.
A ideia é receber um objeto que representa o módulo depois de analisado pelo runtime, mas antes de carregar as suas dependências. Ou seja, nada foi validado, ligado a outros módulos ou executado ainda.
Esse objeto é uma instância de AbstractModuleSource, um novo tipo global em lib.esnext.d.ts. No caso de WebAssembly, ele é um WebAssembly.Module, que pode ser passado direto para WebAssembly.instantiate.
import source SuperFastThing from "./super-fast-thing.wasm";
import { WASI } from "node:wasi";
const wasi = new WASI({ /* opções */ });
// o módulo fonte vai direto para a instanciação
const fastThingInstance = await WebAssembly.instantiate(SuperFastThing, {
wasi_snapshot_preview1: wasi.wasiImport
});
wasi.start(fastThingInstance);
As versões recentes do Node.js já suportam source imports de WebAssembly. Por outro lado, com arquivos JavaScript ainda há pouco a fazer, porque muitos ambientes e ferramentas nem suportam esse recurso.
Importações dinâmicas e identidade
Também existe a forma dinâmica, por meio da meta propriedade import.source. Além disso, source imports preservam identidade, então você pode usá-los em Map e Set ou compará-los com segurança.
import source source1 from "./plugin.wasm";
import source source2 from "./plugin.wasm";
const source3 = await import.source("./plugin.wasm");
console.log(source1 === source2); // true
console.log(source2 === source3); // true
Para usar o recurso no TypeScript 7.1, o projeto precisa de --module igual a esnext, nodenext ou preserve. Também é necessário o esnext em --lib ou em --target.
Suporte a es2026 e novos métodos no TypeScript 7.1
O TypeScript 7.1 adiciona es2026 como opção estável de --target e --lib. Na prática, o que importa são as declarações de biblioteca que saíram de esnext porque estabilizaram no ECMAScript 2026.
Entre os itens estabilizados em --lib es2026, estão estes:
Math.sumPrecise()eError.isError()Iterator.concat()eArray.fromAsync()JSON.rawJSON()e uma sobrecarga deJSON.parse()com o texto originalgetOrInsert()egetOrInsertComputed()emMapeWeakMaptoBase64(),setFromBase64(),toHex()esetFromHex()emUint8ArrayUint8Array.fromHex()eUint8Array.fromBase64()
Os métodos getOrInsert resolvem um padrão muito comum, que é buscar um valor e inserir um padrão quando ele não existe. Por exemplo, um contador de acessos fica bem mais direto.
const visitsByPage = new Map<string, number>();
// busca o valor ou insere 0 antes de somar
const current = visitsByPage.getOrInsert("/home", 0);
visitsByPage.set("/home", current + 1);
// a versão "computed" só executa a função quando a chave não existe
const cache = new Map<string, string[]>();
const tags = cache.getOrInsertComputed("articles", () => []);
tags.push("typescript");
No TypeScript 7.1, o --lib esnext também passa a incluir métodos ainda em proposta. Entre eles estão Promise.allKeyed, Promise.allSettledKeyed, Iterator.zip, Iterator.zipKeyed e os métodos chunks, join e includes em Iterator.prototype.
A nova API do TypeScript 7.1
A grande novidade do TypeScript 7.1 é a API para construir ferramentas sobre as análises sintática e semântica do compilador. Em vez de um bloco monolítico, ela está dividida em pontos de entrada com propósitos específicos.
| Ponto de entrada | Propósito |
|---|---|
typescript/sync |
Cliente síncrono do compilador, com opções, type checker e APIs de programa |
typescript/async |
Equivalente baseado em Promise do cliente síncrono |
typescript/fs |
Sistemas de arquivos completos ou em camadas |
typescript/ast |
Tipos de nós da árvore sintática e enums como SyntaxKind |
typescript/ast/is |
Type guards de nós |
typescript/ast/factory |
Funções create* e update* imutáveis |
O exemplo a seguir faz uma compilação simples com arquivos e opções vindos da linha de comando. Diferente do tsc, ele não procura nem carrega um tsconfig.json.
import { API, formatDiagnosticsWithColorAndContext, type Diagnostic } from "typescript/sync";
using api = new API();
const reportErrors = (diagnostics: readonly Diagnostic[]) => {
console.log(formatDiagnosticsWithColorAndContext(diagnostics, api));
};
// lê arquivos e opções da linha de comando
const parsedCommandLine = api.parseCommandLine(process.argv.slice(2));
if (parsedCommandLine.errors.length > 0) {
reportErrors(parsedCommandLine.errors);
process.exit();
}
// cria o programa resolvendo todos os arquivos
using program = api.createProgram(parsedCommandLine.fileNames, parsedCommandLine.options);
const diagnostics = [
...program.getGlobalDiagnostics(),
...program.getProgramDiagnostics(),
...program.getSyntacticDiagnostics(),
...program.getSemanticDiagnostics(),
];
if (diagnostics.length > 0) {
reportErrors(diagnostics);
}
// respeita o noEmitOnError
const emitResult = program.emit();
if (emitResult.diagnostics.length > 0) {
reportErrors(emitResult.diagnostics);
}
Repare no uso de using nas declarações da API do TypeScript 7.1. Os objetos API e Program são Disposable, porque o TypeScript reserva memória e mantém registros entre os runtimes de Go e de JavaScript.
Se você deixar essa limpeza para o garbage collector, haverá vazamento de memória do lado do Go. Então, você precisa chamar métodos como api.close() e program.dispose() manualmente ou declarar os objetos com using, que faz o descarte ao sair do escopo.
Snapshots
Muitos fluxos com TypeScript lidam com mudanças ao longo do tempo, seja no disco ou enquanto alguém digita. Por isso, o conceito central da API é o snapshot, uma versão de todos os projetos e programas em um ponto no tempo.
Você cria um snapshot novo com api.createSnapshot ou deriva um a partir do anterior com snapshot.update. Dessa forma, o compilador evita reconstruir tudo e reduz o volume de informação enviada.
import { ScriptTarget } from "typescript/ast";
import { createFileSystemLayer, createFileSystemWithLib } from "typescript/fs";
import { API } from "typescript/sync";
import { resolve } from "node:path";
const fileName = resolve("snapshot-example.ts");
using api = new API();
using before = api.createSnapshot({
fileSystem: createFileSystemWithLib([
[fileName, 'export const answer: number = "forty-two";'],
]),
createPrograms: [
{ rootFiles: [fileName], compilerOptions: { strict: true, noEmit: true, target: ScriptTarget.ES2022 } },
],
});
using beforeProgram = before.operation.createdPrograms[0];
// novo snapshot alterando apenas um arquivo
using after = before.update({
fileSystem: createFileSystemLayer([[fileName, "export const answer: number = 42;"]]),
ensurePrograms: [beforeProgram.id],
});
using afterProgram = after.getProgram(beforeProgram.id);
console.log("before:", beforeProgram.getSemanticDiagnostics().length); // 1
console.log("after:", afterProgram?.getSemanticDiagnostics().length); // 0
Os programas têm identificadores que persistem entre snapshots. Com ensurePrograms, você indica quais programas quer observar no snapshot novo, enquanto o padrão é mais preguiçoso e permite empilhar várias chamadas de update.
Mesmo depois do update, o snapshot anterior continua utilizável. Contudo, a documentação alerta para não misturar objetos derivados de programas, projetos ou checkers de snapshots diferentes.
APIs de sistema de arquivos
Por padrão, a API do TypeScript 7.1 trabalha sobre o sistema de arquivos do host. No entanto, o módulo typescript/fs oferece três funções para criar sistemas virtuais.
| Função | Comportamento |
|---|---|
createFileSystem(files) |
Define um sistema completo, e arquivos não listados não existem |
createFileSystemWithLib(files) |
Define um sistema completo e monta a biblioteca padrão do compilador como somente leitura |
createFileSystemLayer(files) |
Sobrescreve arquivos selecionados e recorre à camada base para o resto |
No exemplo anterior, a primeira camada continha só o arquivo de interesse. Depois, a camada de atualização trocou apenas esse arquivo, enquanto lib.d.ts e os demais continuaram vindo da base.
A API também oferece emitToString, que gera a saída sem gravar nada em disco. Além disso, a opção removedPaths bloqueia requisições à camada inferior, e as notificações de arquivos chegam pela opção fileNotifications do update.
Content mappers no TypeScript 7.1 para Vue, Svelte e Astro
Frameworks como Vue, Svelte, Astro e Ember usam arquivos que misturam HTML, JavaScript e CSS. O Volar surgiu para levar a experiência do TypeScript a esses formatos, mas sempre esbarrou em limitações da API antiga.
Na prática, o Volar precisava embutir o TypeScript e assumir o editor ou recorrer a contornos desajeitados. Além disso, os type checkers de linha de comando desses formatos não conseguiam reaproveitar o modelo do Volar.
O TypeScript 7.1 cria um novo ponto de extensão chamado content mapper. Ele é registrado para extensões específicas e produz uma representação virtual do arquivo, com regiões mapeadas de volta ao original.
Com isso, mensagens de erro e posições ficam mais precisas, e o tooling funciona dentro das regiões embutidas. O registro acontece no tsconfig.json, como no exemplo com um pacote hipotético que ainda não está no npm.
{
"compilerOptions": {},
"contentMappers": [
{
"package": "@vue/content-mapper",
"extensions": [".vue"],
"options": { "target": 3.5 }
}
]
}
Content mappers são, na prática, executáveis arbitrários que o compilador aciona. Por esse motivo, a linha de comando exige a flag --runExternalCode para permitir a execução.
# autoriza o compilador a executar os content mappers registrados
tsc --runExternalCode
No VS Code, tudo funciona sem configuração extra, desde que haja confiança no workspace e permissão para content mappers. Também é possível desligar o recurso com a configuração js/ts.contentMappers.enabled.
Já existem protótipos ou implementações para Astro, CSS Modules, Ember, MDX, Svelte e Vue.
Desempenho do TypeScript 7.1 em projetos reais
Mesmo depois do salto do TypeScript 7.0, o TypeScript 7.1 traz cerca de cinquenta otimizações para reduzir pressão de memória e acelerar builds. A lista inclui construção mais rápida de uniões, melhor reuso de programas após mudanças em um único arquivo e agendamento de projetos do tsc -b por profundidade de dependência.
Os números oficiais usam quatro threads de type checking e a opção --noEmit. Em todos os projetos medidos, a beta foi mais rápida e consumiu menos memória que a versão 7.0.
O destaque é o mui-docs, que caiu de 22,2 para 11,9 segundos, uma redução de 46,6%. No mesmo projeto, o uso de memória foi de 8.093 MiB para 4.079 MiB, ou seja, quase metade.
| Projeto | Tempo 7.0 | Tempo 7.1 Beta | Redução de tempo | Memória 7.0 | Memória 7.1 Beta | Redução de memória |
|---|---|---|---|---|---|---|
| bluesky | 4,48 s | 3,89 s | 13,2% | 2.017 MiB | 1.698 MiB | 15,8% |
| mui-docs | 22,2 s | 11,9 s | 46,6% | 8.093 MiB | 4.079 MiB | 49,6% |
| sentry | 19,6 s | 15,1 s | 22,8% | 6.587 MiB | 5.216 MiB | 20,8% |
| t3code-server | 26,5 s | 19,1 s | 28,2% | 6.894 MiB | 5.197 MiB | 24,6% |
| vscode | 21,6 s | 17,7 s | 17,7% | 8.387 MiB | 6.943 MiB | 17,2% |
Nos outros projetos, os ganhos de tempo ficaram entre 1,2 e 1,4 vez. Para quem roda type checking em CI várias vezes por dia, essa diferença se acumula rapidamente.
Mudanças de comportamento
O TypeScript 7.1 traz duas mudanças que podem gerar erros novos em um build que antes passava.
A primeira envolve o lib.d.ts. Os tipos gerados para o DOM mudaram e podem afetar a checagem de código que depende de APIs do navegador.
Checagem mais rígida em tipos distributivos no TypeScript 7.1
A segunda mudança afeta tipos condicionais distributivos. Quando um tipo condicional avalia um parâmetro de tipo e recebe uma união, ele aplica a condição a cada membro separadamente e une os resultados.
type Classify<T> = T extends "a" | "b" | "c" ? { known: T } : { unknown: T };
// { known: "a" } | { known: "b" } | { unknown: "x" } | { unknown: "y" }
type Result = Classify<"a" | "b" | "x" | "y">;
Dentro de cada ramo, porém, o T não é exatamente o mesmo parâmetro original. Ele representa um subconjunto mais específico da união, e as versões anteriores não faziam essa distinção.
type Show<A extends number, B extends A> = `${B} extends ${A}`;
type Issue<A extends number, B extends A> =
A extends B ?
never :
Show<A, B>; // erro no TypeScript 7.1
// antes, isto gerava o literal "0 extends 1"
type UhOh = Issue<0 | 1, 0>;
O literal 0 é subtipo da união 0 | 1 como um todo, mas não de cada membro isolado. A analogia do anúncio ajuda bastante, porque um gato é um animal, mas não é subtipo de todo tipo de animal.
Agora, o TypeScript 7.1 trata o parâmetro em cada ramo como um novo parâmetro, subtipo do original, sem relação com outros tipos restritos ao original. Assim, o exemplo passa a gerar o erro informando que B não satisfaz a restrição A.
Para corrigir, você pode remover a restrição original quando ela não for necessária. Outra opção é adicionar checagens condicionais extras que satisfaçam as restrições dos outros tipos.
Próximos passos e quando migrar
O time do TypeScript vai focar agora em velocidade, estabilidade e no feedback sobre os recursos da beta. A Release Candidate está prevista para cerca de um mês depois do anúncio, com a versão final pouco tempo depois.
Para quem já usa o TypeScript 7.0, testar o TypeScript 7.1 em um branch é um passo de baixo risco. Primeiro, rode o type checking e observe os erros novos ligados a tipos distributivos e ao lib.d.ts.
Em seguida, avalie se o projeto pode tirar proveito de es2026 e dos import attributes em módulos ambientes. Por fim, acompanhe as bibliotecas do seu framework, porque a migração completa do editor e do lint depende delas adotarem a nova API.
Conclusão
O TypeScript 7.1 fecha a lacuna mais visível deixada pelo compilador nativo, que era a ausência de uma API para o ecossistema. Com snapshots, sistemas de arquivos em camadas e content mappers, frameworks como Vue e Svelte têm um caminho oficial para abandonar o TypeScript 6.
Ao mesmo tempo, a versão evolui a linguagem com import attributes em declarações ambientes, source phase imports e o target es2026. Além disso, ela reduz o consumo de memória em até 49,6% nos projetos medidos.
A mudança nos tipos distributivos exige atenção, embora corrija uma falha real de segurança de tipos. Portanto, a recomendação é instalar a beta, medir o impacto no seu projeto e enviar feedback antes da versão final.
FAQ
O TypeScript 7.1 já é estável?
Ainda não, pois a versão anunciada em 20 de outubro de 2026 é uma beta. A Release Candidate está prevista para cerca de um mês depois, seguida pela versão final.
Como instalo a versão beta?
Use o comando npm install -D typescript@beta no projeto. O ideal é testar em um branch separado antes de adotar no pipeline principal.
Preciso mudar meu código para atualizar?
Na maioria dos casos, não. Porém, tipos condicionais distributivos que repassam parâmetros restritos e código sensível aos tipos do DOM podem gerar erros novos.
O que são content mappers?
São plugins registrados por extensão que geram uma visão virtual de arquivos como .vue e .svelte. Eles permitem que o compilador verifique e mapeie erros de volta ao arquivo original.
Por que a API usa a palavra-chave using?
Os objetos da API guardam memória do lado do Go. O using descarta esses objetos ao sair do escopo e evita vazamentos que o garbage collector não resolveria.