Language-tools: Visão geral das abordagens em um servidor de linguagem

Criado em 24 mar. 2020  ·  37Comentários  ·  Fonte: sveltejs/language-tools

Este tópico se destina a uma visão geral das abordagens sobre como implementar um servidor de linguagem para arquivos svelte. A discussão sobre uma solução preferida também pode ser feita aqui.

Estado atual do servidor de linguagem

Atualmente, o realce de sintaxe e alguns autocompletar básicos funcionam para arquivos esbeltos.
Entretanto, o rich intellisense não é fornecido, não sabe sobre todos os arquivos de texto digitados no espaço de trabalho e também não tem informações de tipo sobre os componentes esbeltos.
O mesmo é verdadeiro para a parte html onde a sintaxe svelte especial é destacada, mas o intellisense não está funcionando. O vscode-html-languageservice é usado, o qual não conhece a sintaxe especial por padrão.
O servidor de linguagem atual usa o compilador svelte para analisar arquivos e obter diagnósticos como "nenhum atributo alt nesta tag img".
Uma análise mais aprofundada: Comentário

Abordagens / soluções existentes

Isenção de responsabilidade: não sou o autor de nenhuma das soluções existentes, no entanto, examinei o código. Se alguma das informações estiver incorreta ou não detalhada o suficiente, ou você estiver faltando uma solução, fique à vontade para comentar, eu irei ajustar.

https://github.com/alexprey/sveltedoc-parser

Usa htmlparser2 para analisar a parte html de um arquivo svelte. Usa ganchos do analisador html para adicionar análise customizada e extrair informações relevantes.
Usa espree para analisar as partes do script de um arquivo esbelto. Em seguida, percorre o AST para extrair informações relevantes.
Fornece os resultados em um formato JSON.

https://github.com/ArdenIvanov/svelte-intellisense o usa para analisar arquivos svelte. Ele usa o resultado para anexar algum comportamento adicional.

Como ele usa um analisador javascript, não funcionará com o typescript.

https://github.com/halfnelson/svelte2tsx

Usa o compilador svelte para analisar a parte HTMLx de um componente svelte. Em seguida, transforma o HTMLx mais a parte do script original em um arquivo tsx com um transformador autoescrito.

https://github.com/simlrh/svelte-language-server/blob/feature/extend-ts-support/src/plugins/ts-svelte/service.ts (um fork do svelte-language-server) o usa para crie arquivos shadow-tsx de arquivos svelte. Em seguida, ele usa o serviço de linguagem do typescript, mas faz o proxy das solicitações: ele substitui o caminho do arquivo original pelo caminho do arquivo para o arquivo tsx gerado. O servidor de linguagem typescript, portanto, verifica os arquivos tsx gerados. Os resultados são transformados de volta para obter as posições corretas dos documentos.

https://github.com/halfnelson/svelte-type-checker-vscode é outro servidor de linguagem que usa a biblioteca svelte2tsx.

https://github.com/marcus-sa/svelte-ts

Descrição retirada do comentário do implementador :

Compilador:

  1. O compilador lê todos os arquivos fonte
  2. Tudo, exceto o conteúdo das tags de script, é jogado fora
  3. O código-fonte do script é compilado para arquivos JS e de declaração válidos.
  4. Volte para a etapa 2, onde substitui a fonte TS nas tags de script pelo JS compilado
  5. O compilador Svelte compila todo o arquivo fonte
  6. A compilação de saída da etapa acima é armazenada em cache, onde o HTML AST é então usado no verificador de tipo.

Ele consome as variáveis ​​e funções exportadas de dentro de um script Svelte e, em seguida, o gera como um arquivo de declaração que contém uma classe que estende o SvelteComponent de tempo de execução.

Typechecker:

  1. O verificador de tipo encontra todos os componentes no AST em cache.
  2. Verifica se todos os componentes do modelo são uma classe válida que estende SvelteComponent
  3. Verifica se há declarações válidas, propriedades etc.

Outras abordagens

Veja os comentários deste tópico.

Comentários muito úteis

OK ótimo! Vou prosseguir e fazer alguns PRs, reestruturando parte do código e preparando-o para que possamos paralelizar o trabalho em itens individuais.

Todos 37 comentários

Então nenhum deles está usando o analisador Svelte?

