Language-tools: Обзор подходов на языковом сервере

Созданный на 24 мар. 2020  ·  37Комментарии  ·  Источник: sveltejs/language-tools

Этот поток предназначен для обзора подходов к реализации языкового сервера для файлов svelte. Здесь также можно обсудить предпочтительное решение.

Текущее состояние языкового сервера

В настоящее время подсветка синтаксиса и некоторые базовые функции автозаполнения работают для файлов svelte.
Однако расширенный intellisense не предоставляется, не знает обо всех файлах машинописного текста в рабочей области, а также не имеет информации о типах компонентов svelte.
То же самое верно и для части html, где выделен специальный стройный синтаксис, но intellisense не работает. Используется vscode-html-languageservice, который по умолчанию не знает о специальном синтаксисе.
Текущий языковой сервер действительно использует компилятор svelte для синтаксического анализа файлов и получения диагностики типа «нет атрибута alt в этом теге img».
Более глубокий анализ: Комментарий

Существующие подходы / решения

Отказ от ответственности: я не являюсь автором каких-либо существующих решений, однако я просмотрел код. Если какая-то информация неверна или недостаточно подробна, или вам не хватает решения, не стесняйтесь комментировать, я исправлю его.

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

Использует htmlparser2 для анализа html-части svelte-файла. Использует ловушки парсера html для добавления пользовательского синтаксического анализа и извлечения соответствующей информации.
Использует espree для разбора частей скрипта
Предоставляет результаты в формате JSON.

https://github.com/ArdenIvanov/svelte-intellisense использует его для разбора файлов svelte. Он использует результат для добавления некоторого дополнительного поведения.

Поскольку здесь используется парсер javascript, он не будет работать с машинописным текстом.

https://github.com/halfnelson/svelte2tsx

Использует компилятор svelte для анализа части HTMLx компонента svelte. Затем преобразует HTMLx плюс исходную часть скрипта в файл tsx с помощью самописного преобразователя.

https://github.com/simlrh/svelte-language-server/blob/feature/extend-ts-support/src/plugins/ts-svelte/service.ts (вилка от svelte-language-server) использует его для создавать shadow-tsx-файлы из svelte-файлов. Затем он использует языковую службу машинописного текста, но проксирует запросы: он заменяет исходный путь к файлу на путь к сгенерированному tsx-файлу. Поэтому сервер языка машинописного текста проверяет сгенерированные файлы tsx. Результаты преобразуются, чтобы получить правильные позиции документа.

https://github.com/halfnelson/svelte-type-checker-vscode - это еще один языковой сервер, использующий библиотеку svelte2tsx.

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

Описание взято из комментария разработчика :

Компилятор:

  1. Компилятор читает все исходные файлы
  2. Все, кроме содержимого тегов скрипта, выбрасывается.
  3. Исходный код скрипта компилируется в допустимые файлы JS и декларации.
  4. Возвращается к шагу 2, где он заменяет источник TS в тегах скрипта скомпилированным JS.
  5. Компилятор Svelte компилирует весь исходный файл
  6. Компиляция выходных данных вышеупомянутого шага кэшируется, где HTML AST затем используется в средстве проверки типов.

Он использует экспортированные переменные и функции из сценария Svelte, а затем генерирует их как файлы объявлений, которые содержат класс, расширяющий среду выполнения SvelteComponent.

Проверка типов:

  1. Средство проверки типов находит все компоненты в кэшированном AST.
  2. Проверяет, что все компоненты в шаблоне являются допустимым классом, который расширяет SvelteComponent
  3. Проверяет действительные декларации, свойства и т. Д.

Другие подходы

См. Комментарии к этой ветке.

Самый полезный комментарий

Ок, отлично! Я продолжу и сделаю несколько PR, реструктурируя часть кода и подготовив его, чтобы затем мы могли распараллелить работу над отдельными элементами.

Все 37 Комментарий

Значит, никто из них не использует парсер Svelte?

svelte2tsx удаляет теги style / script, а затем использует компилятор svelte для анализа части htmlx.
svelte-ts использует компилятор svelte в качестве второго шага.
Текущий языковой сервер не использует компилятор svelte для синтаксического анализа.
Я обновлю исходный пост.

