im

How to use Typescript with Svelte (updated)

Probably the ultimate Svelte.js with Typescript HOWTO there is

With the official Typescript support in Svelte in place we can finally have turtles all the way down! The only things required are proper tools and plugins. Read on to learn more.

Update (2020-08-10)#

This article has been updated since the official Typescript support for Svelte was released. The official Svelte blog post explains how to get started with Svelte and Typescript quickly, but this article takes a little different approach with an opinionated (improved if you ask me) setup, plus some extra bonus materials.

Also, Rollup, which I use in the article as a bundler is pretty slow. Why? Because it needs to re-compile the whole shebang every time you change a file. It produces very small and efficient bundles though.

Things in tech move almost at the speed of light. Since this article was written a few other alternative and faster bundlers popped up. Bundlers that help you shorten the feedback loop. You have Snowpack and Svite for example.

If you are curious about Snowpack I have written an article on it - Snowpack with Svelte, Typescript and Tailwind CSS is a very pleasant surprise.

Boilerplate#

One of the show stoppers for people who want to start using Svelte was the lack of first class Typescript integration. And it's not so much about the type safety as about tooling. Great supporting tools for any framework are important for its future growth and popularity. Svelte is still a young framework, but without proper tooling ecosystem surrounding it, I am afraid it might die. It would be such shame.

I've done some experimenting with Rollup, Webpack and Parcel trying to get Svelte to play nice with Typescript. While I achieved decent results with all of them, I will use Rollup here as it has a pretty straight forward setup plus the re-compilation step was the fastest of them all.

Let's start with a standard vanilla JS Svelte setup and adjust our project there to enable Typescript support. The best way to learn is by doing.

$ npx degit sveltejs/template svelte-and-typescript
$ cd svelte-and-typescript && npm i

We now have a simple Svelte app, with all the greatest and latest dependencies, that we can run with npm run dev.

Rollup Refactoring#

I prefer a slightly different Rollup configuration, so we will adjust it a bit. It requires us to bring in a few new utilities first. We will start with them.

$ npm add -D rollup-plugin-serve rollup-plugin-html2 rim-raf npm-run-all

Time to refactor our rollup.config.js to something more readable.

// rollup.config.js

import commonjs from '@rollup/plugin-commonjs';
import html from 'rollup-plugin-html2';
import livereload from 'rollup-plugin-livereload';
import resolve from '@rollup/plugin-node-resolve';
import serve from 'rollup-plugin-serve';
import svelte from 'rollup-plugin-svelte';
import { terser } from 'rollup-plugin-terser';

const isDev = process.env.NODE_ENV === 'development';
const buildDir = 'dist';
const port = 3000;

