5 pontos por GN⁺ 2024-05-05 | 1 comentários | Compartilhar no WhatsApp
  • A Figma migrou todo o código em Skew para TypeScript quando sua linguagem interna, usada em renderização móvel e no visualizador de protótipos, encontrou limitações em onboarding, integração e ecossistema
  • As condições para a transição foram criadas pela ampliação do suporte a WebAssembly nos navegadores móveis, pela substituição do engine em C++ nos caminhos críticos e pelo crescimento das equipes de prototipagem e mobile
  • A migração aconteceu em 3 fases: Write Skew, build SkewWrite Skew, build TypeScriptWrite TypeScript, build TypeScript, com um transpilador de Skew para TypeScript preservando o fluxo de desenvolvimento
  • No processo real de migração, surgiram problemas de compatibilidade como desempenho de desestruturação de arrays, diferenças na remoção de chamadas virtuais (devirtualization), ordem de inicialização, encadeamento de source maps e ausência de compilação condicional
  • No fim, a empresa obteve uma base em TypeScript que passava nos testes unitários e tinha desempenho parecido com o de Skew, e vê como próximos passos a integração entre códigos internos e externos, a gestão de pacotes e o aproveitamento do ecossistema TypeScript

Por que sair de Skew para TypeScript

  • Skew começou como um projeto paralelo nos primeiros dias da Figma e atendia à necessidade de criar rapidamente um visualizador de protótipos com suporte para web e mobile
  • Ao evoluir para uma linguagem compilada para JavaScript, ofereceu otimizações avançadas e tempos rápidos de compilação, mas o custo de manutenção cresceu à medida que o código se acumulava no visualizador de protótipos
    • era difícil para novos funcionários aprenderem
    • não se integrava facilmente com o restante da base de código
    • não havia um ecossistema de desenvolvedores fora da Figma
  • Com a adoção de TypeScript, os ganhos esperados eram ampliar a velocidade de desenvolvimento e o alcance da colaboração
    • integração mais simples entre código interno e externo com imports estáticos e gerenciamento nativo de pacotes
    • uso de ferramentas criadas por uma grande comunidade de desenvolvedores, como linters, bundlers e analisadores estáticos
    • uso de recursos modernos de JavaScript, como async/await, e de um sistema de tipos mais flexível
    • redução da barreira para onboarding de novos desenvolvedores e participação de outras equipes

Condições que só recentemente tornaram a transição viável

  • Quando a Figma criou sua primeira base de código mobile, os navegadores móveis ainda não tinham suporte a WebAssembly, e também era difícil carregar bundles grandes com bom desempenho
  • O próprio TypeScript ainda estava em estágio inicial, então o Skew, com tipagem estática e um sistema de tipos mais rigoroso, era na época uma escolha mais prática
  • O WebAssembly ganhou suporte amplo em mobile até 2018 e, nos testes da Figma, o desempenho em mobile chegou a um nível confiável em 2020
  • Um dos pontos fortes iniciais do Skew era a combinação de otimizações clássicas de compilador, como constant folding e remoção de chamadas virtuais, com otimizações voltadas à web, como gerar JavaScript usando operações com inteiros reais
  • Em benchmarks de 2020, foi confirmado que carregar protótipos da Figma com TypeScript no Safari podia ser quase 2 vezes mais lento, e isso bloqueava a transição porque, no iOS, o Safari era o único engine de navegador permitido
    • no iOS 17.4, a Apple abriu outros engines de navegador para usuários da UE, mas para usuários de outras regiões o WebKit continua sendo o único engine permitido
  • Depois disso, muitos componentes centrais do engine em Skew foram substituídos por componentes equivalentes no engine em C++ e o risco de desempenho da migração para TypeScript diminuiu
    • isso incluía os caminhos de código mais críticos, como carregamento de arquivos
    • com isso, a empresa ganhou confiança de que poderia absorver a perda de desempenho ao migrar para TypeScript
  • À medida que as equipes de prototipagem e mobile cresceram, a Figma passou a ter recursos para investir em migração automática e melhoria da experiência do desenvolvedor

