Language-tools: Übersicht der Ansätze auf einem Sprachserver

Erstellt am 24. März 2020  ·  37Kommentare  ·  Quelle: sveltejs/language-tools

Dieser Thread soll einen Überblick über Ansätze zur Implementierung eines Sprachservers für schlanke Dateien geben. Hier kann auch über eine bevorzugte Lösung diskutiert werden.

Aktueller Stand des Sprachservers

Derzeit funktionieren Syntaxhervorhebung und einige grundlegende Autovervollständigungen für schlanke Dateien.
Rich Intellisense wird jedoch nicht bereitgestellt, kennt nicht alle Typoskriptdateien im Arbeitsbereich und hat auch keine Typinformationen zu den schlanken Komponenten.
Das gleiche gilt für den HTML-Teil, in dem eine spezielle schlanke Syntax hervorgehoben ist, aber Intellisense nicht funktioniert. Es wird der vscode-html-languageservice verwendet, der die spezielle Syntax standardmäßig nicht kennt.
Der aktuelle Sprachserver verwendet den schlanken Compiler zum Analysieren von Dateien und zum Abrufen von Diagnosen wie "kein Alt-Attribut für dieses img-Tag".
Eine tiefergehende Analyse: Kommentar

Bestehende Ansätze/Lösungen

Haftungsausschluss: Ich bin nicht der Autor einer der vorhandenen Lösungen, habe mir jedoch den Code angesehen. Wenn einige der Informationen falsch oder nicht detailliert genug sind oder Sie eine Lösung vermissen, können Sie dies gerne kommentieren, ich werde sie anpassen.

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

Verwendet htmlparser2 , um den HTML-Teil einer schlanken Datei zu analysieren. Verwendet Hooks des HTML-Parsers, um benutzerdefiniertes Parsen hinzuzufügen und relevante Informationen zu extrahieren.
Verwendet espree , um die Skriptteile einer schlanken Datei zu analysieren. Durchläuft dann den AST, um relevante Informationen zu extrahieren.
Liefert die Ergebnisse im JSON-Format.

https://github.com/ArdenIvanov/svelte-intellisense verwendet es, um Svelte-Dateien zu analysieren. Es verwendet das Ergebnis, um zusätzliches Verhalten anzuhängen.

Da dies einen Javascript-Parser verwendet, funktioniert es nicht mit Typoskript.

https://github.com/halfnelson/svelte2tsx

Verwendet den Compiler von svelte, um den HTMLx-Teil einer svelte-Komponente zu analysieren. Wandelt dann das HTMLx plus den ursprünglichen Skriptteil mit einem selbstgeschriebenen Transformer in eine tsx-Datei um.

https://github.com/simlrh/svelte-language-server/blob/feature/extend-ts-support/src/plugins/ts-svelte/service.ts (ein Fork von svelte-language-server) verwendet es, um Schatten-tsx-Dateien von Svelte-Dateien erstellen. Es verwendet dann den Sprachdienst von typescript, stellt aber die Anfragen als Proxy: Es ersetzt den ursprünglichen Dateipfad durch den Dateipfad zur generierten tsx-Datei. Der Typescript-Sprachserver überprüft daher die generierten tsx-Dateien. Die Ergebnisse werden zurücktransformiert, um die korrekten Dokumentpositionen zu erhalten.

https://github.com/halfnelson/svelte-type-checker-vscode ist ein weiterer Sprachserver, der die svelte2tsx-Bibliothek verwendet.

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

Beschreibung aus dem Kommentar des

Kompilierer:

  1. Der Compiler liest alle Quelldateien
  2. Alles außer dem Inhalt der Skript-Tags wird weggeworfen
  3. Der Skriptquellcode wird in gültige JS- und Deklarationsdateien kompiliert.
  4. Springt zurück zu Schritt 2, wo die TS-Quelle in Skript-Tags durch das kompilierte JS ersetzt wird
  5. Der Svelte-Compiler kompiliert die gesamte Quelldatei
  6. Die Ausgabekompilierung des obigen Schrittes wird zwischengespeichert, wo der HTML-AST dann in der Typprüfung verwendet wird.

Es konsumiert die exportierten Variablen und Funktionen aus einem Svelte-Skript und generiert es dann als Deklarationsdateien, die eine Klasse enthalten, die die Laufzeit-SvelteComponent erweitert.

Typprüfer:

  1. Die Typprüfung findet alle Komponenten im zwischengespeicherten AST.
  2. Überprüft, ob alle Komponenten in der Vorlage eine gültige Klasse sind, die SvelteComponent . erweitert
  3. Prüfung auf gültige Deklarationen, Eigenschaften etc.

Andere Ansätze

Siehe Kommentare zu diesem Thread.

Hilfreichster Kommentar

Okay super! Ich werde weitermachen und einige PRs erstellen, um einen Teil des Codes umzustrukturieren und ihn vorzubereiten, damit wir dann die Arbeit an einzelnen Elementen parallelisieren können.

Alle 37 Kommentare

Also verwendet keiner von ihnen den Svelte-Parser?

svelte2tsx entfernt style/script-Tags und verwendet dann den svelte-Compiler, um den htmlx-Teil zu analysieren.
svelte-ts verwendet als zweiten Schritt den svelte-Compiler.
Der aktuelle Sprachserver verwendet den schlanken Compiler nicht zum Parsen.
Ich werde den Anfangspost aktualisieren.

Ich war mir nicht sicher, ob ich das teilen sollte, aber ein Teil davon könnte nützlich sein. Letztes Jahr (Juli/August 2019) habe ich angefangen, mit der Typprüfung von Svelte herumzuspielen, da ich wusste, dass ich Wegwerfarbeiten mache, nur um zu lernen. Es unterstützt eine unbekannte Menge einer vollständigen Lösung, vielleicht 30%, und es hat einige Einschränkungen (dazu später mehr). Es gibt keine Tests oder externe Dokumentation, weil ich das immer für eine Sackgasse gehalten habe.

Die Svelte-Kompilierung läuft wie heute mit einem TS-Präprozessor ohne Typprüfung ab. Wenn Sie Typen in Ihrem HTMLx-Markup haben möchten, nicht nur in Ihrem script-Tag, benötigen Sie einen Präprozessor, um das zu verarbeiten, oder Svelte müsste TS intern verarbeiten.

Der Typechecker ist ein separater Build-Schritt, der den nicht vorverarbeiteten TypeScript-Code aus script Blöcken und den Markup-AST aus svelte.compile , um eine virtuelle Svelte-TypeScript-Datei ausschließlich zu Typprüfungszwecken zu erstellt das Rollup-Plugin überprüft sie dann ) Es ähnelt der Strategie von @halfnelson , unterscheidet sich jedoch darin, dass es normales TS produziert, nicht TSX, also versucht es im Markup nicht, HTML-Tags zu überprüfen und Attribute, nur das Zeug in den Schnurrbart-Tags und Svelte-Komponenten. Es wandelt Svelte-Komponenten in Konstruktoren um, die new ed mit ihren Requisiten erhalten. Ich habe viele von Sveltes Konstrukten halbwegs unterstützt, bevor ich erkannte, dass ich mich mehr hinreißen ließ, als ich beabsichtigt hatte, und weitere Fortschritte sollten Serious Engineering sein.

Die TypeScript-Compiler-API wird verwendet, um ein Programm im Speicher zu erstellen und den virtuellen Svelte TS zu

Die virtuelle Svelte TS-Datei wird mit einer Sourcemap generiert, die dann verwendet wird, um die TypeScript-Diagnose zurück auf die ursprüngliche Svelte-Quelle abzubilden . Es war ziemlich cool zu sehen, dass Typfehler auf die Svelte-Quelle zurückwiesen, aber ich hatte insgesamt kein Vertrauen in meinen Ansatz.

Ein Problem, von dem ich mich erinnere, dass ich es nicht herausfinden konnte, war die Diagnose, die in einigen Fällen nicht spezifisch genug war. IIRC war ein Beispiel für die Typprüfung von Svelte-Komponentenkonstruktor-Requisiten - VSCode würde irgendwie auf einzelne Prop-Fehler zeigen, aber die TS-Diagnose, die ich erhielt, zeigte nur auf den Konstruktor, nicht auf die Problemeigenschaft.

Wenn Sie den Code durchsehen, beachten Sie, dass Teile ein Durcheinander sind und einige Kommentare möglicherweise veraltet sind. Ich habe ursprünglich magic-string und irgendwann auf source-map umgestellt. Und denken Sie daran, es war von Anfang an ein zum Scheitern verurteiltes Unterfangen! Beachten Sie, dass sich all dies im ts Zweig dieses Repositorys befindet, nicht in master .

Hier sind die 4 oben genannten Dateien:

Ich habe etwas im Quellcode des aktuellen Svelte-Sprachservers gegraben. Hier sind meine Gedanken zum Typoskriptteil davon:

Überblick

Der Sprachserver funktioniert im Großen und Ganzen so:

  • DocumentManager verarbeitet alle Dokumente, die geöffnet werden.
  • TypescriptPlugin registriert sich bei DocumentManager , um bei Sprachserver-Ereignissen ("doHover" usw.) aufgerufen zu werden. Es wird nicht direkt registriert: Es wird mit wrapFragmentPlugin umschlossen, wodurch sichergestellt wird, dass TypescriptPlugin nur den Inhalt innerhalb des script Tags erhält. Die Zuordnung der Positionen (von Maus/Textcursor) erfolgt in wrapFragmentPlugin um sicherzustellen, dass die Informationen an der richtigen Position angezeigt werden.
  • TypescriptPlugin verwendet service , einen Typoskript-Sprachdienst-Wrapper. Es delegiert im Grunde alle Ereignisse an diesen Sprachserver, mit einigen Zuordnungen, um die Rückgabetypen der Methode einzuhalten.
  • service nimmt das Dokument zum Bearbeiten sowie eine createDocument Methode von TypescriptPlugin . service behält seine eigene Map von Dokumenten, die im Moment im Grunde nur ein Delegierter für das Dokument des DocumentsManager . Jedes Mal, wenn service von TypescriptPlugin aufgerufen wird, aktualisiert service seine Dokumentenzuordnung mit dem angegebenen Dokument (nachdem es umschlossen wurde). createDocument würde aufgerufen, wenn der Sprachdienst ein Dokument öffnet, das noch nicht Teil der Dokumentenzuordnung ist - dies wird jedoch nie passieren, da der Typescript-Sprachdienst keine anderen schlanken Module findet (siehe nächster Abschnitt).

Aktuelle Mängel und Verbesserungsvorschläge

  • Da das script-Tag unverändert an den Typescript-Sprachdienst übergeben wird, kann es keine spezielle schlanke Syntax ($) verarbeiten.
  • Der Typescript-Sprachdienst versucht, andere Dateien zu suchen, auf die in der aktuell geöffneten Svelte-Datei verwiesen wird. Für .ts / .js Dateien funktioniert dies, für .svelte Dateien nicht. Grund: Der Typoskript-Sprachdienst kennt die Endung .svelte , also nimmt er einfach an, dass es sich um eine normale Typoskriptdatei handelt und sucht nach Dateien wie ../Component.svelte.ts , was falsch ist. Um dies zu beheben, müssen wir die Methode resolveModuleNames auf LanguageServiceHost implementieren und alle .svelte.ts Dateisuchvorgänge auf .svelte umleiten. Dann wird der Sprachdienst alle schlanken Dateien durchlaufen. HINWEIS: Um dies zu implementieren, müssen wir auch den folgenden Fehler beheben: Da der Typescript-Sprachdienst jetzt alle Dateien durchlaufen würde, würde er createDocument von TypescriptPlugin aufrufen. Dies gibt jedoch derzeit das gesamte Dokument zurück, nicht nur den Inhalt innerhalb des script-Tags. Dies ist ein Fehler in wrapFragmentPlugin , der openDocument nicht umschließt.
  • Um erweiterte Funktionen wie "Gehe zu Svelte-Datei" zu implementieren, indem Sie auf den Importnamen der Svelte-Komponente klicken oder Typskript die $-Zeichen grog machen, müssen wir einen Präprozessor hinzufügen. Dieser Präprozessor würde irgendwie die fehlenden Teile für Typoskript hinzufügen. Idealerweise können alle zusätzlichen Sachen über den bestehenden Code gelegt werden, so dass das Mapping der Positionen genauso einfach ist wie das Mapping der Positionen mit Offsets. Dies stellt jedoch eine weitere Herausforderung dar: Wir hätten jetzt zwei Positionszuordnungen: Eine von der gesamten schlanken Datei zum script-Tag und eine vom script-Tag zum vorverarbeiteten Typescript-Code. Ich bin mir an dieser Stelle nicht sicher, ob dies der richtige Weg ist oder ob wir überarbeiten sollten, wie TypescriptPlugin (und andere) ihr Dokument erhalten - vielleicht sollten sowohl die Extraktion des Skript-Tags als auch die Vorverarbeitung in einem Schritt erfolgen . Dies würde bedeuten, dass wrapFragmentPlugin geändert oder vollständig ersetzt wird. Ein weiteres Argument für die Überarbeitung ist, dass wir, um Dinge wie "unbenutzte Funktion" korrekt zu bestimmen, den HTML-Anteil berücksichtigen müssen, daher reicht das script-Tag nicht aus.

Die Gedanken?

Auf resolveModuleNames , so hat es ein TypeScript-Teammitglied für Vetur gemacht , und hier ist eine ähnliche Version, die ich für Svelte gemacht habe, die einen Kommentar enthält, der eine alternative Strategie erwähnt, die funktioniert, aber das Kopieren von Vetur schien besser zu sein.

@dummdidumm macht es Ihnen etwas aus, als technischer aufzuschlüsseln ? Ich würde gerne freiwillige Entwicklerzeit zur Verfügung stellen, aber ich finde vieles davon überwältigend und würde in kleinen, definierten Problemen besser funktionieren. Sofern keine großen architektonischen Änderungen vorgenommen werden müssen (sieht aus wie nein?)

Öffnen Sie im Grunde einfach eine Reihe von Problemen und lassen Sie die Leute sie beanspruchen und als unseren informellen technischen Leiter verwalten. Ich würde anbieten, es zu tun, aber ehrlich gesagt habe ich den Schmerz noch nicht gespürt, haha

auch ein Link zu ortas ursprünglichem LSP-Post für andere, die https://github.com/sveltejs/svelte/issues/4518 durchsuchen

Natürlich würde ich gerne dabei helfen, aber im Moment bin ich nur ein zufälliger Typ, der sich sehr auf den Sprachserver freut, in den Code stöbert und ein paar Vorschläge macht 😄 Ich brauche auch noch etwas Zeit, um mich mit dem Code vertraut zu machen, seine Architektur und LSP im Allgemeinen. Aber ich bin auf jeden Fall bereit, Dinge voranzutreiben. @orta, da du der Betreuer dieses

👋 Ja, wir sind beide zufällige Leute, die sich nur darum kümmern - ich habe nicht einmal eine schlanke Codebasis, um meinen "Das sollte besser sein"-Juckreiz auszulösen. Also, ich freue mich sehr über jede Bewegung - @dummdidumm -

Okay super! Ich werde weitermachen und einige PRs erstellen, um einen Teil des Codes umzustrukturieren und ihn vorzubereiten, damit wir dann die Arbeit an einzelnen Elementen parallelisieren können.

Vielleicht könnten wir @ryanatkn , den Ansatz von svelete-ts und den variablen Ansatz von eslint-plugin-svelte3 kombinieren :

  1. Verwenden Sie den schlanken Preprocess, um das Skript in js vorzuverarbeiten.
    Hier könnten wir einfach auf die neueste oder nächste Version von ES übergehen, um so wenig Transformation wie möglich vorzunehmen.
  2. Lassen Sie svelte die vorverarbeitete Quelle kompilieren .
  3. Wir können dann Ergebnisvariablen mit injected = true in die Instanz ts source oder ts AST einfügen und sie dann mit der Quellzuordnung verwenden, um eine Typprüfung und automatische Vervollständigung bereitzustellen.

Das Skript kann drei Fragmente haben: Modul <script context="module"> , Instanz und Schnurrbart (Vorlage). Der Schnurrbart-Teil könnte vorerst einfach in eine template.js gedumpt werden. Aber langfristig könnten wir Templates in tsx oder einfache ts wie den Ansatz von ryanatkn einfließen lassen.

Modulvariablen, wobei module = true in template.js und Instanz eingefügt werden, aber nicht umgekehrt.

Auf diese Weise müssen wir nicht neu implementieren, wie wir die reaktive Variable $: a = 1 oder den Speicher mit dem Präfix $ in Typoskript-AST finden. Diese vom svelte-Compiler eingefügten Variablen stammen von der Anweisung der obersten Ebene und wir transpilieren sie in die neueste ES. Es sollte also meistens nicht per Typoskript umbenannt werden.

Über die reaktive Variable $: a könnte es so geschrieben werden:

$: a = 1

```ts
lass a: Zahl
$: a = 1

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

Alles gültige ts, die vor dem Transpilen nicht transformiert werden müssen

Und mit $-präfixiertem Store können wir eine generische Funktion wie svelte/store get erstellen, um den Store-Typ zu extrahieren

/** injected */
let $store = getType(store)
  1. Verwenden Sie den schlanken Preprocess, um das Skript in js vorzuverarbeiten.
    Hier könnten wir einfach auf die neueste oder nächste Version von ES übergehen, um so wenig Transformation wie möglich vorzunehmen.
  2. Lassen Sie svelte die vorverarbeitete Quelle kompilieren .
  3. Wir können dann Ergebnisvariablen mit injected = true in die Instanz ts source oder ts AST einfügen und sie dann mit der Quellzuordnung verwenden, um eine Typprüfung und automatische Vervollständigung bereitzustellen.

Ich denke, svelte-ts macht so etwas. Scheint eine gute Idee zu sein. On injected = true : Welcher Code hat genau diese Eigenschaft?

Das Skript kann drei Fragmente haben: Modul <script context="module"> , Instanz und Schnurrbart (Vorlage). Der Schnurrbart-Teil könnte vorerst einfach in eine template.js gedumpt werden. Aber langfristig könnten wir Templates in tsx oder einfache ts wie den Ansatz von ryanatkn einfließen lassen.

Modulvariablen, wobei module = true in template.js und Instanz eingefügt werden, aber nicht umgekehrt.

Auf diese Weise müssen wir nicht neu implementieren, wie wir die reaktive Variable $: a = 1 oder den Speicher mit dem Präfix $ in Typoskript-AST finden. Diese vom svelte-Compiler eingefügten Variablen stammen von der Anweisung der obersten Ebene und wir transpilieren sie in die neueste ES. Es sollte also meistens nicht per Typoskript umbenannt werden.

Sie möchten die Datei also in eine virtuelle .ts -Datei und eine .template -Datei aufteilen? Warum nicht die Schnurrbart-Teile unten in der .ts Datei haben? Auf diese Weise würden wir auch "unbenutzte Methode"-Warnungen loswerden. Beim Durchlaufen des AST: Ja, ich denke, wir können hier schlau sein und den Blick auf den AST zu sehr überspringen, weil alles, was in der Vorlage referenziert wird, im Skript auf höchster Ebene sein muss.

Über die reaktive Variable $: a könnte es so geschrieben werden:

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

oder

$: a = someFunction(b) as Type

Alles gültige ts, die vor dem Transpilen nicht transformiert werden müssen

Kann man auf den Typ schließen? Vielleicht lassen wir das einfach so, und wenn der Benutzer Typoskript verwenden möchte, muss er let a: number selbst definieren. Als Alternative könnten wir let a; einfügen, aber dann wäre es any .

Und mit $-präfixiertem Store können wir eine generische Funktion wie svelte/store get erstellen, um den Store-Typ zu extrahieren

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

Ja, das scheint nett zu sein. Eine andere Idee, die ich hatte, war, Getter/Setter zu konstruieren.

Ich denke, svelte-ts macht so etwas. Scheint eine gute Idee zu sein. On injected = true : Welcher Code hat genau diese Eigenschaft?

Das Kompilierungsergebnis hat eine Eigenschaft vars, bei der es sich um ein Array von Objekten mit der folgenden Eigenschaft handelt:
name, export_name, injiziert, modul, mutiert, neu zugewiesen, referenziert_von_script und beschreibbar
Sie können die schlanke Kompilierungs-API für weitere Informationen sehen

Sie möchten die Datei also in eine virtuelle .ts-Datei und eine .template-Datei aufteilen? Warum nicht die Schnurrbart-Teile unten in der .ts-Datei? Auf diese Weise würden wir auch "unbenutzte Methode"-Warnungen loswerden.

Dies ist der Ansatz von eslint-plugin-svelte3 . Ich dachte auch nur, ob wir ts in der Vorlage unterstützen wollen, weil es kein Attribut gibt, um die Sprache anzugeben.

Kann man auf den Typ schließen?

Da diese virtuelle Quelle nur für Typprüfung und Autovervollständigung verwendet wird, gehe ich davon aus, dass sie durch Kopieren der ersten Assign-Anweisung zum Initialisieren erreicht werden kann, aber es scheint mehr Arbeit zu erfordern.

Kann man auf den Typ schließen? Vielleicht lassen wir das einfach so, und wenn der Benutzer Typoskript verwenden möchte, muss er let a: number selbst definieren. Als Alternative könnten wir let a; einfügen, aber dann wäre es any.

Sieht so aus, als würden uns normale Schlussfolgerungen nicht den ganzen Weg dorthin bringen. (Meinten Sie Kompilierzeit-Inferenz? Ich bin mir nicht sicher, ob das funktionieren könnte)

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

Der Grund dafür ist, dass TypeScript im linearen Codefluss erkennen kann, dass a nicht neu zugewiesen wurde, aber verschachtelte Funktionsbereiche diese Garantie nicht haben und TypeScript auf der Seite der Typsicherheit Fehler macht.

Spielplatz Beispiel

Genau, das waren meine Sorgen damit. Aber wie @jasonyu123 darauf hingewiesen hat, könnten wir einfach die Definition kopieren (alles nach dem = ).

Vor

$: a = true;

Nach

let a = true;
$: a = true;

Aber ich weiß nicht, ob das unter allen Umständen machbar ist. Insbesondere bei großen Objekten, bei denen jemand eine Schnittstelle implementiert, wird daraus nicht alles abgeleitet, wie es der Benutzer erwarten könnte. Außerdem könnte die Copy-Pasta ziemlich groß werden. Ich bin also immer noch eher auf der Seite "vom Benutzer eingeben lassen".

Danke für die Klarstellung, ich habe falsch verstanden, was @jasonyu123 gesagt hat. Es behebt das erwähnte Problem mit dem verschachtelten Funktionsumfang.

Gibt es ein Beispiel dafür, wann das kein gutes Standardverhalten ist? Es scheint, als ob es die meiste Zeit ergonomisch richtig ist, und andere Fälle können manuell repariert werden. Hier sind einige Beispiele.

Mhm ich denke du hast recht. Es ist das ergonomischste. Worüber ich mir Sorgen mache, ist, wenn der abgeleitete Typ falsch ist, zum Beispiel ist die Eigenschaft string anstelle einer definierten Vereinigung von Zeichenfolgen ( 'a' | 'b' ). Aber wie Sie sagten, diese Fälle sind nicht so häufig und der Benutzer kann sie immer noch manuell eingeben.

Es gibt einen Teil von mir, der ist wie: "Großartig, die explizite Let-Syntax funktioniert großartig", aber das würde am Ende nicht mit JS-Unterstützung aus der Box funktionieren

Wie wäre es, wenn Sie das erste Etikett ersetzen, um Folgendes zu lassen:

$: a = 1
$: a = 2

verwandle dich in

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

Ich überdenke ein bisschen und kann den Vorteil von "Definition kopieren" gegenüber "Ersetzen von $: durch let " nicht finden. Können Sie sich einen Nachteil vorstellen, wenn man $: durch let ?

Können wir in Bezug auf den abgeleiteten Typunterschied zu dem, was der Benutzer erwartet hat, Refactoring-Aktionen bereitstellen, um ihn in eine manuelle Definition umzuwandeln? Diese Aktion wird der Benutzer auf manuelle Art die Variable wie prompt diese , oder Umgestalten seiner abgeleiteten Typ , wenn möglich.

Vor

$: a = 'const1'

nach

let a : AskUserForType
$: a = 'const1'

Denkt auch an die Dinge, auf die du hinweist, Etwas gelernt 👍

Ha, kluge Idee, ersetzen Sie einfach das $: vollständig durch ein let . Ich kenne auch keine Situationen, in denen das schlimm enden würde.
Wir könnten auch daran denken, die Transformation immer durchzuführen, nicht nur für das erste Label. Dann würde es einen "cannot redeclare"-Fehler geben, der den Benutzer auf "woops, ich habe diese Variable zweimal definiert" hinweist - oder gibt es Situationen, in denen dies praktikabel und kein Fehler wäre?

Schön, ich kann mir auch keinen Grund vorstellen, warum das Ersetzen des Etiketts fehlschlagen würde. (da break $ nie innerhalb von Aufgaben funktioniert, oder?)

Um dieselbe reaktive Variable erneut zu deklarieren, sieht es so aus, als ob Svelte dies zulässt und es funktioniert wie erwartet. ( siehe dieses Ersatzbeispiel ) Ich persönlich sehe dieses Muster nicht absichtlich verwenden und würde den Fehler gerne haben, aber es liegt im Bereich der Interpretation von Sveltes Semantik, also nehmen Sie nicht mein Wort dafür!

OK, es neu zu deklarieren ist ein Muster, das tatsächlich funktioniert. Ich würde sagen, dass wir es dann besser zulassen - oder später ein "Redeclare Not Allowed"-Feature-Flag erstellen, das standardmäßig deaktiviert ist.

@halfnelson hat einen Sprachserver basierend auf seinem svelte2tsx-Ansatz in https://github.com/halfnelson/svelte-type-checker-vscode erstellt . Ich finde es wirklich toll, wie gut das alles schon funktioniert, aber ich würde tsx trotzdem lieber nicht als Zwischenausgabe verwenden, weil ich befürchte, dass es etwas zu langsam sein könnte und einige Einschränkungen hat - aber das ist nur mein Bauchgefühl. Dennoch konnten wir von svelte2tsx einiges mitnehmen: Wie der Ablauf von Parsing-Compiling-Transformation abläuft und auch die umfangreiche Testsuite. Wie sehen andere den Ansatz, den Code in tsx umzuwandeln?

Ich dachte darüber nach, wie andere Transformationen aussehen könnten. Hier sind einige Beispiele. Rückmeldung erwünscht.

So sieht eine Komponentendefinition aus

Original:

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

    export let todo: string;

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

Umgewandelt:

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);
}

Erläuterung:
Holen Sie sich irgendwie alle Requisiten (einfach) und verteilte Ereignisse (schwer) und fügen Sie sie zu einem großen Requisiten-Objekt hinzu. Das Hinzufügen von Ereignissen mit dem Präfix on: sollte Kollisionen minimieren und die Implementierung von Vorschlägen zur automatischen Vervollständigung erleichtern.

(in den folgenden Beispielen wird die Komponentendefinition der Kürze halber weggelassen)

Variable in Vorlage verwenden

Original:

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

Umgewandelt:

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

Erläuterung:
Fügen Sie beliebige const Elemente hinzu, möglicherweise mit einem obskuren Unicode-Zeichen, um die korrekte Verwendung von Variablen zu überprüfen und auch die "unused"-Warnung zu entfernen. Vielleicht müssen wir uns dann überlegen, wie wir die ungenutzten _bla -Warnung loswerden...

Nativen Ereignis-Listener verwenden

Original:

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

Umgewandelt:

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

Erläuterung:
Wenn Sie native Ereignis-Listener verwenden, erstellen Sie das entsprechende Element und fügen Sie dann einen Ereignis-Listener hinzu. Auf diese Weise nutzen wir die addEventListener -Typisierungen, die Überladungen für so ziemlich jeden Listener da draußen haben: .addEventListener('click', ...); -> Ereignis ist vom Typ MouseEvent . Dieses Muster würde verwendet, nachdem überprüft wurde, ob das Element eine andere schlanke Komponente ist, die das Ereignis definiert hat (siehe auch nächstes Beispiel). Mängel: Was ist, wenn Svelte in einer Nicht-Web-Umgebung (Nativescript) verwendet wird?

Andere Komponente verwenden

Original:

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

Umgewandelt:

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

Erläuterung:
Jede Komponente erhält eine Klassendefinition, also instanziieren wir diese Klasse einfach.

Jede Schleife

Original:

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

Umgewandelt:

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

Erläuterung:
forEach scheint am einfachsten zu konvertieren. Im Inneren können wir alle anderen Funktionen rekursiv anwenden.

Wenn

Original:

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

Umgewandelt:

if (x === 1) {

} else if (x === 2) {

} else {

}

Erläuterung:
Ziemlich selbsterklärend.

Fehlende Teile

  • Schlüssel
  • spezielle svelte:x Komponenten
  • andere Element-/Komponentendirektiven wie use:action , transition , bind
  • $-Syntax

Was denkt ihr? Sind diese Transformationen machbar und machbar? Habe ich etwas verpasst?

Über die Komponentenklasse würde ich lieber die eigene SvelteComponent-Klasse von svelte erweitern, aber ich weiß nicht, ob dies einfach zu implementieren ist oder nicht

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 () => { }
    }
}

und importieren Sie diese Schnittstelle

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>>
}

Angemerkt, dass $$slots wahrscheinlich die interne API von svelte ist, ich grabe sie nur aus kompiliertem Code

Der Grund dafür ist, dass es dann verwendet werden kann, um .d.ts auszugeben, die verwendet werden könnte, um Vanilla ts/js einzugeben.
Auch weil dies, obwohl seltsam genug, eine gültige Syntax ist:

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

Elementanweisungen

Transition , use:action und andere Elementdirektiven können wie folgt umgewandelt werden

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

Erwarten

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

zu

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

binden: das

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

zu

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

musste es in einen Rückruf einschließen, da es nicht sofort verfügbar ist

Kontext="Modul"

Das ist etwas knifflig

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

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

sollte kompiliert werden zu

import _ from "lodash"

let count = 1;

// any block is ok
{
    let b;
{

Und was ist auch, wenn der Benutzer in zwei Skripten irgendwie unterschiedliche Sprachen verwendet, wie z. B. js im Modul und ts in der Instanz?

Schön, sieht gut aus. Für "Variable in Vorlage verwenden" könnte const _bla = bla.bla; nur bla.bla; damit TypeScript seine Prüfung durchführen kann, ohne die Diagnose "unbenutzte Variable" herausfiltern zu müssen. Ich kann mir keine Eckfälle vorstellen, in denen das nicht funktioniert.

Einige Mitglieder des Vue-Teams untersuchen ein Typoskript-Sprachserver-Plugin - https://github.com/znck/vue-developer-experience

Es ist ein interessanter Blickwinkel, da diese Erfahrungen heute von außerhalb von TypeScript und in (wie vetur, da es seine eigenen tsserver-ish sowie html/css/etc LSPs ausführt) oder versuchen Sie, von innerhalb von TypeScript nach außen zu arbeiten ( zB dieser Vorschlag), wo Sie den TSServer manipulieren, um effektiv zu glauben, dass die .vue-Dateien legitimes TypeScript sind, indem Sie Nicht-TS-Code maskieren.

Obwohl es heute nur mit Patches für die Typoskription funktionieren kann

Das ist ein interessanter Ansatz. Aber wie würden dann Funktionen wie Autovervollständigungen für HTML oder CSS funktionieren? Müsste man das alles "von Hand" implementieren und nicht mehr auf die Out-of-the-Box-Sprachserver angewiesen sein, oder übersehe ich etwas?

Siehst du irgendwelche Vorteile darin, einen gegenüber dem Over zu machen?

Alles außer der JS/TS-Unterstützung würde von einem separaten vscode LSP gehandhabt. Es wäre so, wenn diese Erweiterung alle Unterstützung für JS/TS entfernt und nur den Rest erledigt, dann würde das Typescript-Plugin den Rest erledigen.

Vorteilhafterweise erhalten Sie in diesem Fall alle TS-Tools "kostenlos", springen zu Symbolen, Dateizuordnungen usw

Ich kann es wahrscheinlich nicht empfehlen, heute erfordert es Forks oder Patches von TypeScript, um zu funktionieren, was eine ziemlich hohe Eintrittsbarriere darstellt - und das TypeScript-Team ist sich immer noch nicht sicher, ob / wann Compiler-Plugins diese Art von Zugriff haben könnten.

Danke für die Einblicke. Ich würde sagen wir mit dem jetzigen Ansatz dann (von außen nach innen).

Wenn wir mit dem oben diskutierten "Make a virtual ts file"-Ansatz fortfahren, wie würden wir dies implementieren und die Quellzuordnungen beibehalten?

Heute ist es einfach, weil es nur "Skriptanfang finden, Skriptende finden", "Skriptteil extrahieren" und für die Zuordnung "Versatz der schlanken Datei zu Positionen hinzufügen" bedeutet.
Wenn wir jetzt den Skriptteil ändern, brauchen wir ein ausgefeilteres Mapping:

Original

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

Zugeordnet:

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

Irgendwelche Ideen? Ich habe noch nie zuvor Source-Mapping-Zeug gemacht.

Einige Mitglieder des Vue-Teams untersuchen ein Typoskript-Sprachserver-Plugin - https://github.com/znck/vue-developer-experience

Es ist ein interessanter Blickwinkel, da diese Erfahrungen heute von außerhalb von TypeScript und in (wie vetur, da es seine eigenen tsserver-ish sowie html/css/etc LSPs ausführt) oder versuchen Sie, von innerhalb von TypeScript nach außen zu arbeiten ( zB dieser Vorschlag), wo Sie den TSServer manipulieren, um effektiv zu glauben, dass die .vue-Dateien legitimes TypeScript sind, indem Sie Nicht-TS-Code maskieren.

Obwohl es heute nur mit Patches für die Typoskription funktionieren kann

Der TypeScript-Patch PR entfernen wird zusammengeführt.
https://github.com/znck/vue-developer-experience/pull/7

Also ... @orta das heißt die Nachteile bezüglich "inoffiziell" sind nicht mehr der Fall? Oder ist es immer noch irgendwie inoffiziell, nur nicht durch einen Patch, sondern durch eine package.json Konfiguration, die Sie kennen müssen und die Sie jederzeit ändern können?

Ein weiteres Thema: Ich denke, wir diskutieren derzeit noch darüber, wie wir das am besten angehen können, und wenn wir uns entscheiden, wird die Umsetzung einige Zeit in Anspruch nehmen. Was halten Sie davon, svelte2tsx in der Zwischenzeit zu verwenden? Es könnte als sehr guter Ausgangspunkt dienen, wir könnten einige Tests durchführen und dann entweder sehen "ok, das funktioniert tatsächlich großartig, lass es uns nicht ändern" oder allmählich davon weggehen. Die Gedanken?

Ja, es gab zwei Möglichkeiten, den Standardfluss von TS zu bearbeiten: Es wird immer noch TypeScript gepatcht – aber zur Laufzeit durch Ersetzen der Funktionen von TS. Ich denke, es ist vernünftig, da die Plugin-Entwicklung noch nicht auf der Roadmap steht.

Ich wäre jedoch ein schlechtes Kernteammitglied, wenn ich es empfehlen würde ;)

svelte2ts auszuprobieren klingt nach einem guten Weg zum Erkunden!

Sie können sehen, wie es mit svelte2tsx mit diesem vscode-Plugin aussehen würde: https://marketplace.visualstudio.com/items?itemName=halfnelson.svelte-type-checker-vscode zusammen mit Svelte Beta (funktioniert am besten, wenn Sie svelte beta deaktivieren Typoskript-Optionen, damit Sie die Hinweise nicht doppelt erhalten :) )

Auch wenn svelte2tsx nicht verwendet wird, sehe ich Diskussionen darüber, welche Transformationen am JS-Code von svelte vorgenommen werden müssen, um Typskript glücklich zu machen. Die Testsuite für svelte2tsx umfasst nicht nur Änderungen, die an der Vorlage vorgenommen werden müssen, sondern auch das Skript-Tag, das ein guter Ausgangspunkt sein könnte, wenn Sie sich für eine reine Skript-Tag-Lösung entscheiden: https://github.com/halfnelson /svelte2tsx/tree/master/test/svelte2tsx/samples

htmlx2jsx ist der Ordner, in dem die Vorlagenänderungstests (htmlx) durchgeführt werden, während die svelte2tsx Beispiele die schlanken spezifischen Änderungen sind, die für den speziellen Missbrauch der js-Syntax von svelte benötigt werden :)

Habe gerade dieses Repo entdeckt https://github.com/pivaszbs/svelte-autoimport . Vielleicht einen Blick wert, wenn wir Intellisense unabhängig von svelte2tsx importieren wollen.

Ich denke, wir sind an einem Punkt angelangt, an dem die meisten allgemeinen Richtungen für dieses Projekt festgelegt sind und ordentlich zu funktionieren scheinen. Ich werde dies eine Woche für Feedback geben, aber ich bin ziemlich froh, dass wir es jetzt nicht architektonisch überdenken müssen.

Anscheinend hat niemand Einwände, also schließe ich diesen hier. Danke an alle für die Diskussion!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

matthewmueller picture matthewmueller  ·  3Kommentare

johanbissemattsson picture johanbissemattsson  ·  4Kommentare

opensas picture opensas  ·  4Kommentare

koddr picture koddr  ·  6Kommentare

PatrickG picture PatrickG  ·  3Kommentare