// define all our plugins
const plugins = [
svelte({
dev: isDev,
// extract all styles to an external file
css: css => {
css.write(`${buildDir}/bundle.css`);
},
extensions: ['.svelte'],
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
// injects your bundles into index page
html({
template: 'src/index.html',
fileName: 'index.html',
}),
];

if (isDev) {
plugins.push(
// like a webpack-dev-server
serve({
contentBase: buildDir,
historyApiFallback: true, // for SPAs
port,
}),
livereload({ watch: buildDir })
);
} else {
plugins.push(terser({ sourcemap: isDev }));
}

module.exports = {
input: 'src/main.js',
output: {
name: 'bundle',
file: `${buildDir}/bundle.js`,
sourcemap: isDev,
format: 'iife',
},
plugins,
};

Alright, Rollup config done. Now we need to fix our package.json too. Replace your scripts property with the following content.

{
"start": "rimraf dist && run-p watch:*",
"clean": "rimraf dist",
"build": "run-s clean compile",
"compile": "cross-env NODE_ENV=production rollup --config",
"watch:build": "cross-env NODE_ENV=development rollup --config --watch"
}

There are a few things going on in scripts. We use run⁠-⁠s (run serial) and run⁠-⁠p (run parallel) from the npm-run-all utility package to run NPM scripts in serially and in parallel. We also use rimraf and cross⁠-⁠env utilities to achieve cross-platform functionality.

We now need to create a minimal index.html file in the src directory with the following content.

<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte App</title>
<link rel='stylesheet' href='/bundle.css'>
</head>
<body></body>
</html>

We can now run and build our app with npm start and npm build respectively. Try it!

From JS to TS#

It's time to bring out the big guns - Typescript. For this to work we need to add a few NPM modules - @rollup/plugin-typescript, @tsconfig/svelte, typescript and tslib, which is a dependency for Rollup's typescript plugin.

$ npm add -D typescript tslib @rollup/plugin-typescript @tsconfig/svelte

Done? Good! Now we have to create a minimal tsconfig.json using @tsconfig/svelte as our base.

{
"extends": "@tsconfig/svelte/tsconfig.json",

"include": ["src/**/*"],
"exclude": ["node_modules/*", "dist/*"]
}

We also need to add Typescript support to our Rollup config, so we can process regular Typescript files.

// add typescript plugin to imports
import typescript from '@rollup/plugin-typescript';

// and replace plugins section with this
const plugins = [
svelte({
dev: isDev,
extensions: ['.svelte']
}),
typescript({ sourceMap: isDev }),
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
html({
template: 'src/index.html',
fileName: 'index.html'
}),
];

We will now test drive this with an actual Typescript file. Create a timer.ts file in your src folder.

// timer.ts

import { readable } from 'svelte/store';

const timer = readable(0, (set) => {
let current: number = 0;

const id = setInterval(() => {
current += 1;
set(current);
}, 1000);

return () => clearInterval(id);
});

export { timer };

Let's require it in our App.svelte and see if it works.

<!-- App.svelte -->

<script>

import { timer } from './timer';
</script>

<main>
<h2>Count is
{ $timer }</h2>
</main>

Fire up the app and see if it works. Hint: It should!

Just your main.js to main.ts and don't forget to change the input property in the Rollup config.

When we now start the app it should also work as expected. Achievement unlocked. We now can work with Typescript files in our Svelte project!

What about Typescript in Svelte files?#

Ah! Of course! Glad you asked. The most important thing! For that you have to use the awesome svelte-preprocess plugin. It's a plugin that can help you pre-process many different kinds of languages in Svelte files such as SASS, Less, Pug and, of course, Typescript.

$ npm add -D svelte-preprocess

Tell Svelte plugin to use preprocess in our rollup.config.js

// import preprocess
import preprocess from `svelte-preprocess`;

// add preprocess to Svelte config

// ...
svelte({
dev: isDev,
extensions: [".svelte"],
preprocess: preprocess()
})
// ...

In our App.svelte we can now change the script tag from default JS to Typescript. To test that it actually works we will add a variable with a type.

<script lang="typescript">
import
{ timer } from './timer';

let a: number = 42;
</script>

<main>
<h2>Count is
{$timer}</h2>
<p>What's the meaning of life?
{a}</p>
</main>

You can annotate your scripts in different ways. All examples below are valid.

<script lang="ts">

<script type="ts">

<script lang="typescript">

<script type="typescript">

And now we can write code like this.

<script lang="typescript">
import
{ timer } from './timer';

let a: number = 42;
$: sum = a + $timer;
</script>

Our goal is accomplished. We can write parts of our codebase in Typescript and we can also use Typescript in our Svelte files.

Check your Svelte files for errors with svelte-check#

Svelte language-tools repo includes a great little CLI utility called svelte-check.

It helps you check your files for unused CSS, Svelte a11y hints and JavaScript/TypeScript compiler errors.

Let's try to integrate it in out setup. First we need to install it.

$ npm add -D svelte-check npm-run-all

Next we need to change our scripts section in package.json to this.

// package.json scripts section

{
"start": "rimraf dist && run-p watch:*",
"clean": "rimraf dist",
"check": "svelte-check",
"build": "run-s clean check compile",
"compile": "cross-env NODE_ENV=production rollup --config",
"watch:check": "svelte-check --watch",
"watch:build": "cross-env NODE_ENV=development rollup --config --watch"
}

When we start our dev server (npm start), svelte⁠-⁠check runs in watch mode and continuously checks our Svelte files for errors. When we bundle the application (npm run build) we run svelte⁠-⁠check before the bundling step.

BONUS: Speed up your compilation#

Rollup is great for building production bundles, but TS compiler is really slow. If you are feeling adventurous and want faster compilation times you can try the SWC compiler to transpile your Svelte TS scripts. It's written in Rust and is still young, but looks very promising.

First, we have to install the SWC npm package.

$ npm add -D @swc/core

Now we have to change the Typescript configuration in our Rollup config.

// rollup.config.js

import { transformSync } from '@swc/core';

// change the preprocess to this

preprocess: preprocess({
typescript({ content }) {
// use SWC to transpile TS scripts in Svelte files
const { code } = transformSync(content, {
jsc: {
parser: { syntax: 'typescript' }
}
});
return { code };
}
})

I've also tried to replace the regular Rollup Typescript plugin with rollup-plugin-swc, but for some reason it cannot resolve external Typescript files in Svelte files.

BONUS: Svelte editor integration#

You are most likely using VS Code, Neovim or Vim when coding. There are extensions for all of them - Svelte extension for VSCode and coc-svelte for Neovim/Vim.

Before the official TS support you had to create a special svelte.config.js file so the editor could understand Typescript in Svelte files. This has now changed and you no longer need to do it.

But if you see that your editor is not recognizing Typescript you can create that file with the following contents.

// svelte.config.js

const { preprocess } = require('svelte-preprocess');

module.exports = {
preprocess: preprocess(),
};

There might be Svelte extensions for other editors too. You can find more info at https://github.com/sveltejs/language-tools.

BONUS: Use Prettier to format your files#

We can also install the "love-it-or-hate-it" Prettier plugin to help us out with code formatting.

$ npm add -D prettier prettier-plugin-svelte

Create a Prettier config file and adjust it to your needs.

// prettier.config.js

module.exports = {
tabWidth: 2,
semi: true,
singleQuote: true,
printWidth: 100,
plugins: ['prettier-plugin-svelte'],
svelteSortOrder: 'styles-scripts-markup',
svelteStrictMode: false,
svelteBracketNewLine: true,
};

If you have a Prettier plugin in your editor it will now read these settings.

And if you want to run Prettier from the command line, with npm run format for example, add this line to your scripts section in package.json.

{
"scripts": {
// ...
"format": "prettier --write src"
}
}

You can probably add other useful code linting plugins, but that's out of the scope for the article, so I will stop here.

Conclusion#

There you go. We just learned how to use Typescript in Svelte together with Rollup bundler. But no matter what bundler you use svelte-preprocess always plays the central role when it comes to Typescript integration in Svelte. Actually, without it we wouldn't be able to use any other languages in our Svelte files.

Even though it's now possible to use Typescript in Svelte, I would recommend to keep your Svelte files thin and write the heavy logic in separate files. By utilizing libraries such as XState, RxJS and Rambda you can write concise and testable code. The bar is high, but it's totally worth it! Give them a fair chance!

Plugins mentioned

Before you go#

You can find the code here https://github.com/codechips/svelte-and-typescript

If you want to use this setup in your next Svelte project, you can use my template.

$ npx degit codechips/svelte-starter-template#with-typescript my-app

Hope you learned something new with me today and if something is not right or can be improved, please ping me on Twitter or shoot me an email.