svelte2tsx remove as tags de estilo / script e usa o compilador svelte para analisar a parte htmlx.
svelte-ts usa o compilador svelte como uma segunda etapa.
O servidor de linguagem atual não usa o compilador svelte para análise.
Vou atualizar o post inicial.

Eu não tinha certeza se deveria compartilhar isso, mas parte disso pode ser útil. No ano passado (julho / agosto de 2019), comecei a brincar com a digitação de Svelte, sabendo que estava fazendo um trabalho inútil apenas para aprender. Ele suporta uma quantidade desconhecida de uma solução completa, talvez 30%, e tem algumas limitações (mais sobre isso depois). Não existem testes ou documentação externa porque sempre considerei isso um beco sem saída.

A compilação Svelte funciona como hoje com um pré-processador TS sem verificação de tipo. Se você quisesse tipos em sua marcação HTMLx, não apenas sua tag de script, você precisaria de um pré-processador para lidar com isso, ou o Svelte precisaria lidar com TS internamente.

O typechecker é uma etapa de construção separada que usa o código TypeScript não pré-processado de blocos script e a marcação AST de svelte.compile para criar um arquivo Svelte TypeScript virtual exclusivamente para fins de verificação de tipo. (aqui é onde o plugin Rollup cria o arquivo virtual e então o verifica ) É semelhante à estratégia de @halfnelson , mas diferente por produzir TS normal, não TSX, então na marcação ele não tenta digitar tags HTML e atributos, apenas o material dentro de tags de bigode e componentes Svelte. Ele transforma os componentes Svelte em construtores que ganham new ed com seus adereços. Eu comecei a apoiar muitas das construções de Svelte antes de perceber que me empolguei mais do que pretendia, e o progresso futuro deveria ser Engenharia Séria.

A API do compilador TypeScript é usada para construir um programa na memória e verificar o

O arquivo Svelte TS virtual é gerado com um mapa de origem que é então usado para mapear os diagnósticos do TypeScript de volta para a fonte Svelte original . Foi muito bom ver os erros de tipo apontando para a fonte Svelte, mas eu não tinha confiança na minha abordagem geral.

Um problema que me lembro e que não consegui resolver foi com diagnósticos que não eram específicos o suficiente em alguns casos. Um exemplo do IIRC foi com a verificação de tipos de props do construtor de componente Svelte - VSCode de alguma forma apontaria para erros de prop individuais, mas os diagnósticos de TS que eu estava obtendo apontavam apenas para o construtor, não para a propriedade do problema.

Se você olhar o código, saiba que as partes estão uma bagunça e alguns comentários podem estar desatualizados. Eu originalmente usei magic-string e em algum momento mudei para source-map . E lembre-se de que foi um esforço fadado ao fracasso desde o início! Observe que tudo isso está no branch ts desse repo, não master .

Aqui estão os 4 arquivos mencionados acima:

Eu fiz algumas pesquisas no código-fonte do servidor de linguagem svelte atual. Aqui estão meus pensamentos sobre a parte datilografada dele:

Visão geral

O servidor de linguagem funciona amplamente assim:

  • DocumentManager lida com todos os documentos que são abertos.
  • TypescriptPlugin registra-se em DocumentManager para ser invocado em eventos de servidor de linguagem ("doHover" etc). Ele não se registra diretamente: ele é envolvido por wrapFragmentPlugin que garante que TypescriptPlugin apenas obtenha o conteúdo dentro da tag script . O mapeamento das posições (do mouse / cursor de texto) é feito em wrapFragmentPlugin para garantir que as informações sejam mostradas na posição correta.
  • TypescriptPlugin usa service , um wrapper de serviço de linguagem typescript. Basicamente, delega todos os eventos a este servidor de linguagem, com algum mapeamento para aderir aos tipos de retorno do método.
  • service pega o documento para fazer o trabalho, bem como um método createDocument de TypescriptPlugin . service mantém seu próprio mapa de documentos que no momento são basicamente apenas um delegado ao documento de DocumentsManager . Sempre que service é invocado a partir de TypescriptPlugin , service atualiza seu mapa de documentos com o documento fornecido (após empacotá-lo). createDocument seria invocado se o serviço de linguagem abrir um documento que ainda não faz parte do mapa de documentos - mas isso nunca acontecerá, pois o serviço de linguagem de typecript não encontra outros módulos esbeltos (consulte a próxima seção).

