The language-tools
repository is a monorepo containing several packages which are closely related to each other.
svelte2tsx
- transforms Svelte code into JSX/TSX codelanguage-server
- a language server adhering to the LSPsvelte-check
- a command line tool to get diagnostics for Svelte codesvelte-vscode
- the Svelte for VSCode extension
This is how they are related:
svelte-vscode |
|-> language-server -> svelte2tsx
svelte-check |
As briefly touched already, this is a language-server adhering to the Language Server Protocol (LSP). The protocol defines the communication between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc.
Our language-server
can roughly be split into four areas:
- CSS: Provides IntelliSense for the things inside
<style>
. Internally it mostly forwards stuff to thevscode-css-languageservice
. - HTML: Provides IntelliSense for basic HTML tags like
div
,a
etc. Internally it mostly forwards stuff to thevscode-html-languageservice
. Svelte-specific template syntax is NOT handled here. - Svelte: Provides the diagnostics of the Svelte compiler. If you use preprocessors, those are invoked beforehand - that's why we need the
svelte.config.js
to know how to preprocess your files. It also does the formatting throughprettier-plugin-svelte
and other cross-cutting concerns like the "Extract Component" refactoring. - TypeScript/JavaScript: Provides IntelliSense for all JS/TS related stuff. This not only includes code inside
<script>
but also inside the template the moment you use any of the Svelte specifics like bindings or template syntax.svelte2tsx
is used in here, and only here.
The last area, TS/JS, is where most of the work is done. Svelte is a mix of JavaScript/TypeScript inside script
and more of it within Svelte's template syntax. To get a holistic view of the file, we need to account for both.
This is also the reason why preprocessors such as svelte-preprocess
cannot do type checking because it produces wrong diagnostics. To preprocess the script content, it only gets the content from the script. Think of this situation:
<script lang="ts">
let a: number = 1;
</script>
{a}
svelte-preprocess
only gets let a = 1
and the diagnostics would output that a
is never used, which is wrong.
To get that holistic view, we need svelte2tsx
.
To get a holistic view of Svelte files, we have two options:
- Write a language service ourselves which is capable of doing things like auto complete, go to definition, rename etc.
- Convert the Svelte code to something an existing language service can process which then does auto complete, go to definition, rename etc for us.
We chose the second option because TypeScript provides a language service which can do all the heavy lifting for us: We give it some files and then invoke methods like getQuickInfoAtPosition
for hover info. These files need to be in a format the language service can understand: A form of JavaScript or TypeScript. svelte2tsx
is the package which does this transformation: Pass in Svelte code and get back JS or TS code, depending on whether or not you use TS in Svelte.
svelte2tsx
uses some ambient definitions which are loaded by the language server to do some of the transformations. It also provides type definitions which are recognized by the TypeScript compiler and define intrinsic elements, attributes and events - so if you ever get an error that a DOM attribute is not assignable to a DOM element, it's likely a missing declaration in there.
The code generated by svelte2tsx
is not runnable in any way and does not actually exist at runtime when you run your app, it purely exists for the IntelliSense.
The code also returns source mappings so we know which position in the original code corresponds to which generated position.
The package is called svelte2tsx because originally it transformed the code to jsx/tsx - but that's no longer the case.
This example shows how our language-server
uses svelte2tsx
:
Svelte file changes:
- Svelte file comes in
<script>
export let world = 'name';
</script>
<h1>hello {world}</h1>
- Transform Svelte to TS/JS
<></>;
function render() {
// -- the transformed script:
let world = 'name';
// -- the transformed template
async () => {
{
svelteHTML.createElement('h1', {});
world;
}
};
// -- obtained props, slots and events,
// to get intellisense for this component in other components
return { props: { world }, slots: {}, events: {} };
}
// -- generate a class
export default class _World_ extends createSvelte2TsxComponent(__sveltets_2_partial(render)) {}
- Pass that result to the TypeScript language service
User wants hover info:
- User hovers over a component property in the template
- Get generated file from original file
- Transform the position (line, character) where the user hovers from the original to the generated position
- Invoke TypeScript language service's method for hover info with the generated position
- Hand back results to user
As you can see, the language-server
is mostly doing position transformations and then handing the main work off to the TypeScript language service. Sometimes we also need to do post processing of the results and work around quirks.
As mentioned above, preprocessors cannot do type checking. That's why we added svelte-check
, a command line tool to get diagnostics for Svelte code.
The tool is using the language-server
to do this work. Inside the language-server
we added an entry point for svelte-check
which spins up the language server and just executes diagnostics.
This is the Svelte for VSCode extension most of you probably use. Syntax highlighting is provided through a JSON file which tells VSCode how to tokenize the input. To get all the intellisense capabilities, the extension uses the the language-server
. VSCode implements the LSP and also provides a node library to do most of the wiring for us. Some things like special refactoring commands ("Extract Component") need some extra glue code, but that's all.