Я не был уверен, стоит ли мне этим поделиться, но какая-то часть может быть полезной. В прошлом году (июль / август 2019 года) я начал экспериментировать с проверкой типов в Svelte, зная, что делаю одноразовую работу, просто чтобы научиться. Он поддерживает некоторое неизвестное количество полного решения, может быть, 30%, и у него есть некоторые ограничения (подробнее об этом позже). Нет никаких тестов или внешней документации, потому что я всегда считал это тупиком.

Компиляция Svelte выполняется так же, как и сегодня, с препроцессором TS без проверки типов. Если вам нужны типы в разметке HTMLx, а не только тег сценария, вам понадобится препроцессор для обработки этого, или Svelte потребуется внутренняя обработка TS.

Проверка типов - это отдельный этап сборки, на котором используется необработанный код TypeScript из блоков script и AST разметки из svelte.compile для создания виртуального файла Svelte TypeScript исключительно для целей проверки типов. (здесь плагин Rollup создает виртуальный файл, а затем проверяет его тип ). Он похож на стратегию @halfnelson , но отличается тем, что создает обычный TS, а не TSX, поэтому в разметке он не пытается проверять типы тегов HTML и атрибуты, только то, что находится внутри тегов усов и компонентов Svelte. Он преобразует компоненты Svelte в конструкторы, которые получают new ed со своими реквизитами. Я частично поддержал множество конструкций Svelte, прежде чем понял, что увлекся больше, чем планировал, и дальнейший прогресс должен быть в серьезной инженерии.

API компилятора TypeScript используется для создания программы в памяти и проверки типов виртуального Svelte TS. В отличие от Svelte, он видит мир целыми программами, а не отдельными файлами. В то время казалось, что будет просто добавить инкрементную компиляцию и рабочие потоки в качестве будущих оптимизаций.

Виртуальный файл Svelte TS генерируется с исходной картой, которая затем используется для сопоставления диагностики TypeScript с исходным исходным кодом Svelte . Было довольно круто видеть, что типовые ошибки указывают на исходный код Svelte, но в целом я не был уверен в своем подходе.

Я помню, что одна проблема, которую я не мог понять, заключалась в диагностике, которая в некоторых случаях была недостаточно конкретной. IIRC пример был с проверкой типов свойств конструктора компонентов Svelte - VSCode каким-то образом указывал на отдельные ошибки свойств, но диагностика TS, которую я получал, указывала только на конструктор, а не на свойство проблемы.

Если вы посмотрите код, имейте в виду, что части в беспорядке, а некоторые комментарии могут быть устаревшими. Изначально я использовал magic-string а в какой-то момент перешел на source-map . И помните, что с самого начала это было обречено! Обратите внимание, что все это находится в ветке ts этого репо, а не в master .

Вот 4 файла, упомянутых выше:

Я немного покопался в исходном коде текущего сервера изящных языков. Вот мои мысли о машинописной части:

Обзор

Языковой сервер в целом работает примерно так:

  • DocumentManager обрабатывает все открываемые документы.
  • TypescriptPlugin регистрируется в DocumentManager чтобы вызывать события языкового сервера ("doHover" и т. Д.). Он не регистрируется напрямую: он обертывается с wrapFragmentPlugin что гарантирует, что TypescriptPlugin получает только содержимое внутри тега script . Сопоставление позиций (мыши / текстового курсора) выполняется в wrapFragmentPlugin чтобы информация отображалась в правильной позиции.
  • TypescriptPlugin использует service , оболочку службы языка машинописного текста. Он в основном делегирует все события этому языковому серверу с некоторым отображением, чтобы придерживаться типов, возвращаемых методом.
  • service принимает документ для работы, а также метод createDocument из TypescriptPlugin . service хранит свою собственную карту документов, которая на данный момент в основном является делегатом документа DocumentsManager . Каждый раз, когда service вызывается из TypescriptPlugin , service обновляет свою карту документов с данным документом (после его упаковки). createDocument будет вызываться, если языковая служба откроет документ, который еще не является частью карты документов, но этого никогда не произойдет, поскольку языковая служба машинописного текста не находит другие стройные модули (см. Следующий раздел).

