im

Eslint, Svelte and TypeScript

Linting is useful, but can be overwhelming to set up. Here is how I did it.

You never realize what value linting brings until you start using it. I was that developer once. Ignorant. Why use linter when you have svelte⁠-⁠check, right? Wrong! The TypeScript compiler won't either help you catch all errors. It will not whine about you naming your variables incorrectly. Nor will svelte-check, but a good linter will annoy the hell out of you until you learn to do things right.

A short while ago Svelte's Eslint plugin didn't support linting TypeScript in templates. That was a show stopper for many. Now that it's in place there is no excuse not to use it.

In this post I will guide you to the right tools and libraries for building a proper linting pipeline for your Svelte Typescript-enabled project.

Project Setup#

First, we need a Svelte project to test this on. Let's use the official Webpack template for a change. The template comes with TypeScript support that we need to enable explicitly.

$ npx degit sveltejs/template-webpack svelte-eslint
$ cd svelte-eslint
$ node scripts/setupTypeScript.js
$ npm install
$ npm run dev
$ open localhost:8080

If you followed the instructions above you should see a simple page with "HELLO WORLD!" displayed on it.

Setup Prettier#

If you use Svelte's official VSCode extension you get auto formatting for free, but I use Vim, so I need to setup Prettier explicitly.

$ npm add -D prettier prettier-plugin-svelte

Even if you use VSCode extension it's a good practice to create a Prettier configuration for every project. We also need it to format the code when linting as you will learn later.

// .prettierrc.js

module.exports = {
arrowParens: 'avoid',
singleQuote: true,
printWidth: 90,
plugins: ['prettier-plugin-svelte'],
semi: false,
svelteSortOrder: 'options-styles-scripts-markup',
svelteStrictMode: false,
svelteBracketNewLine: true,
svelteIndentScriptAndStyle: true,
trailingComma: 'none'
}

Above is my Prettier configuration, but you can adjust it to fit your needs.

Strict mode on#

When starting a new TypeScript project it's a good idea to turn the strict mode on directly. For this we need to change the tsconfig.json to the following.

// tsconfig.json

{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"lib": ["DOM", "ES2017", "WebWorker"],
"resolveJsonModule": true,
"esModuleInterop": true,
"importsNotUsedAsValues": "remove",
"strict": true
},
"include": ["src/**/*", "src/node_modules/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "static/*"]
}

Again, TypeScript configuration varies greatly. Adjust if needed.

Linting and formatting are two different concepts. Try not to mix them. I know that you can integrate Prettier with Eslint, but don't do it. It's always better to format files separately, before you lint them.

Setting up Eslint#

The time has come to setup Eslint with TypeScript support. Let's install the required dependencies first.

$ npm add -D eslint eslint-plugin-svelte3
$ npm add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

Now we need to add ESlint configuration file. If you are using Tailwind CSS you have to disable Eslint from linting the <style> tags in Svelte templates. You can do it in the settings section of Eslint config by setting svelte2/ignore⁠-⁠styles setting to an function that returns true. I know, weird, but it works.

// .eslintrc.js

module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte']
},
env: {
es6: true,
browser: true
},
overrides: [
{
files: ['*.svelte'],
processor: 'svelte3/svelte3'
}
],
settings: {
'svelte3/typescript': require('typescript'),
// ignore style tags in Svelte because of Tailwind CSS
// See https://github.com/sveltejs/eslint-plugin-svelte3/issues/70
'svelte3/ignore-styles': () => true
},
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['node_modules']
}

If you have done everything correctly, and your editor has linting support enabled, it should just work. If you are not sure, check the guidelines on how to integrate Eslint in your editor.

Next thing we need to do is to add an error to the app to see if the ESlint catches it. Replace the script section in App.svelte with the code below.

<!-- App.svelte -->

<script lang="ts">
import
{ writable } from 'svelte/store'

const store = writable([])
$store.length // incorrect no-unsafe-member-access error

export let name: string
</script>

Alright! Time to test-drive the beast. Add the following commands to the scripts section in your package.json.

"format": "prettier --write ./src/**/*.{js,svelte,html,ts}",
"lint": "eslint './src/**/*.{js,ts,svelte}'",
"lint:fix": "eslint --fix './src/**/*.{js,ts,svelte}'",
"prelint": "npm run format"

The prelint command will make the format command run before doing the actual linting, "format, then lint" style. It will be invoked automatically if you run npm run lint or npm run lint:fix. This is neat. One less command to keep in mind.

Try to lint the project and see Eslint catch the $store.length error above. This error would have slipped though the TypeScript compiler, try running npm build, but now Eslint caught it for us.

BONUS: Lint before you commit#

Humans are not machines, not yet at least. It's easy to forget things, because it's ... human. Therefore, we need to automate things as much as possible so nothing can slip through the cracks. Husky and lint-staged are two tools that can help us achieve this.

Husky is a CLI tool that helps working with Git hooks easier. It will install Git hooks that we can in turn hook up different commands to. We can for example use the pre⁠-⁠commit Git hook to trigger a command and depending on the outcome either allow or disallow a commit.

Another tool, lint⁠-⁠staged, helps us narrow it down only to the files that have changed. This might seem unnecessary, but as your project grows in size I guarantee that you will appreciate it. Let's install and configure them.

$ npm add -D husky lint-staged
$ npx husky init

Husky should now have installed a pre⁠-⁠commit hook in the .husky directory. Open that file in your editor and add the npx lint⁠-⁠staged command to the bottom.

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# run lint-staged command
npx lint-staged

To explain, every time you will try to commit your changes in Git Husky will call lint⁠-⁠staged command and that command can in turn call other commands on only the files that are affected by the commit.

You can configure lint⁠-⁠staged in many different ways, but for our case adding a new lint⁠-⁠staged option in the package.json file should do.

"lint-staged": {
"*.{js,ts,svelte}": ["svelte-check", "npm run lint:fix"]
}

This configuration will run the two commands above on any files matching the pattern when you try to do a Git commit. If the checks fail nothing will be committed.

One thing I recommended doing is to add the linting to your build command as the first step. If something managed to slip through the cracks it will be caught there. You probably don't build production builds often, maybe you even have a CI pipeline setup for this, so it will not affect the DX and slow things down for you.

Libraries Mentioned#

Conclusion#

You can write JavaScript in many different ways. Linting helps you stick to the defined rules and keep your code consistent. TypeScript compiler can help you catch some errors, but not all. A good linter will guide you to write better code and become a disciplined developer.

Solid linting pipeline can be daunting to setup. Honestly, I've been skeptical of linting and haven't used it much myself in the past, but after working on a bigger project with many developers involved I realized why people use linters and why they are extra useful in a JavaScript project.

I cannot guarantee that my setup will work for you, things move fast in the frontend world, but at least I hope that you got some inspiration out of it.