A transição da base de código em 3 fases

  • No primeiro protótipo de migração, em 2020, o desempenho em TypeScript era quase 2 vezes mais lento
  • Depois que o suporte a WebAssembly e a migração do engine mobile para C++ amadureceram o suficiente, a Figma aproveitou a Maker Week para corrigir o protótipo existente e demonstrar uma migração funcional que passava em todos os testes
  • O objetivo era converter toda a base para TypeScript, mas uma reescrita manual poderia gerar queda na velocidade de desenvolvimento, erros em runtime e regressões de desempenho
  • Skew e TypeScript tinham diferenças de semântica maiores do que uma simples transição entre linguagens da família “JavaScript com tipos”
    • no TypeScript, namespaces e classes só são inicializados depois que o arquivo é importado
    • se a ordem dos imports não for a esperada, podem ocorrer erros em runtime
    • no Skew, todos os símbolos ficam disponíveis em runtime para toda a base assim que o carregamento acontece
  • Com base em um trabalho que Evan Wallace havia iniciado no passado, a Figma desenvolveu um transpilador de Skew para TypeScript
  • Fase 1: escrever em Skew e gerar Skew

    • o transpilador foi desenvolvido mantendo o processo de build existente
    • o código TypeScript gerado era versionado no GitHub para que os desenvolvedores pudessem ver a forma da nova base de código
  • Fase 2: escrever em Skew e gerar TypeScript

    • depois que passou a existir um bundle em TypeScript capaz de passar em todos os testes unitários, o tráfego de produção foi gradualmente direcionado para builds da base em TypeScript
    • os desenvolvedores continuavam escrevendo em Skew, e o transpilador convertia o código Skew para TypeScript, atualizando o código TypeScript no GitHub
    • os erros de tipo no código gerado também continuaram sendo corrigidos
    • o TypeScript conseguia gerar um bundle válido mesmo com erros de tipo
  • Fase 3: escrever em TypeScript e gerar TypeScript

    • depois que todos os usuários passaram pelo processo de build em TypeScript, o código TypeScript se tornou a única fonte de verdade do desenvolvimento
    • a equipe encontrou um momento sem merges em andamento para interromper o processo de geração automática e remover o código Skew da base
    • numa sexta-feira à noite, fizeram o merge da remoção da geração automática e da mudança nas tarefas de CI para executar diretamente os arquivos TypeScript
    • durante o rollout gradual, uma quebra no recurso Smart Animate foi detectada internamente e, graças ao modelo de deploy com gate, foi possível desligar o rollout, corrigir o problema e revisar o plano

Problemas revelados no transpilador

  • Compiladores normalmente são compostos por frontend e backend
    • o frontend faz parsing do código de entrada, executa verificação de tipos e de sintaxe, e converte o resultado para uma representação intermediária (IR)
    • o backend converte a IR para outra linguagem
    • o backend do compilador Skew gerava JavaScript ofuscado e minificado
  • Um transpilador é um compilador especial cujo backend gera código legível por humanos, e a Figma precisava gerar TypeScript legível a partir da IR do Skew
  • A implementação inicial, fortemente inspirada no backend JavaScript do Skew, avançou com relativa facilidade, mas mais adiante surgiram vários problemas difíceis de rastrear e tratar
  • Desempenho da desestruturação de arrays

    • ao investigar a diferença de desempenho offline entre Skew e TypeScript em um protótipo de exemplo, a equipe observou uma taxa de frames menor em TypeScript
    • a causa era a desestruturação de arrays em JavaScript
    • em operações como const [a, b] = function_that_returns_an_array(), o JavaScript não indexa o array diretamente: ele cria um iterator e o percorre
    • a Figma usava essa abordagem para obter argumentos a partir da palavra-chave arguments do JavaScript, e isso tornava alguns casos de teste mais lentos
    • ao gerar código que indexava diretamente o array de argumentos em vez de usar desestruturação, a latência por frame melhorou em até 25%
  • Otimização de remoção de chamadas virtuais no Skew

    • o compilador Skew executava uma otimização de devirtualization que, em certas condições, movia funções de dentro de classes para fora, elevando-as a funções globais
    • myObject.myFunc(a, b) podia virar myFunc(myObject, a, b)
    • o TypeScript não faz essa otimização
    • a quebra no Smart Animate acontecia quando myObject era null
    • a chamada devirtualized funcionava normalmente, mas a chamada sem devirtualization gerava exceção por acesso a null
    • para encontrar outros pontos com o mesmo problema, a Figma adicionou logging a todas as funções que podiam participar da devirtualization
    • após manter esse logging ativo por um curto período, a equipe analisou os logs de produção e corrigiu os pontos problemáticos
  • Diferenças na ordem de inicialização

    • no Skew, variáveis, classes, namespaces e definições de função podiam ser declarados em qualquer lugar do código sem preocupação com a ordem
    • no TypeScript, a ordem de inicialização de variáveis globais ou definições de classes importa
    • inicializar variáveis estáticas de uma classe antes da definição da própria classe gera erro em tempo de compilação
    • no início, o transpilador mantinha um comportamento parecido com o de Skew sem usar namespaces, achatando todas as funções em um escopo global
    • o código resultante era difícil de ler, e depois o transpilador foi ajustado para emitir código TypeScript na ordem correta e reintroduzir namespaces para melhorar a legibilidade
    • no fim, a equipe construiu um transpilador capaz de passar nos testes unitários e compilar código TypeScript com desempenho equivalente ao do Skew
    • alguns problemas menores foram resolvidos manualmente no código-fonte Skew ou deixados para depois da migração para TypeScript, em vez de acrescentar novas correções ao transpilador