Текущие недостатки и идеи по улучшению

  • Поскольку тег сценария предоставляется языковой службе машинописного текста как есть, он не может работать со специальным синтаксисом с расширенным форматом ($).
  • Служба языка машинописного текста пытается найти другие файлы, на которые есть ссылки в текущем открытом файле svelte. Для файлов .ts / .js это работает, для файлов .svelte - нет. Причина: языковая служба машинописного текста не знает окончания .svelte , поэтому просто предполагает, что это обычный файл машинописного текста, и ищет такие файлы, как ../Component.svelte.ts , что неверно. Чтобы исправить это, нам нужно реализовать метод resolveModuleNames на LanguageServiceHost и перенаправить все поиски файлов .svelte.ts на .svelte . Тогда языковая служба пройдется по всем стройным файлам. ПРИМЕЧАНИЕ. Чтобы реализовать это, нам также необходимо исправить следующую ошибку: поскольку языковая служба машинописного текста теперь будет просматривать все файлы, она будет вызывать createDocument из TypescriptPlugin . Но в настоящее время это возвращает весь документ, а не только содержимое внутри тега скрипта. Это ошибка внутри wrapFragmentPlugin без упаковки openDocument .
  • Чтобы реализовать более продвинутые функции, такие как «перейти к стройному файлу», щелкнув имя импорта стройного компонента, или заставить машинописный текст убрать знаки $, нам нужно добавить препроцессор. Этот препроцессор каким-то образом добавлял недостающие части для машинописного текста. В идеале все дополнительные элементы можно разместить поверх существующего кода, так что сопоставление позиций так же просто, как сопоставление позиций со смещениями. Однако это создает еще одну проблему: теперь у нас будет два сопоставления позиций: одно от всего файла svelte к тегу сценария, а второе от тега сценария к предварительно обработанному коду машинописного текста. На данный момент я не уверен, следует ли это делать или нам следует переработать способ получения документа TypescriptPlugin (и другие) - возможно, извлечение тега скрипта и предварительная обработка должны выполняться за один шаг. . Это будет означать изменение или полную замену wrapFragmentPlugin . Еще один аргумент в пользу переделки состоит в том, что для правильного определения таких вещей, как «неиспользуемая функция», мы должны учитывать часть html, поэтому тега сценария недостаточно.

Мысли?

На resolveModuleNames вот как член команды TypeScript сделал это для Vetur , и вот аналогичная версия, которую я сделал для Svelte, которая включает комментарий, в котором упоминается альтернативная стратегия, которая сработала, но копирование Vetur показалось лучше.

@dummdidumm не могли бы вы

По сути, просто откройте кучу проблем и позвольте людям заявить о них и управлять ими в качестве нашего неформального технического лидера в этом. Я бы предложил это сделать, но, честно говоря, я еще не чувствовал боли, ха-ха

также ссылка на исходный пост LSP компании orta для других пользователей, просматривающих https://github.com/sveltejs/svelte/issues/4518

Конечно, я хотел бы помочь в этом, но сейчас я просто случайный чувак, который очень взволнован языковым сервером, копается в коде и вносит некоторые предложения 😄 Мне также нужно немного времени, чтобы ознакомиться с кодом, его архитектура и ИСП в целом. Но я определенно готов двигаться вперед. @orta, поскольку вы поддерживаете это репо, что вы думаете по этому поводу?

👋 Да, мы оба случайные люди, которым просто не все равно - у меня даже нет стройных кодовых баз, чтобы вызвать мой зуд «так должно быть лучше». Итак, я очень рад видеть любое движение - @dummdidumm - отталкивает, и я

Ок, отлично! Я продолжу и сделаю несколько PR, реструктурируя часть кода и подготовив его, чтобы затем мы могли распараллелить работу над отдельными элементами.

Возможно, мы могли бы объединить подход @ryanatkn , svelete-ts и подход переменных eslint-plugin-svelte3 :

  1. Используйте препроцесс svelte для предварительной обработки скрипта в js.
    Здесь мы могли бы просто перейти на последнюю или следующую версию ES, чтобы преобразовать как можно меньше.
  2. Позвольте svelte скомпилировать предварительно обработанный исходный код.
  3. Затем мы можем ввести переменные результата, где injected = true в экземпляр ts source или ts AST, а затем использовать его с исходной картой для обеспечения проверки типа и автозаполнения.

Скрипт может иметь три фрагмента: модуль <script context="module"> , экземпляр и ус (шаблон). Часть усов на данный момент может быть просто выгружена в template.js. Но в долгосрочной перспективе мы могли бы отразить шаблон в tsx или plain ts, как подход Райанаткна.

Вары модуля, где module = true , будут вставлены в template.js и instance, но не наоборот.

