Assine para receber notificações de novos posts:

Melhorando a compatibilidade do Workers TypeScript: precisão, ergonomia e interoperabilidade

2022-11-18

4 min. de leitura
Este post também está disponível em English, 繁體中文, Français, Deutsch, 日本語, Español e 简体中文.

O TypeScript torna mais fácil para os desenvolvedores escrever código que não falha, detectando erros de tipo antes que o programa seja executado. Queremos que os desenvolvedores aproveitem essas ferramentas, e é por isso que, há um ano, criamos um sistema para gerar automaticamente tipos TypeScript para o tempo de execução do Cloudflare Workers. Ele permitia que os desenvolvedores vissem as conclusões de código em seus IDEs para APIs do Workers e digitassem o código de verificação antes da implantação. A cada semana, uma nova versão dos tipos seria publicada, refletindo as mudanças mais recentes.

Improving Workers TypeScript support: accuracy, ergonomics and interoperability

No ano passado, recebemos muitos comentários de clientes e equipes internas sobre como poderíamos melhorar nossos tipos. Com a mudança para o sistema de compilação Bazel em preparação para o código aberto do tempo de execução, vimos uma oportunidade de reconstruir nossos tipos para serem mais precisos, fáceis de usar e simples de gerar. Hoje, temos o prazer de anunciar o próximo grande lançamento do @cloudflare/workers-types com vários novos recursos e o código aberto dos scripts de geração automática totalmente reescritos.

Como usar o TypeScript com o Workers

Configurar o TypeScript no Workers é fácil. Se está começando com o Workers, instale o Node.js e execute npx wrangler init em seu terminal para gerar um novo projeto. Se tiver um projeto Workers existente e quiser aproveitar nossos tipos aprimorados, instale as versões mais recentes do TypeScript e @cloudflare/workers-types com npm install --save-dev typescript @cloudflare/workers-types@latest e crie um arquivo tsconfig.json com o seguinte conteúdo:

Seu editor agora vai destacar os problemas e fornecer conclusões de código enquanto você digita, levando a uma experiência de desenvolvedor menos propensa a erros e mais agradável.

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": ["esnext"],
    "types": ["@cloudflare/workers-types"]
  }
}

Editor destacando o uso incorreto de set em vez de put e fornecendo conclusões de código

Interoperabilidade aprimorada com tipos padrão

O Cloudflare Workers implementa muitas das mesmas APIs de tempo de execução dos navegadores, e estamos trabalhando para melhorar ainda mais a conformidade de nossos padrões com o WinterCG. No entanto, sempre haverá diferenças fundamentais entre o que os navegadores e o Workers podem fazer. Por exemplo, os navegadores podem reproduzir arquivos de áudio, enquanto o Workers tem acesso direto à rede da Cloudflare para armazenar dados distribuídos globalmente. Essa incompatibilidade significa que as APIs de tempo de execução e os tipos fornecidos por cada plataforma são diferentes, o que dificulta o uso de tipos do Workers com estruturas, como o Remix, que executam os mesmos arquivos na rede da Cloudflare e no navegador. Esses arquivos precisam ser verificados quanto ao tipo lib.dom.d.ts, que é incompatível com nossos tipos.

Para resolver esse problema, agora geramos uma versão separada de nossos tipos que podem ser importados seletivamente, sem a necessidade de incluir @cloudflare/workers-types no campo de tipos do seu tsconfig.json. Aqui está um exemplo de como fica:

Além disso, geramos automaticamente uma diferença de nossos tipos em relação a lib.webworker.d.ts do TypeScript. No futuro, vamos usar isso para identificar áreas em que podemos melhorar ainda mais nossa conformidade com as especificações.

import type { KVNamespace } from "@cloudflare/workers-types";

declare const USERS_NAMESPACE: KVNamespace;

Compatibilidade aprimorada com datas de compatibilidade

