Integrating TypeScript with Svelte
Svelte is one of the newer JavaScript frameworks and it’s rapidly rising in popularity. It’s a template-based framework, but one which allows for arbitrary JavaScript inside the template bindings; it has a superb reactivity story that’s simple, flexible and effective; and as an ahead-of-time (AOT) compiled framework, it has incredibly impressive perf, and bundle sizes. This post will focus on configuring TypeScript inside of Svelte templates. If you’re new to Svelte, I’d urge you to check out the introductory tutorial and docs.
If you’d like to follow along with the code (or you want to debug what you might be missing in your own project) you can clone the repo. I have branches set up to demonstrate the various pieces I’ll be going over.
Basic TypeScript and Svelte setup
Let’s look at a baseline setup. If you go to the initial-setup
branch in the repo, there’s a bare Svelte project set up, with TypeScript. To be clear, TypeScript is only working in stand-alone .ts
files. It’s not in any way integrated into Svelte. Accomplishing the TypeScript integration is the purpose of this post.
I’ll go over a few pieces that make Svelte and TypeScript work, mainly since I’ll be changing them in a bit, to add TypeScript support to Svelte templates.
First, I have a tsconfig.json
file:
{
"compilerOptions": {
"module": "esNext",
"target": "esnext",
"moduleResolution": "node"
},
"exclude": ["./node_modules"]
}
This file tells TypeScript that I want to use modern JavaScript, use Node resolution, and exclude a node_modules
from compilation.
Then, in typings/index.d.ts
I have this:
declare module "*.svelte" {
const value: any;
export default value;
}
This allows TypeScript to co-exist with Svelte. Without this, TypeScript would issue errors any time a Svelte file is loaded with an import statement. Lastly, we need to tell webpack to process our Svelte files, which we do with this rule in webpack.config.js
:
{
test: /.(html|svelte)$/,
use: [
{ loader: "babel-loader" },
{
loader: "svelte-loader",
options: {
emitCss: true,
},
},
],
}
All of that is the basic setup for a project using Svelte components and TypeScript files. To confirm everything builds, open up a couple of terminals and run npm start
in one, which will start a webpack watch, and npm run tscw
in the other, to start a TypeScript watch task. Hopefully both will run without error. To really verify the TypeScript checking is running, you can change:
let x: number = 12;
…in index.ts
to:
let x: number = "12";
…and see the error come up in the TypeScript watch. If you want to actually run this, you can run node server
in a third terminal (I recommend iTerm2, which allows you to run these terminals inside tabs in the same window) and then hit localhost:3001
.
Adding TypeScript to Svelte
Let’s add TypeScript directly to our Svelte component, then see what configuration changes we need to make it work. First go to Helper.svelte
, and add lang="ts"
to the script tag. That tells Svelte there’s TypeScript inside the script. Now let’s actually add some TypeScript. Let’s change the val
prop to be checked as a number, via export let val: number;
. The whole component now looks like this:
<script lang="ts">
export let val: number;
</script>
<h1>Value is: {val}</h1>
Our webpack window should now have an error, but that’s expected.
We need to tell the Svelte loader how to handle TypeScript. Let’s install the following:
npm i svelte-preprocess svelte-check --save
Now, let’s go to our webpack config file and grab svelte-preprocess
:
const sveltePreprocess = require("svelte-preprocess");
…and add it to our svelte-loader:
{
test: /.(html|svelte)$/,
use: [
{ loader: "babel-loader" },
{
loader: "svelte-loader",
options: {
emitCss: true,
preprocess: sveltePreprocess({})
},
},
],
}
OK, let’s restart the webpack process, and it should build.
Add checking
So far, what we have builds, but it doesn’t check. If we have invalid code in a Svelte component, we want that to generate an error. So, let’s go to App.svelte
, add the same lang="ts"
to the script tag, and then pass an invalid value for the val
prop, like this:
<Helper val={"3"} />
If we look in our TypeScript window, there are no errors, but there should be. It turns out we don’t type check our Svelte template with the normal tsc compiler, but with the svelte-check utility we installed earlier. Let’s stop our TypeScript watch and, in that terminal, run npm run svelte-check
. That’ll start the svelte-check process in watch mode, and we should see the error we were expecting.
Now, remove the quotes around the 3
, and the error should go away:
Neat!
In practice, we’d want both svelte-check and tsc running at the same time so we catch both errors in both our TypeScript files and Svelte templates. There’s a bunch of utilities on npm that allow can do this, or we can use iTerm2, is able to split multiple terminals in the same window. I’m using it here to run the server, webpack build, tsc build, and svelte-check build.
This setup is in the basic-checking
branch of the repo.
Catching missing props
There’s still one problem we need to solve. If we omit a required prop, like the val
prop we just looked at, we still won’t get an error, but we should, since we didn’t assign it a default value in Helper.svelte
, and is therefore required.
<Helper /> // missing `val` prop
To tell TypeScript to report this as an error, let’s go back to our tsconfig
, and add two new values
"strict": true,
"noImplicitAny": false
The first enables a bunch of TypeScript checks that are disabled by default. The second, noImplicitAny
, turns off one of those strict checks. Without that second line, any variable lacking a type—which is implicitly typed as any
—is now reported as an error (no implicit any, get it?)
Opinions differ widely on whether noImplicitAny
should be set to true
. I happen to think it’s too strict, but plenty of people disagree. Experiment and come to your own conclusion.
Anyway, with that new configuration in place, we should be able to restart our svelte-check task and see the error we were expecting.
This setup is in the better-checking
branch of the repo.
Odds and ends
One thing to be aware of is that TypeScript’s mechanism for catching incorrect properties is immediately, and irreversibly switched off for a component if that component ever references $$props
or $$restProps
. For example, if you were to pass an undeclared prop of, say, junk
into the Helper component, you’d get an error, as expected, since that component has no junk
property. But this error would immediately go away if the Helper
component referenced $$props
or $$restProps
. The former allows you to dynamically access any prop without having an explicit declaration for it, while $$restProps
is for dynamically accessing undeclared props.
This makes sense when you think about it. The purpose of these constructs is to dynamically access a property on the fly, usually for some sort of meta-programming, or to arbitrarily pass attributes on to an html element, which is common in UI libraries. The existence of either of them implies arbitrary access to a component that may not have been declared.
There’s one other common use of $$props
, and that’s to access props declared as a reserved word. class
is a common example of this. For example:
const className = $$props.class;
…since:
export let class = "";
…is not valid. class
is a reserved word in JavaScript but there’s a workaround in this specific case. The following is also a valid way to declare that same prop—thanks to Rich Harris for helping with this.
let className;
export { className as class };
If your only use of $$props
is to access a prop whose name is reserved, you can use this alternative, and maintain better type checking for your component.
Parting thoughts
Svelte is one of the most promising, productive, and frankly fun JavaScript frameworks I’ve worked with. The relative ease with which TypeScript can be added is like a cherry on top. Having TypeScript catch errors early for you can be a real productivity boost. Hopefully this post was of some help achieving that.
The post Integrating TypeScript with Svelte appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.