Таким образом, нам не нужно заново реализовывать то, как найти реактивную переменную $: a = 1 или хранилище с префиксом $ в машинописном AST. Эти переменные, введенные svelte-компилятором, являются производными от оператора верхнего уровня, и мы переносим его в последнюю версию ES. Поэтому в большинстве случаев его не следует переименовывать с помощью машинописного текста.

Про реактивную переменную $: a можно было бы написать так:

$: a = 1

`` тс
пусть a: number
$: a = 1

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

Все это действительные ts, которые не нуждаются в преобразовании перед транспилированием

И хранилище с префиксом $, мы можем создать общую функцию, такую ​​как svelte / store get для извлечения типа хранилища

/** injected */
let $store = getType(store)
  1. Используйте препроцесс svelte для предварительной обработки скрипта в js.
    Здесь мы могли бы просто перейти на последнюю или следующую версию ES, чтобы преобразовать как можно меньше.
  2. Позвольте svelte скомпилировать предварительно обработанный исходный код.
  3. Затем мы можем ввести переменные результата, где injected = true в экземпляр ts source или ts AST, а затем использовать его с исходной картой для обеспечения проверки типа и автозаполнения.

Я думаю, что svelte-ts делает что-то в этом роде. Похоже, хорошая идея. На injected = true : какой именно код имеет это свойство?

Скрипт может иметь три фрагмента: модуль <script context="module"> , экземпляр и ус (шаблон). Часть усов на данный момент может быть просто выгружена в template.js. Но в долгосрочной перспективе мы могли бы отразить шаблон в tsx или plain ts, как подход Райанаткна.

Вары модуля, где module = true , будут вставлены в template.js и instance, но не наоборот.

Таким образом, нам не нужно заново реализовывать то, как найти реактивную переменную $: a = 1 или хранилище с префиксом $ в машинописном AST. Эти переменные, введенные svelte-компилятором, являются производными от оператора верхнего уровня, и мы переносим его в последнюю версию ES. Поэтому в большинстве случаев его не следует переименовывать с помощью машинописного текста.

Итак, вы хотите разделить файл на виртуальный .ts -файл и .template -файл? Почему бы не разместить части усов внизу файла .ts ? Таким образом мы также избавимся от предупреждений о "неиспользованном методе". По ходу AST: Да, я думаю, что здесь можно проявить смекалку и пропустить слишком много внимания к AST, потому что все, на что ссылаются в шаблоне, должно быть верхнего уровня в скрипте.

Про реактивную переменную $: a можно было бы написать так:

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

или

$: a = someFunction(b) as Type

Все это действительные ts, которые не нуждаются в преобразовании перед транспилированием

Можно ли вывести тип? Возможно, это просто то, что мы оставим как есть, и если пользователь хочет использовать машинописный текст, он должен сам определить let a: number . В качестве альтернативы мы могли бы вставить let a; , но тогда это будет any .

И хранилище с префиксом $, мы можем создать общую функцию, такую ​​как svelte / store get для извлечения типа хранилища

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

Да, это кажется приятным. Еще одна идея, которая у меня была, заключалась в создании геттеров / сеттеров.

Я думаю, что svelte-ts делает что-то в этом роде. Похоже, хорошая идея. На injected = true : в каком именно коде есть это свойство?

Результат компиляции имеет свойство vars, которое представляет собой массив объектов со следующим свойством:
name, export_name, injected, module, mutated, reassigned, referenced_from_script и доступный для записи
вы можете увидеть svelte compile api для получения дополнительной информации

Итак, вы хотите разделить файл на виртуальный .ts-файл и .template-файл? Почему бы не разместить части усов внизу .ts-файла? Таким образом мы также избавимся от предупреждений о "неиспользованном методе".

Это подход eslint-plugin-svelte3 . Также я просто подумал, хотим ли мы поддерживать ts в шаблоне, потому что нет атрибута для указания языка.

Можно ли вывести тип?

Поскольку этот виртуальный источник используется только для проверки типа и автозаполнения, я предполагаю, что этого можно добиться, просто скопировав первый оператор assign для его инициализации, но, похоже, это требует дополнительной работы.

Можно ли вывести тип? Возможно, это просто то, что мы оставим как есть, и если пользователь хочет использовать машинописный текст, он должен сам определить let a: number. В качестве альтернативы мы могли бы вставить let a ;, но тогда это будет любой.

Похоже, что нормальный вывод не приводит нас к этому полностью. (вы имели в виду вывод времени компиляции? не уверен, что это может сработать)

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