A Cloudflare preserva as promessas sólidas de compatibilidade com versões anteriores para todas as APIs que fornecemos. Usamos sinalizadores e datas de compatibilidade para fazer alterações importantes de maneira compatível com versões anteriores. Às vezes, esses sinalizadores de compatibilidade alteram os tipos. Por exemplo, o sinalizador global_navigator adiciona um novo navigator global e o sinalizador url_standard altera a assinatura do construtor URLSearchParams.

Agora permitimos que você selecione a versão dos tipos que correspondem à sua data de compatibilidade, para que você tenha certeza de que não está usando recursos que não serão compatíveis com o tempo de execução.

Integração aprimorada com o Wrangler

{
  "compilerOptions": {
    ...
    "types": ["@cloudflare/workers-types/2022-08-04"]
  }
}

Além das datas de compatibilidade, a configuração do ambiente do Workers também afeta o tempo de execução e a superfície da API de tipo. Se você tiver ligações como namespaces KV ou buckets R2 configuradas em seu wrangler.toml, elas precisam ser refletidas nos tipos TypeScript. Da mesma forma, regras personalizadas de texto, dados e módulo WebAssembly precisam ser declaradas para que o TypeScript conheça os tipos de exportações. Anteriormente, cabia a você criar um arquivo TypeScript de ambiente separado contendo essas declarações.

Para manter o wrangler.toml como a única fonte de verdade, agora você pode executar npx wrangler types para gerar esse arquivo automaticamente.

Por exemplo, o seguinte wrangler.toml

…gera estes tipos de ambiente:

kv_namespaces = [{ binding = "MY_NAMESPACE", id = "..." }]
rules = [{ type = "Text", globs = ["**/*.txt"] }]

Documentação e logs de alterações integrados aprimorados

interface Env {
  MY_NAMESPACE: KVNamespace;
}
declare module "*.txt" {
  const value: string;
  export default value;
}

As conclusões de código fornecem uma ótima maneira para os desenvolvedores novos na plataforma Workers explorarem a superfície da API. Agora incluímos a documentação para APIs padrão dos tipos oficiais do TypeScript em nossos tipos. Também estamos iniciando o processo de incluir documentos para APIs específicas da Cloudflare neles.

Para desenvolvedores que já usam a plataforma Workers, pode ser difícil ver como os tipos estão mudando a cada versão do @cloudflare/workers-types. Para evitar erros de tipo e destacar novos recursos, agora geramos um log de alterações detalhado com cada versão dividindo as definições novas, alteradas e removidas.

docs in types shown with code completions

Como a geração de tipos funciona internamente?

Conforme mencionado antes, reconstruímos completamente os scripts de geração automática de tipos para serem mais confiáveis, extensíveis e fáceis de manter. Isso significa que os desenvolvedores obterão tipos aprimorados assim que novas versões do tempo de execução forem publicadas. Nosso sistema agora usa o novo sistema do Workers, runtime-type-information (RTTI) para consultar tipos de APIs de tempo de execução do Workers, em vez de tentar extrair essas informações de C++ ASTs analisados.

Em seguida, passamos esse RTTI para um programa TypeScript que usa TypeScript Compiler API para gerar declarações e realizar transformações AST para organizá-las. Isso é incorporado ao sistema de compilação Bazel do workerd, o que significa que a geração de tipos agora é um único comando bazel build //types:types de compilação bazel. Aproveitamos o cache do Bazel para recompilar o mínimo possível durante a geração.

// Encode the KV namespace type without any compatibility flags enabled
CompatibilityFlags::Reader flags = {};
auto builder = rtti::Builder(flags);
auto type = builder.structure<KvNamespace>();
capnp::TextCodec codec;
auto encoded = codec.encode(type);
KJ_DBG(encoded); // (name = "KvNamespace", members = [ ... ], ...)

Embora os tipos gerados automaticamente descrevam de forma correta a interface JavaScript das APIs de tempo de execução do Workers, o TypeScript fornece recursos adicionais que podemos usar para fornecer tipos de maior fidelidade e melhorar a ergonomia do desenvolvedor. Nosso sistema nos permite escrever manualmente “substituições” parciais do TypeScript que são mescladas com os tipos gerados automaticamente. Isso nos permite…

import ts, { factory as f } from "typescript";