Deficiências atuais e ideias para melhorias

  • Uma vez que a tag de script é fornecida como está para o serviço de linguagem typescript, ela não pode lidar com sintaxe svelte especial ($).
  • O serviço de linguagem typescript tenta pesquisar outros arquivos que são referenciados no arquivo svelte aberto no momento. Para .ts / .js arquivos isso funciona, para .svelte arquivos não. Motivo: o serviço de linguagem datilografada não sabe sobre a terminação .svelte , então ele apenas assume que é um arquivo datilografado normal e procura por arquivos como ../Component.svelte.ts , o que está errado. Para corrigir isso, precisamos implementar o método resolveModuleNames em LanguageServiceHost e redirecionar todas as pesquisas de arquivo .svelte.ts para .svelte . Em seguida, o serviço de linguagem percorrerá todos os arquivos esbeltos. NOTA: Para implementar isso, também precisamos corrigir o seguinte bug: Como o serviço de linguagem de digitação agora percorreria todos os arquivos, ele invocaria createDocument de TypescriptPlugin . Mas isso atualmente retorna todo o documento, não apenas o conteúdo dentro da tag do script. Este é um bug dentro do wrapFragmentPlugin não envolve openDocument .
  • Para implementar recursos mais avançados como "ir para o arquivo esbelto" clicando no nome de importação do componente esbelto, ou fazer com que o texto digitado grogue os sinais $, precisamos adicionar um pré-processador. Este pré-processador adicionaria de alguma forma as partes que faltam para o texto datilografado. Idealmente, todo o material adicional pode ser colocado em cima do código existente, de modo que mapear as posições seja tão fácil quanto mapear as posições com deslocamentos. No entanto, isso representa outro desafio: agora teríamos dois mapeamentos de posição: um de todo o arquivo svelte para a tag de script e outro da tag de script para o código typecript pré-processado. Não tenho certeza neste ponto se este é o caminho a percorrer ou se devemos retrabalhar como TypescriptPlugin (e outros) obtêm seu documento - talvez a extração da tag do script e o pré-processamento devam ser feitos em uma única etapa . Isso significaria mudar ou substituir totalmente wrapFragmentPlugin . Outro argumento para retrabalho é que, para determinar corretamente coisas como "função não utilizada", temos que levar em conta a parte html, de modo que a tag de script não é suficiente.

Pensamentos?

Em resolveModuleNames , aqui está como um membro da equipe TypeScript fez isso para Vetur , e aqui está uma versão semelhante que fiz para Svelte, que inclui um comentário que menciona uma estratégia alternativa que funcionou, mas copiar Vetur parecia melhor.

@dummdidumm , você se importa em atuar como líder de tecnologia e revelar essas coisas para questões específicas? Eu ficaria feliz em oferecer tempo de devolução voluntário, mas considero muito disso opressor e funcionaria melhor em questões pequenas e definidas. A menos que haja alguma grande mudança arquitetônica que precise ser feita (parece que não?)

Basicamente, basta abrir um monte de problemas e deixar as pessoas reivindicá-los e gerenciá-los como nosso líder de tecnologia informal nisso. Eu me ofereceria para fazer isso, mas, honestamente, ainda não senti a dor ainda haha

também com link para a postagem LSP original da orta para outras pessoas que estão navegando em https://github.com/sveltejs/svelte/issues/4518

Claro, eu adoraria ajudar nisso, mas agora sou apenas um cara aleatório que está muito animado com o servidor de linguagem, pesquisando o código e fazendo algumas sugestões 😄 Também preciso de mais algum tempo para me familiarizar com o código, sua arquitetura e lsp em geral. Mas estou definitivamente disposto a levar as coisas adiante. @orta já que você é o mantenedor deste repositório, o que você acha disso?

👋 Sim, nós dois somos pessoas aleatórias que simplesmente se importam - eu nem mesmo tenho nenhuma base de código esbelta para acionar minha coceira de "isso deveria ser melhor". Então, estou muito feliz em ver qualquer movimento - @dummdidumm - afaste-se e eu irei apoiá-lo e tentar garantir que tudo corra bem

OK ótimo! Vou prosseguir e fazer alguns PRs, reestruturando parte do código e preparando-o para que possamos paralelizar o trabalho em itens individuais.