Причина в том, что в линейном потоке кода TypeScript может видеть, что a не было переназначено, но области вложенных функций не имеют этой гарантии, а TypeScript ошибается на стороне безопасности типов.

пример игровой площадки

Собственно, это было моё беспокойство по этому поводу. Но, как указал @ jasonlyu123 , мы можем просто скопировать определение (все после = ).

До

$: a = true;

После

let a = true;
$: a = true;

Но я не знаю, возможно ли это при любых обстоятельствах. Особенно с большими объектами, где кто-то реализует интерфейс, это не приведет к выводу всего, как мог бы ожидать пользователь. Кроме того, копи-паста может стать довольно большой. Так что я по-прежнему больше на стороне «пусть пользователь вводит это».

Спасибо за разъяснения, я неправильно понял, что сказал @ jasonlyu123 . Он устраняет проблему с областью вложенных функций, о которой я упоминал.

Есть ли пример, когда это нехорошее поведение по умолчанию? Кажется, что в большинстве случаев он работает правильно с точки зрения эргономики, а другие случаи можно исправить вручную. Вот несколько примеров.

Думаю, ты прав. Это самая эргономичная вещь. Меня беспокоит, что предполагаемый тип неверен, например, свойство string вместо определенного объединения строк ( 'a' | 'b' ). Но, как вы сказали, эти случаи не так распространены, и тогда пользователь все равно может ввести его вручную.

Часть меня вроде: «отлично, явный синтаксис let работает потрясающе», но в конечном итоге это не сработает с поддержкой JS из коробки.

Как насчет того, чтобы просто заменить первую метку, чтобы:

$: a = 1
$: a = 2

преобразоваться в

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

Я немного переосмыслил и не могу найти преимущества «определения копии» перед «заменой $: на let ». Можете ли вы подумать о каких-либо недостатках замены $: на let ?

Что касается различий между предполагаемым типом и тем, что ожидал пользователь, можем ли мы предоставить действия по рефакторингу, чтобы преобразовать его в определение вручную? Это действие предложит пользователь ручного типа переменный , как это , Или рефакторинг к его предполагаемому типу , если это возможно.

до

$: a = 'const1'

после

let a : AskUserForType
$: a = 'const1'

Также думает о том, на что вы указываете, Что-то узнал 👍

Ха, умная идея, просто полностью замените $: на let . Я также не знаю ситуаций, когда это закончилось бы плохо.
Мы также могли подумать о том, чтобы всегда проводить трансформацию, а не только для первого лейбла. Тогда будет ошибка «невозможно повторно объявить», которая намекает пользователя на «woops, я дважды определил эту переменную» - или есть ситуации, когда это было бы жизнеспособным, а не ошибкой?

Хорошо, я не могу придумать ни одной причины, по которой замена лейбла не удалась бы. (поскольку break $ никогда не работает внутри назначений, верно?)

Для повторного объявления той же реактивной переменной, похоже, Svelte допускает это и работает так, как вы ожидаете. ( см. этот пример ответа ). Я лично не вижу, что использую этот шаблон намеренно и хотел бы получить ошибку, но она находится на территории интерпретации семантики Svelte, так что не верьте мне на слово!

Хорошо, так что повторное объявление этого шаблона действительно работает. Я бы сказал, что нам лучше разрешить это тогда - или позже сделать флаг «повторное объявление не разрешено», который по умолчанию отключен.

@halfnelson создал языковой сервер на основе своего подхода svelte2tsx в https://github.com/halfnelson/svelte-type-checker-vscode . Мне очень нравится, насколько хорошо это все работает, но я все же предпочел бы не использовать tsx в качестве промежуточного вывода, потому что я боюсь, что он может быть слишком медленным и иметь некоторые ограничения - но это просто мое внутреннее ощущение. Тем не менее, мы могли бы многое почерпнуть из svelte2tsx: как выполняется поток синтаксического анализа-компиляции-преобразования, а также обширный набор тестов. Как другие видят подход к преобразованию кода в tsx?

Я задумался о том, как могут выглядеть другие преобразования. Вот несколько примеров. Обратная связь приветствуется.

Как выглядит определение компонента

Оригинал:

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

    export let todo: string;

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

Конвертировано:

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