Mantendo a experiência de depuração com source maps

  • A Figma tratou os source maps como algo essencial para que os desenvolvedores pudessem continuar depurando sem interrupções durante a migração para TypeScript
  • O debugger do navegador entende apenas JavaScript, mas os desenvolvedores colocam breakpoints no código-fonte em Skew ou TypeScript
  • Source maps conectam posições no JavaScript compilado às posições no código-fonte original
    • por exemplo, pode haver um mapeamento como helper → c, myInt → a, arrayOfInts → b
  • Em geral, um único arquivo de source map com extensão .map é associado ao bundle final de JavaScript
  • A infraestrutura existente já gerava source maps de Skew → JavaScript, mas na Fase 2 o pipeline mudou para um fluxo Skew → TypeScript → bundling com esbuild
  • Se os source maps antigos fossem usados como estavam, o mapeamento entre JavaScript e código Skew ficaria incorreto, impedindo a depuração
  • O novo processo de build montava os source maps em 3 etapas
    • Etapa 1: o esbuild gera o source map TypeScript → JavaScript ts-to-js.map
    • Etapa 2: o transpilador gera, para cada arquivo Skew, um source map Skew → TypeScript
    • Etapa 3: os dois source maps são compostos para gerar o source map final Skew → JavaScript
  • Com esse source map final, foi possível mapear o novo bundle JavaScript de volta ao código Skew e preservar a experiência do desenvolvedor também na Fase 2

Como a compilação condicional foi tratada no TypeScript

  • O Skew oferecia suporte a compilação condicional com instruções if no topo do arquivo e com a opção defines passada ao compilador
  • Isso permitia gerar bundles diferentes a partir da mesma base de código
    • o bundle real distribuído aos usuários
    • um bundle exclusivo para testes unitários
    • funções ou classes com implementações diferentes em builds de debug ou release
  • Como o TypeScript não tem compilação condicional, a Figma passou a usar defines do esbuild e dead code elimination depois da verificação de tipos, na etapa de bundling
  • Nesse modelo, os defines não podem influenciar a verificação de tipos
    • por isso, código como testOnlyFunction, que só existe quando BUILD == "TEST", não podia continuar existindo dessa forma em TypeScript
  • A solução foi converter o padrão para que classes e métodos estivessem sempre definidos, com desvio interno baseado no valor de BUILD
    • se testOnlyFunction fosse chamada em um build que não fosse de teste, seria lançada a exceção Unexpected call to test-only function
  • O JavaScript final ainda podia ser compilado de forma idêntica ao JavaScript que o código original em Skew gerava
  • Ainda assim, alguns símbolos que antes só existiam em modos específicos de compilação passaram a existir em todos os modos, deixando o bundle final um pouco maior
  • Nos testes, o aumento no tamanho do bundle ficou em um nível aceitável, e símbolos de topo não exportados podiam ser removidos com tree-shaking

O rumo depois da transição

  • Ao migrar todo o código em Skew para TypeScript, a Figma modernizou uma de suas bases de código centrais
  • A integração com código interno e externo ficou mais fácil, e os desenvolvedores passaram a trabalhar com mais eficiência
  • Dadas as necessidades e capacidades da Figma na época, a decisão de começar essa base em Skew foi adequada
  • Com o amadurecimento da tecnologia, o TypeScript, que antes não era a escolha certa, passou a ser uma opção apropriada
  • Como trabalho futuro, a Figma está explorando possibilidades como
    • integração com o restante da base de código
    • gerenciamento de pacotes muito mais simples
    • uso direto de novos recursos do ecossistema ativo de TypeScript
  • Ao longo da transição, a equipe aprendeu vários aspectos do TypeScript, incluindo resolução de imports, sistema de módulos e geração de código JavaScript