Talvez pudéssemos combinar @ryanatkn , a abordagem de svelete-ts e a abordagem de variável de eslint-plugin-svelte3 :

  1. Use o pré - pré -processar o script em js.
    Aqui, poderíamos simplesmente transpilar para a versão mais recente ou para a próxima versão do ES para fazer o mínimo de transformação possível.
  2. Vamos compilar o código fonte pré-processado.
  3. Podemos então injetar vars de resultado onde injected = true na instância ts source ou ts AST e, em seguida, usá-lo com o mapa de origem para fornecer verificação de tipo e preenchimento automático.

O script pode ter três fragmentos: módulo <script context="module"> , instância e bigode (modelo). A parte do bigode pode ser apenas despejada em um template.js por enquanto. Mas, a longo prazo, poderíamos refletir o modelo em tsx ou ts simples, como a abordagem de ryanatkn.

O módulo vars, onde module = true , seria injetado no template.js e na instância, mas não o contrário.

Desta forma, não temos que reimplementar como encontrar a variável reativa $: a = 1 ou $ -prefixed store no typescript AST. Essas variáveis ​​injetadas pelo compilador svelte derivam de uma instrução de nível superior e a estamos transpilando para o ES mais recente. Portanto, geralmente não deve ser renomeado por texto digitado.

Sobre a variável reativa $: a ela poderia ser escrita assim:

$: a = 1

`` `ts
deixe um: número
$: a = 1

```ts
$: a = someFunction(b) as Type

Todos são ts válidos que não precisam ser transformados antes de transpilar

E US $ store -prefixed podemos criar uma função genérica como esbelto loja / get ao extrato tipo de armazenamento

/** injected */
let $store = getType(store)
  1. Use o pré - pré -processar o script em js.
    Aqui, poderíamos simplesmente transpilar para a versão mais recente ou para a próxima versão do ES para fazer o mínimo de transformação possível.
  2. Vamos compilar o código fonte pré-processado.
  3. Podemos então injetar vars de resultado onde injected = true na instância ts source ou ts AST e, em seguida, usá-lo com o mapa de origem para fornecer verificação de tipo e preenchimento automático.

Eu acho que svelte-ts faz algo assim. Parece uma boa ideia. Em injected = true : qual código exatamente tem essa propriedade?

O script pode ter três fragmentos: módulo <script context="module"> , instância e bigode (modelo). A parte do bigode pode ser apenas despejada em um template.js por enquanto. Mas, a longo prazo, poderíamos refletir o modelo em tsx ou ts simples, como a abordagem de ryanatkn.

O módulo vars, onde module = true , seria injetado em template.js e instância, mas não o contrário.

Desta forma, não temos que reimplementar como encontrar a variável reativa $: a = 1 ou $ -prefixed store no typescript AST. Essas variáveis ​​injetadas pelo compilador svelte derivam de uma instrução de nível superior e a estamos transpilando para o ES mais recente. Portanto, geralmente não deve ser renomeado por texto digitado.

Então você deseja dividir o arquivo em um .ts -file virtual e um .template -file? Por que não colocar as partes do bigode na parte inferior do arquivo .ts ? Dessa forma, também nos livraríamos dos avisos de "método não utilizado". Sobre a caminhada pelo AST: Sim, acho que podemos ser inteligentes aqui e pular muito a análise do AST porque tudo o que é referenciado no modelo deve ser de nível superior no script.

Sobre a variável reativa $: a ela poderia ser escrita assim:

$: a = 1
let a: number
$: a = 1

ou

$: a = someFunction(b) as Type

Todos são ts válidos que não precisam ser transformados antes de transpilar

É possível inferir o tipo? Talvez isso seja apenas algo que deixamos como está e se o usuário quiser usar o texto datilografado, ele deve definir let a: number ele mesmo. Como alternativa, poderíamos inserir let a; , mas então seria any .

E US $ store -prefixed podemos criar uma função genérica como esbelto loja / get ao extrato tipo de armazenamento

/** injected */
let $store = getType(store)

Sim, isso parece bom. Outra ideia que tive foi construir getters / setters.

Eu acho que svelte-ts faz algo assim. Parece uma boa ideia. Em injected = true : Qual código exatamente tem essa propriedade?