const keyParameter = f.createParameterDeclaration(
  /* decorators */ undefined,
  /* modifiers */ undefined,
  /* dotDotDotToken */ undefined,
  "key",
  /* questionToken */ undefined,
  f.createTypeReferenceNode("string")
);
const returnType = f.createTypeReferenceNode("Promise", [
  f.createUnionTypeNode([
    f.createTypeReferenceNode("string"),
    f.createLiteralTypeNode(f.createNull()),
  ]),
]);
const getMethod = f.createMethodSignature(
  /* modifiers */ undefined,
  "get",
  /* questionToken */ undefined,
  /* typeParameters */ undefined,
  [keyParameter],
  returnType
);
const kvNamespace = f.createInterfaceDeclaration(
  /* decorators */ undefined,
  /* modifiers */ undefined,
  "KVNamespace",
  /* typeParameters */ undefined,
  /* heritageClauses */ undefined,
  [getMethod]
);

const file = ts.createSourceFile("file.ts", "", ts.ScriptTarget.ESNext);
const printer = ts.createPrinter();
const output = printer.printNode(ts.EmitHint.Unspecified, kvNamespace, file);
console.log(output); // interface KVNamespace { get(key: string): Promise<string | null>; }
new automatic type generation architecture
  • Adicionar parâmetros de tipo (genéricos) a tipos como ReadableStream e evitar valores de tipos any.

  • Especificar a correspondência entre os tipos de entrada e saída com sobrecargas de método. Por exemplo, KVNamespace#get() deve retornar uma string quando o argumento de type for text, mas ArrayBuffer quando for arrayBuffer.

  • Renomear os tipos para corresponder aos padrões do TypeScript e reduzir o excesso de palavras.

  • Substituir totalmente um tipo para obter declarações mais precisas. Por exemplo, substituímos WebSocketPair por uma declaração const para melhores tipos com Object.values().

  • Fornecer tipos para valores que não são digitados internamente, como o objeto Request#cf.

  • Ocultar tipos internos que não podem ser usados em seu Workers.

Anteriormente, essas substituições eram definidas em arquivos TypeScript separados para as declarações C++ que estavam substituindo. Isso significava que muitas vezes elas ficavam fora de sincronia com as declarações originais. No novo sistema, as substituições são definidas juntamente com os originais com macros C++, o que significa que podem ser revisadas junto com as alterações de implementação de tempo de execução. Consulte o README do workerd JavaScript glue code para muitos outros detalhes e exemplos.

Experimente os tipos com workers-types ainda hoje.

Recomendamos que você atualize para a versão mais recente de @cloudflare/workers-types com npm install --save-dev @cloudflare/workers-types@latest e experimente o novo comando wrangler types. Vamos publicar uma nova versão dos tipos com cada lançamento do workerd. Compartilhe sua opinião no Discord para desenvolvedores da Cloudflare e abra uma questão no GitHub caso encontre algum tipo que possa ser melhorado.

Protegemos redes corporativas inteiras, ajudamos os clientes a criarem aplicativos em escala de internet com eficiência, aceleramos qualquer site ou aplicativo de internet, evitamos os ataques de DDoS, mantemos os invasores afastados e podemos ajudar você em sua jornada rumo ao Zero Trust.

Acesse 1.1.1.1 a partir de qualquer dispositivo para começar a usar nosso aplicativo gratuito que torna sua internet mais rápida e mais segura.

Para saber mais sobre nossa missão de construir uma internet melhor, comece aqui. Se estiver procurando uma nova carreira para trilhar, confira nossas vagas disponíveis.
Developer WeekDesenvolvedoresWrangler

Seguir no X

Brendan Coll|@_mrbbot
Cloudflare|@cloudflare

Posts relacionados

24 de outubro de 2024 às 13:00

Durable Objects aren't just durable, they're fast: a 10x speedup for Cloudflare Queues

Learn how we built Cloudflare Queues using our own Developer Platform and how it evolved to a geographically-distributed, horizontally-scalable architecture built on Durable Objects. Our new architecture supports over 10x more throughput and over 3x lower latency compared to the previous version....