1 comentários

 
GN⁺ 2024-05-05
Opiniões do Hacker News
  • Já é surpreendente que o Figma tivesse uma linguagem customizada para JS, e é ainda mais surpreendente que ela fosse mais rápida que TS
    Mas também é interessante que, no fim, tenham migrado para o TS, que era mais lento
    Esse tipo de coisa aparece com bastante frequência: uma empresa cria sua própria tecnologia no começo e, depois de crescer, migra para algo mais “padrão”

    • Parece um fluxo bem típico. No início, uma pessoa excepcional como Evan configura toda a toolchain e o núcleo do produto, e depois sai, é deixada de lado ou perde o interesse
      Quando a equipe e o produto ficam muito maiores, acabam levando a plataforma de volta para uma stack mais familiar e amplamente usada
      O sucesso desse tipo de transição depende muito de quão sólida é a cultura de engenharia da organização. Não tenho provas, mas parece que Evan e os fundadores criaram uma excelente cultura de engenharia no Figma, e ela parece ter resiliência suficiente para corrigir erros quando eles surgirem
    • Se você ler com atenção, todas as partes sensíveis a desempenho foram movidas para C++/WASM
      Nesse ponto, só sobrou o código de cola, e a linguagem customizada deixou de ter razão para existir
    • Algo parecido aconteceu na comunidade PHP. O Facebook é o exemplo principal, mas o Yahoo também tinha várias otimizações internas, e vi outras empresas fazendo ajustes próprios por desempenho ou segurança
      Então chega um momento em que a comunidade alcança esse nível e passa a ter uma força de tração maior que a da empresa; aí faz sentido migrar para a implementação padrão
      Também vejo o caminho do Borg do Google para o k8s como algo um pouco parecido. Só que, nesse caso, o Google convidou a comunidade para se reunir em torno de um padrão que eles próprios criaram
    • Ser mais rápido que TS em um uso restrito é uma barra relativamente baixa. Mas, conforme a escala aumenta, essa barra precisa ser avaliada junto com o pool de desenvolvedores disponível no mercado
      É preciso comparar o custo de desenvolvedores capazes de olhar para um framework totalmente novo pela primeira vez e aprendê-lo com o valor de desenvolvedores que já têm experiência documentada na plataforma ou tecnologia usada
      Com uma solução totalmente interna, é possível extrair desempenho enorme de qualquer linguagem ou framework, mas, quanto mais situações o projeto precisa lidar, maior é o número de desenvolvedores necessário para manutenção, até chegar a um ponto em que software de prateleira se torna melhor, mesmo sendo mais lento e não se encaixando perfeitamente nos requisitos
      Se, no longo prazo, for possível manter critérios de contratação de nível unicórnio, talvez dê para sustentar uma stack totalmente customizada. Mas a maioria das organizações precisa migrar para algo que uma contratação nova mediana consiga manter no futuro, e isso geralmente significa produtos de fornecedores de software famosos e entediantes
    • Coisas incomuns e esquisitas, como uma linguagem customizada, são ruins para o currículo se você não for quem a implementa, mas apenas um usuário, por exemplo um desenvolvedor que escreve nessa linguagem
      Para pessoas ambiciosas, isso pode ser um motivo para sair; também dificulta a contratação e alonga o onboarding. Mais tarde, quando a linguagem customizada for abandonada, será necessário um grande projeto de transição
      Às vezes pode valer a pena se o ganho de desempenho for enorme, se permitir escrever código mais seguro ou se a intenção for contratar apenas pessoas que gostam de aprender linguagens novas
  • Trabalhei nesse projeto. Escrevi um pouco mais no Twitter: https://twitter.com/andrew_k_chan/status/1786769203912925477
    O título do post é enganoso. No Figma, usamos Typescript em outras partes da codebase por quase 10 anos e, durante praticamente todo esse período, havia mais código em Typescript do que em Skew
    Como o post do blog explica, o Skew foi usado no motor mobile, depois no player de prototipagem, no recurso de espelhamento e em uma ou duas áreas de produto das quais não me lembro

  • O Skew não era apenas um pouco mais rápido que o TypeScript
    Segundo Evan Wallace, ex-CTO do Figma, ele era 1,5 a 2 vezes mais rápido graças a otimizações possibilitadas por um sistema de tipos mais estrito

    • É uma pena que os navegadores não tenham continuado o desenvolvimento do modo "use strong" do JS
      Parece que havia desafios bem grandes, mas reduzir parte da dinamicidade para permitir otimizações mais previsíveis soa como uma boa troca para apps de produção
    • A afirmação de que “era 1,5 a 2 vezes mais rápido graças a um sistema de tipos mais estrito” provavelmente não é tão simples assim
      Se os caminhos de execução mais críticos forem otimizados, há uma boa chance de que o ganho de 2 vezes desapareça
      Claro, aí seria preciso escrever JS otimizado à força em vários lugares, e dá para dizer que isso afeta a legibilidade. É verdade, mas a alternativa era usar uma linguagem de compilação para JS completamente nova, com pouco suporte de ferramentas e pouca adoção
      Olhando agora, parece que não valeu tanto a pena, e o post do blog dá a impressão de estar embelezando um pouco uma escolha técnica anterior que era controversa
    • A diferença de velocidade de 2 vezes foi a parte mais surpreendente de todo o texto
      Fico curioso se seria possível restringir o uso de TypeScript ao subconjunto que entrega esse desempenho
  • Ao completar uma operação como const [a, b] = function_that_returns_an_array(), é interessante notar que o JavaScript não indexa diretamente o array, mas cria um iterador que percorre o array
    Por que o JS simplesmente não indexa arrays diretamente na atribuição por desestruturação?

    • Qualquer objeto pode se tornar um objeto iterável se você adicionar Symbol.iterator, e a desestruturação precisa funcionar também nesses objetos. Até o próprio Symbol.iterator de arrays pode ser alterado, então a VM precisa lidar com isso
      Array.prototype[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }
      [...[4, 5, 6]]
      [1, 2, 3]
      O desempenho terrível do protocolo de iteradores já foi discutido na época, mas foi ignorado sob o argumento de que a análise de escape resolveria isso [0]. Quase 10 anos depois, a análise de escape ainda não resolveu, usa muito GC e continua ruim. É uma especificação ruim, projetada por pessoas que não tinham desempenho em mente
      Se o usuário não tiver alterado Symbol.iterator, poderia fazer sentido o engine especializar a desestruturação e o splicing de arrays para eliminar o overhead do protocolo de iteradores, mas isso é outro problema complicado
      [0] https://esdiscuss.org/topic/performance-of-iterator-next-as-...
    • Parece ser por causa da existência de coisas como proxies
      O mais estranho é que, em alguns engines JavaScript, ao desestruturar arrays com dois ou três elementos, a desestruturação de objeto no estilo {0: foo, 1: bar} pode ser mais rápida
  • O texto fala apenas das conversões feitas uma vez durante a migração da base de código, e não aborda muito o impacto contínuo na experiência do desenvolvedor causado pela perda das conveniências do Skew
    Por exemplo, em TypeScript é fácil acabar em situações em que você precisa importar arquivos na ordem correta, ou tudo pode quebrar. Além disso, desestruturação é lenta, então não deve ser usada se desempenho importar minimamente
    Depois de usar TypeScript por alguns anos, vejo que há dezenas dessas armadilhas; algumas vêm do JavaScript e outras não. Se há muitos engenheiros, no mínimo é necessário um guia de estilo enorme
    Fico curioso se houve engenheiros que lamentaram o desaparecimento do Skew

    • Garantir que os desenvolvedores ficassem satisfeitos com o resultado certamente era importante. Por isso, incluímos no rollout uma etapa em que os desenvolvedores continuavam escrevendo Skew, mas as mudanças eram automaticamente registradas em check-in no TS gerado
      Assim, os desenvolvedores podiam ver nos reviews de PR como o resultado ficaria e relatar problemas
      Em termos de desempenho e correção em runtime, é verdade que o TS tem algumas armadilhas. Problemas como desestruturação de arrays foram detectados com instrumentação e monitoramento rigoroso
      A perda de alguns recursos do Skew, como sobrecarga de operadores e tipos inteiros, certamente fez falta. Mas a migração acabou sendo uma decisão tomada pela equipe inteira, e eu também acho que foi a decisão certa
  • O texto fala em “recursos modernos de JavaScript, como async/await, e um sistema de tipos mais flexível”; então o Skew só tinha callbacks?

    • Talvez tivesse Promise?
  • Como alguém que não conhece bem o Figma, fico curioso sobre por que usam WebAssembly

    • Você provavelmente sabe que o Figma é um software de design de UX. Ou seja, no fundo é um programa gráfico. Ele desenha formas, rola a tela e dá zoom
      O Figma faz isso extremamente bem. Mesmo em canvases muito grandes, com muitas telas de UI complexas, ele é incrivelmente responsivo
      Hoje em dia, quase nenhum aplicativo desktop roda tão bem assim. Tenho certeza de que esse tipo de otimização foi uma parte importante do sucesso do Figma
    • Edição vetorial envolve muitas tarefas limitadas pela CPU. O Evan escreveu um artigo sobre a migração para WASM: https://medium.com/figma-design/webassembly-cut-figmas-load-...
    • O objetivo inicial era ser o Photoshop dentro do navegador
  • Há detalhes interessantes em outro post do blog sobre como o Figma criou uma DSL TypeScript + compilador customizados para resolver um problema de segurança, isto é, permissões
    https://www.figma.com/blog/how-we-rolled-out-our-own-permiss...

  • Isso dói um pouco. Toda grande empresa tem suas próprias ferramentas internas, linguagens, algo tipo Kubernetes. Por que não compartilham?
    Se o Skew fosse open source, talvez pudesse ter se tornado um TypeScript melhor

    • O Skew é open source, mas não é mais mantido: https://github.com/evanw/skew
    • Não gosto muito dessa perspectiva; normalmente ela vem de falta de experiência com open source
      Ser open source não significa que contribuições gratuitas simplesmente aparecem. Cada PR não trivial precisa passar por uma revisão longa, discussão e talvez até reescrita
  • Gosto de TypeScript e tenho até sistemas full-stack em TypeScript, mas ele não é perfeito. Configurar TypeScript em um monorepo é um pesadelo.
    Para fazer tudo ficar compreensível junto com os pacotes internos dentro de um monorepo pnpm, é preciso ajustar manualmente muitos tsconfig.json para que todos os caminhos batam entre si.
    E, até o excelente pacote tsx aparecer, a cadeia de ferramentas de produção era praticamente impossível de manter.
    Além disso, é absurdamente lento. Havia um problema em que o Zod deixava o desempenho do language server do TypeScript bem mais lento, então no fim tivemos que adotar referências de projeto e desativar o redirecionamento de referências de projeto.
    No geral, há muito espaço para melhorar o TypeScript. Especialmente em monorepos e em desempenho.

    • É preciso levar em conta que o Zod é um caso bastante excepcional em termos de quanto ele pode desacelerar a verificação de tipos. Por exemplo, em um teste em https://dev.to/nicklucas/typescript-runtime-validators-and-d..., ele chega perto de 300 ms, enquanto outras bibliotecas quase nem chegam a 100 ms.
    • O Zod é conhecido por ser lento. Por exemplo, Typebox é muito mais rápido. Não estou dizendo para trocar; quero dizer que isso não é um problema do TypeScript nem algo que o TypeScript deva resolver.
      O problema do monorepo soa como se vocês não estivessem usando a mesma configuração em todos os pacotes. Se for isso, eu corrigiria primeiro impondo a mesma configuração e padrões de código. Isso também não é um problema do TypeScript.
      Por acaso vocês usavam ts-node antes do tsx? Se sim, tsx é muito mais robusto. Simplesmente funciona bem.
    • Não é o TypeScript que deve dar suporte a uma combinação específica de escolhas de ferramentas e bibliotecas; ao contrário, são as ferramentas e bibliotecas que devem se ajustar.
      Opero um monorepo de porte médio, com vários apps e muitos serviços, em sua maioria em TypeScript, e mesmo com uma configuração tediosa baseada em npm workspaces ele funciona razoavelmente bem.
    • Dizer que o TypeScript é lento porque o Zod é lento é parecido com dizer que C++ é lento porque JavaScript é lento.
      Isso não quer dizer, de forma alguma, que o TypeScript seja rápido, mas, como ele é escrito em JavaScript, também é difícil que seja rápido.
      Ainda assim, deixar o TypeScript executar código para inferência de tipos em uma aplicação de grande porte parece um problema criado por você mesmo.