O resultado da compilação possui uma propriedade vars, que é uma matriz de objeto que possui a seguinte propriedade:
nome, export_name, injetado, módulo, mutado, reatribuído, referenced_from_script e gravável
você pode ver a API de compilação svelte para mais informações

Então você deseja dividir o arquivo em um arquivo .ts virtual e um arquivo .template? Por que não colocar as partes do bigode na parte inferior do arquivo .ts? Dessa forma, também nos livraríamos dos avisos de "método não utilizado".

Esta é a abordagem de eslint-plugin-svelte3 . Também pensei se queremos suportar ts no template porque não há atributo para especificar o idioma.

É possível inferir o tipo?

Uma vez que esta fonte virtual é usada apenas para verificação de tipo e preenchimento automático, presumo que seja possível apenas copiar a primeira instrução de atribuição para inicializá-la, mas parece que precisa de mais trabalho.

É possível inferir o tipo? Talvez seja apenas algo que deixamos como está e se o usuário quiser usar o texto datilografado, ele mesmo deve definir let a: number. Como alternativa, poderíamos inserir let a ;, mas então seria qualquer.

Parece que a inferência normal não nos leva até lá. (você quis dizer inferência em tempo de compilação? não tenho certeza se isso poderia funcionar)

let a; // type is "any" initially
$: a = 5;
a; // type is now "number", good
const cb = () => a; // type is implicitly "any", noo

O motivo é que, no fluxo de código linear, o TypeScript pode ver que a não foi reatribuído, mas os escopos de função aninhados não têm essa garantia, e o TypeScript erra no lado da segurança de tipo.

exemplo de playground

Exatamente, essas eram minhas preocupações com isso. Mas, como @ jasonlyu123 apontou, poderíamos apenas copiar a definição (tudo após = ).

Antes

$: a = true;

Depois de

let a = true;
$: a = true;

Mas não sei se isso é viável em todas as circunstâncias. Especialmente com objetos grandes onde alguém implementa uma interface, isso não inferirá tudo como o usuário pode esperar. Além disso, a massa de cópia pode ficar bem grande. Portanto, estou ainda mais a favor de "deixar o usuário digitar".

Obrigado por esclarecer, eu entendi mal o que @ jasonlyu123 disse. Ele corrige o problema de escopo de função aninhado que mencionei.

Existe um exemplo de quando esse não é um bom comportamento padrão? Parece que faz a coisa ergonômica certa na maioria das vezes, e outros casos podem ser consertados manualmente. Aqui estão alguns exemplos.

Mhm, acho que você está certo. É a coisa mais ergonômica. O que me preocupa é se o tipo inferido está errado, por exemplo propriedade é string vez de uma união definida de strings ( 'a' | 'b' ). Mas como você disse, esses casos não são tão comuns e o usuário ainda pode digitá-lo manualmente.

Há uma parte de mim que diz: "Ótimo, a sintaxe let explícita funciona muito bem", mas isso acabaria não funcionando com suporte a JS fora da caixa

Que tal substituir o primeiro rótulo para permitir:

$: a = 1
$: a = 2

obter a transformação para

/** injected  */
let a = 1
$: a = 2

Eu repenso um pouco e não consigo encontrar a vantagem de "copiar definição" sobre "substituir $: por let ". Vocês podem pensar em alguma desvantagem de substituir $: por let ?

Sobre a diferença de tipo inferida em relação ao que o usuário esperava, podemos fornecer ações de refatoração para convertê-la em definição manual? Esta ação irá pedir usuário para tipo manual da variável como este , ou refatorar o seu tipo inferido, se possível.

antes

$: a = 'const1'

depois de

let a : AskUserForType
$: a = 'const1'

Também pensa nas coisas que você aponta, Aprendeu algo 👍

Ha, boa ideia, basta substituir totalmente $: por um let . Também não conheço nenhuma situação em que isso acabaria mal.
Também poderíamos pensar em fazer sempre a transformação, não só para a primeira gravadora. Em seguida, haveria um erro "não é possível redeclarar" que sugere ao usuário "uau, defini essa variável duas vezes" - ou há situações em que isso seria viável e não um erro?

Legal, também não consigo pensar em nenhuma razão para substituir o rótulo. (já que break $ nunca trabalha dentro de atribuições, certo?)

Para redeclarar a mesma variável reativa, parece que o Svelte permite isso e funciona na ordem que você esperava. ( veja este exemplo repl ) Eu pessoalmente não me vejo usando esse padrão intencionalmente e gostaria do erro, mas ele está no território de interpretar a semântica de Svelte, então não acredite na minha palavra!

OK, então redeclarar é um padrão que realmente funciona. Eu diria que é melhor permitirmos isso então - ou criar um sinalizador de recurso "redeclarar não permitido" posteriormente, que está desativado por padrão.

@halfnelson criou um servidor de linguagem baseado em sua abordagem svelte2tsx em https://github.com/halfnelson/svelte-type-checker-vscode . Eu realmente amo como tudo isso já funciona bem, mas ainda prefiro não usar tsx como uma saída intermediária porque temo que possa ser um pouco lento e ter algumas limitações - mas isso é apenas minha intuição. Ainda assim, poderíamos aprender muito com svelte2tsx: como o fluxo de análise-compilação-transformação é feito e também o amplo conjunto de testes. Como os outros veem a abordagem de transformar o código em tsx?

Pensei em como outras transformações poderiam ser. Aqui estão alguns exemplos. Feedback bem-vindo.

Como uma definição de componente se parece

Original:

<script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();

    export let todo: string;

    function doneChange() {
        dispatch('doneChange', true);
    }
</script>

Convertido:

export default class Component {
   constructor(props: {todo: string; 'on:doneChange': (evt: CustomEvent<boolean>) => void}) {}
}

import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();

export let todo: string;

function doneChange() {
    dispatch('doneChange', !todo.done);
}

Explicação:
De alguma forma, pegue todos os adereços (fácil) e eventos despachados (difícil) e adicione-os a um grande objeto de adereços. Adicionar eventos com on: -prefix deve minimizar as colisões e tornar as sugestões de preenchimento automático mais fáceis de implementar.

(nos exemplos a seguir, a definição de componente é omitida por questões de brevidade)

Usar variável no modelo

Original:

<script>const bla = {bla: 'bla'};</script>
<p>{bla.bla}</p>

Convertido:

const bla = 'bla';
const _bla = bla.bla;

Explicação:
Adicione elementos const arbitrários, talvez prefixados com algum caractere Unicode obscuro, para verificar os usos corretos das variáveis ​​e também fazer com que o aviso "não utilizado" desapareça. Talvez precisemos pensar em como nos livrar do aviso _bla utilizado ...

Use ouvinte de evento nativo

Original:

<script>function bla() {return true;}</script>
<button on:click={bla}>bla</button>

Convertido:

function bla() {return true;}
const btn = document.createElement('button');
btn.addEventListener('click', bla);

Explicação:
Ao usar ouvintes de eventos nativos, crie o elemento correspondente e, em seguida, adicione um ouvinte de eventos. Desta forma, aproveitamos os addEventListener -tipos que têm sobrecargas para praticamente todos os ouvintes: .addEventListener('click', ...); -> evento é do tipo MouseEvent . Este padrão seria usado depois de verificar se o elemento é outro componente esbelto que tem o evento definido (veja também o próximo exemplo). Deficiências: E se o Svelte for usado em um ambiente não-web (Nativescript)?

Use outro componente

Original:

<script>
import Bla from './bla.svelte';
function blub() {}
</script>
<Bla bla={1} on:blubb={blub} />

Convertido:

import Bla from './bla.svelte';
function blub() {}
const bla = new Bla({bla: 1, 'on:blubb': blub});

Explicação:
Cada componente obtém uma definição de classe, então apenas instanciamos essa classe.

Cada loop

Original:

{#each todos as todo,i (todo.id)}
    <p>{todo.text}</p>
{/each}

Convertido:

todos.forEach((todo, i) => {
    const _todoText = todo.text;
});

Explicação:
forEach parece ser o mais fácil de converter. Dentro, podemos aplicar recursivamente todos os outros recursos.

Se

Original:

{#if x === 1}
    <p>if</p>
{else if x === 2}
    <p>elseif</p>
{else}
    <p>else</p>
{/if}

Convertido:

if (x === 1) {

} else if (x === 2) {

} else {

}

Explicação:
Bastante autoexplicativo.

Peças faltando

  • slots
  • componentes especiais de svelte:x
  • outras diretivas de elemento / componente, como use:action , transition , bind
  • $ -sintaxe

O que é que vocês acham? Essas transformações são viáveis ​​e realizáveis? Perdi algo?

Sobre a classe de componente, eu preferiria estender a classe SvelteComponent do próprio svelte, mas não sei se isso é fácil de implementar ou não

import { SvelteComponent } from "svelte";

export default class Component extends SvelteComponent {
    constructor(options: ComponentOption<{ propA: string }>) {
        super(options)
    }

    $on(
        event: 'input',
        callback: (event: CustomEvent<string>) => void
    ): () => void
    $on(
        event: 'click',
        callback: (event: CustomEvent<number>) => void
    ): () => void 
    $on(
        event: string,
        callback: (event: CustomEvent<any>) => void
    ): () => void {
        return () => { }
    }
}

e importar essas interfaces

interface ComponentOption<TProps, TSlot = undefined> {
    target: Element,
    props: TProps | SlotProp<TSlot>
}

type SlotOption<T> = [unknown, unknown, unknown]

interface SlotProp<TSlot> {
    $$slots: Record<string, SlotOption<TSlot>>
}

Notado que $$slots é provavelmente a API interna do svelte, acabei de explorá-la no código compilado

A razão para isso é que ele pode ser usado para emitir .d.ts que poderia ser usado para digitar vanilla ts / js.
Também porque, embora estranho o suficiente, esta é uma sintaxe válida:

<script>
onMount(() => {
    new Component({
        target: document.getElementById('foo')
    })
})
</script>

Diretivas de elemento

Transition , use:action e outras diretivas de elemento podem ser transformadas como

fade(null as Element, {  } /* option in attribute*/)

Aguardam

{#await promise}
    <p>...waiting</p>
{:then number}
    <p>The number is {number}</p>
{:catch error}
    <p style="color: red">{error.message}</p>
{/await}

para

    promise.then(number => {  number })
    promise.catch(error => { error.message })

vincular: este

<script>
let a;
</script>
<div bind:this={a}><div>

para

let a;
onMount(() => {
 a = document.createElement('div')
})

precisava envolvê-lo no retorno de chamada porque não está imediatamente disponível

context = "módulo"

Isso é um pouco complicado

<script context="module">
 let count = 1;
</script>

<script>
import _ from "lodash"
let b;
</script>

deve ser compilado para

import _ from "lodash"

let count = 1;

// any block is ok
{
    let b;
{

E também se o usuário de alguma forma usar uma linguagem diferente em dois scripts, como js no módulo e ts na instância?

Legal, parece bom. Para "Usar variável no modelo" const _bla = bla.bla; poderia ser apenas bla.bla; permitindo que o TypeScript faça sua verificação sem precisar filtrar o diagnóstico "var não utilizado". Não consigo pensar em nenhum caso em que isso não funcione.

Alguns membros da equipe Vue estão explorando um plugin de servidor de linguagem typescript - https://github.com/znck/vue-developer-experience

É um ângulo interessante, pois hoje essas experiências vão de fora e dentro do TypeScript (como vetur faz, já que executa seu próprio tsserver-ish, bem como html / css / etc LSPs) ou você tenta ir de dentro do TypeScript e trabalhar para fora ( por exemplo, esta proposta) onde você manipula o TSServer para efetivamente acreditar que os arquivos .vue são TypeScript legítimos, mascarando o código não-TS.

Embora hoje só funcione com patches para datilografar

Essa é uma abordagem interessante. Mas como recursos como preenchimentos automáticos para html ou css funcionariam então? Seria preciso implementar tudo isso "manualmente" e não depender mais dos servidores de linguagem prontos para uso, ou estou perdendo alguma coisa?

Você vê alguma vantagem em fazer um ao longo do tempo?

Qualquer coisa, exceto o suporte JS / TS, seria tratada por um vscode LSP separado. Seria como se essa extensão removesse todo o suporte para JS / TS e cuidasse apenas do resto, então o plugin de digitação cuidaria do resto.

Em termos de vantagem, você obtém todas as ferramentas de TS "de graça" neste caso, pule para o símbolo, mapeamentos de arquivo, etc.

Provavelmente não o recomendo, hoje ele requer bifurcações ou patches do TypeScript para funcionar, o que é uma barreira muito alta para a entrada - e a equipe do TypeScript ainda não tem certeza se / quando os plug-ins do compilador poderiam ter esse tipo de acesso.

Obrigado pelos insights. Eu diria que estamos com a abordagem atual então (de fora para dentro).

Se seguirmos com a abordagem "criar um arquivo ts virtual" discutida acima, como a implementaríamos e manteríamos os mapeamentos de origem?

Hoje, é fácil porque é apenas "localizar o início do script, localizar o final do script", "extrair a parte do script" e para mapear "adicionar o deslocamento do arquivo esbelto às posições".
Se mudarmos a parte do script agora, precisamos de um mapeamento mais sofisticado:

Original

<script lang="typescript">
  let a = 1;
  $: b = a + 1;
</script>
<p>{b}</p>

Mapeado:

export default class Bla extends SvelteComponent { ... } // <- prepended, no mapping needed as far as I know
let a = 1; // <- untouched
let b = a + 1; // <- modified in place. How to map? Do we need to adjust all following code positions now to have a new offset/position?
b; // <- appended. Needs mapping from {b} inside svelte-mustache-tag to here. We can get the original position from the html ast which is part of the output of svelte.parse

Alguma ideia? Nunca fiz mapeamento de código-fonte antes ..

Alguns membros da equipe Vue estão explorando um plugin de servidor de linguagem typescript - https://github.com/znck/vue-developer-experience

É um ângulo interessante, pois hoje essas experiências vão de fora e dentro do TypeScript (como vetur faz, já que executa seu próprio tsserver-ish, bem como html / css / etc LSPs) ou você tenta ir de dentro do TypeScript e trabalhar para fora ( por exemplo, esta proposta) onde você manipula o TSServer para efetivamente acreditar que os arquivos .vue são TypeScript legítimos, mascarando o código não-TS.

Embora hoje só funcione com patches para datilografar

O patch PR de remoção do TypeScript é mesclado.
https://github.com/znck/vue-developer-experience/pull/7

Então ... @orta isso significa que as desvantagens em relação a "não oficial" não são mais o caso? Ou ainda é meio não oficial, não apenas por meio de um patch, mas por meio de alguma configuração package.json você deve conhecer e pode alterar a qualquer momento?

Outro tópico: no momento, sinto que ainda estamos discutindo sobre como abordar isso da melhor maneira e, se decidirmos, vai levar algum tempo para implementá-lo. O que vocês acham de usar svelte2tsx entretanto? Poderia servir como um ponto de partida muito bom, poderíamos fazer alguns testes em torno dele e então ver "ok, isso realmente funciona muito bem, não vamos alterá-lo" ou migrar gradualmente para longe dele. Pensamentos?

Sim, havia duas maneiras de editar o fluxo padrão do TS. Ele ainda está corrigindo o TypeScript - mas em tempo de

Eu seria um péssimo membro da equipe principal se o recomendasse;)

Experimentar svelte2ts parece um bom caminho para a exploração!

Você pode ver como seria o svelte2tsx usando este plugin vscode: https://marketplace.visualstudio.com/items?itemName=halfnelson.svelte-type-checker-vscode junto com o Svelte Beta (funciona melhor se você desativar o svelte beta opções de texto datilografado para que você não receba o dobro das dicas :))

Mesmo se não estiver usando svelte2tsx, vejo uma discussão sobre quais transformações precisam ser feitas no código JS do svelte para manter o texto digitado feliz. O conjunto de testes para svelte2tsx cobre não apenas as mudanças necessárias para serem feitas no modelo, mas também a tag de script, que se você estiver indo para uma solução apenas de tag de script pode ser um bom ponto de partida: https://github.com/halfnelson / svelte2tsx / tree / master / test / svelte2tsx / samples

o htmlx2jsx é a pasta que os testes de mudança de modelo (htmlx), enquanto os svelte2tsx exemplos são as mudanças específicas do Svelte necessárias para o abuso especial da sintaxe js do Svelte :)

Acabei de descobrir este repo https://github.com/pivaszbs/svelte-autoimport . Talvez valha a pena verificar se queremos importar o intellisense independente de svelte2tsx .

Acho que estamos em um ponto em que a maior parte da direção geral deste projeto está ordenada e parece estar funcionando perfeitamente. Vou dar uma semana para feedback, mas estou muito feliz por não precisarmos repensar arquitetonicamente agora.

Parece que ninguém tem objeções, então vou encerrar este. Obrigado a todos por discutir isso!

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