Объяснение:
Каким-то образом получить все свойства (легко) и отправленные события (сложно) и добавить их в один большой объект свойств. Добавление событий с префиксом on: должно минимизировать коллизии и упростить реализацию предложений автозаполнения.

(в следующих примерах определение компонента опущено для краткости)

Использовать переменную в шаблоне

Оригинал:

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

Конвертировано:

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

Объяснение:
Добавьте произвольные элементы const , возможно с префиксом какого-нибудь непонятного символа Юникода, чтобы проверить правильность использования переменных, а также убрать "неиспользуемое" предупреждение. Может быть, тогда нам нужно подумать о том, как избавиться от неиспользованного _bla -предупреждения ...

Использовать собственный прослушиватель событий

Оригинал:

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

Конвертировано:

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

Объяснение:
При использовании собственных прослушивателей событий создайте соответствующий элемент, а затем добавьте прослушиватель событий. Таким образом, мы воспользуемся преимуществами addEventListener -типов, которые имеют перегрузки практически для каждого слушателя: .addEventListener('click', ...); -> событие имеет тип MouseEvent . Этот шаблон будет использоваться после проверки, является ли элемент другим стройным компонентом, для которого определено событие (также см. Следующий пример). Недостатки: Что делать, если Svelte используется не в веб-среде (Nativescript)?

Использовать другой компонент

Оригинал:

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

Конвертировано:

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

Объяснение:
Каждый компонент получает определение класса, поэтому мы просто создаем экземпляр этого класса.

Каждая петля

Оригинал:

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

Конвертировано:

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

Объяснение:
forEach кажется самым простым для преобразования. Внутри мы рекурсивно можем применить все остальные функции.

Если

Оригинал:

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

Конвертировано:

if (x === 1) {

} else if (x === 2) {

} else {

}

Объяснение:
Довольно понятно.

Недостающие части

  • слоты
  • специальные компоненты svelte:x
  • другие директивы элемента / компонента, такие как use:action , transition , bind
  • $ -синтаксис

Что вы ребята думаете? Жизнеспособны и осуществимы ли эти преобразования? Я что-то пропустил?

Что касается класса компонентов, я бы предпочел расширить собственный класс SvelteComponent svelte, но я не знаю, легко ли это реализовать или нет.

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

и импортировать этот интерфейс

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

Отметил, что $$slots , вероятно, внутренний api svelte, я просто копаю его из скомпилированного кода

Причина этого в том, что затем его можно использовать для генерации .d.ts которые можно использовать для ввода vanilla ts / js.
Также потому, что, хотя это и достаточно странно, это действительный синтаксис:

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

Директивы элемента

Transition , use:action и другие директивы элементов могут быть преобразованы как

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

Ждите

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

к

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

привязать: это

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

к

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

необходимо обернуть его в обратный вызов, потому что он не доступен сразу

context = "модуль"

Это немного сложно

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

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

должен быть скомпилирован

import _ from "lodash"

let count = 1;

// any block is ok
{
    let b;
{

А также что, если пользователь каким-то образом использует разные языки в двух скриптах, например, js в модуле и ts?

Красиво, хорошо выглядит. Для «Использовать переменную в шаблоне» const _bla = bla.bla; может быть просто bla.bla; позволяя TypeScript выполнять свою проверку без необходимости отфильтровывать диагностику «неиспользуемая переменная». Я не могу вспомнить ни одного углового случая, когда это не сработает.

Некоторые из команды Vue изучают плагин сервера машинописного языка - https://github.com/znck/vue-developer-experience

Это интересный ракурс, поскольку сегодня этот опыт идет извне TypeScript и внутри (как это делает vetur, поскольку он запускает собственный tsserver-ish, а также html / css / etc LSP), или вы пытаетесь пойти изнутри TypeScript и работать вовне ( например, это предложение), где вы манипулируете TSServer, чтобы эффективно поверить в то, что файлы .vue являются законным TypeScript, маскируя не-TS код.

Хотя сегодня это может работать только при наличии патчей для машинописного текста.

Это интересный подход. Но как тогда будут работать такие функции, как автозаполнение для html или css? Придется ли все это реализовать «вручную» и больше не полагаться на готовые языковые серверы, или я чего-то упускаю?

Видите ли вы какие-либо преимущества в использовании одного по сравнению с лишним?

Все, кроме поддержки JS / TS, будет обрабатываться отдельным LSP vscode. Было бы так, как если бы это расширение удаляло всю поддержку JS / TS и обрабатывало только остальное, тогда плагин машинописного текста обрабатывал бы все остальное.

В этом случае вы получаете все инструменты TS «бесплатно», переход к символу, сопоставлениям файлов и т. Д.

Я, вероятно, не рекомендую это, сегодня для работы требуются вилки или патчи TypeScript, что является довольно высоким барьером для входа - и команда TypeScript все еще не уверена, могут ли / когда плагины компилятора иметь такой доступ.

Спасибо за понимание. Тогда я бы сказал, что мы с нынешним подходом (снаружи внутрь).

Если мы пойдем с описанным выше подходом «создать виртуальный ts-файл», как бы мы реализовали его и сохранили сопоставления источников?

Сегодня это просто, потому что это просто «найти начало скрипта, найти конец скрипта», «извлечь часть скрипта» и для сопоставления «добавить смещение svelte файла в позиции».
Если мы изменим часть скрипта сейчас, нам понадобится более сложное отображение:

Оригинал

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

Сопоставлено:

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

Любые идеи? Я никогда раньше не занимался картированием источников ..

Некоторые из команды Vue изучают плагин сервера машинописного языка - https://github.com/znck/vue-developer-experience

Это интересный ракурс, поскольку сегодня этот опыт идет извне TypeScript и внутри (как это делает vetur, поскольку он запускает собственный tsserver-ish, а также html / css / etc LSP), или вы пытаетесь пойти изнутри TypeScript и работать вовне ( например, это предложение), где вы манипулируете TSServer, чтобы эффективно поверить в то, что файлы .vue являются законным TypeScript, маскируя не-TS код.

Хотя сегодня это может работать только при наличии патчей для машинописного текста.

Патч удаления TypeScript PR объединен.
https://github.com/znck/vue-developer-experience/pull/7

Итак ... @orta это означает, что недостатков "неофициальных" больше нет? Или это все еще неофициально, только не через патч, а через какую-то конфигурацию package.json вы должны знать и которая может измениться в любой момент?

Другая тема: на данный момент я чувствую, что мы все еще обсуждаем, как лучше всего подойти к этому, и если мы решим, то потребуется некоторое время, чтобы его реализовать. Что вы, ребята, думаете об использовании svelte2tsx тем временем? Это могло бы послужить очень хорошей отправной точкой, мы могли бы провести несколько тестов вокруг него, а затем мы либо увидим «хорошо, это действительно отлично работает, давайте не будем его менять», либо постепенно отойдем от этого. Мысли?

Да, было два способа редактирования потока TS по умолчанию. Он все еще исправляет TypeScript, но во время выполнения заменяет функции TS. Думаю, это разумно, учитывая, что разработка плагинов еще не входит в план.

Я был бы плохим членом основной команды, если бы порекомендовал это;)

Попробовать svelte2ts - неплохой путь для исследования!

Вы можете увидеть, как будет выглядеть svelte2tsx, используя этот плагин vscode: https://marketplace.visualstudio.com/items?itemName=halfnelson.svelte-type-checker-vscode вместе с Svelte Beta (лучше всего работает, если вы отключите бета-версию svelte. опции машинописного текста, чтобы вы не получали двойных подсказок :))

Даже если я не использую svelte2tsx, я вижу обсуждение того, какие преобразования необходимо сделать для svelte JS-кода, чтобы машинописный текст оставался довольным. Набор тестов для svelte2tsx охватывает не только изменения, которые необходимо внести в шаблон, но и тег сценария, который, если вы собираетесь использовать только тег сценария, может стать хорошей отправной точкой: https://github.com/halfnelson / svelte2tsx / дерево / мастер / тест / svelte2tsx / образцы

htmlx2jsx - это папка, в которой проверяются изменения шаблона (htmlx), а svelte2tsx samples - это специфические изменения, необходимые для особого злоупотребления svelte синтаксисом js :)

Только что обнаружил это репо https://github.com/pivaszbs/svelte-autoimport . Возможно, стоит изучить, если мы хотим импортировать intellisense независимо от svelte2tsx .

Я думаю, что мы находимся в точке, где большая часть общего направления этого проекта упорядочена и, кажется, работает аккуратно. Я дам вам неделю для обратной связи, но я очень рад, что нам не нужно сейчас переосмысливать ее архитектурно.

Похоже, ни у кого нет возражений, поэтому я закрою это. Спасибо всем за обсуждение этого вопроса!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги