Add lint-staged To SvelteKit Project
Published: 2022-04-17 07:46:32

In my previous post: [Add Commitint, Commitizen, Standard Version, and Husky to SvelteKit Project](https://dev.to/davipon/add-commitint-commitizen-standard-version-and-husky-to-sveltekit-project-14pc), I'd introduced how to add **Conventional Commits** and **SemVer** to your SvelteKit Project.

We added a `pre-commit` hook to format and lint our code before committing:

```zsh
# Add pre-commit hook
npx husky add .husky/pre-commit 'pnpm format && pnpm lint && git add .'
```

While there is a problem that we format and lint our entire codebase every time, it's not ideal if we make a small commit.

Can we ONLY format and lint files we've changed? 🙋

The answer is "Yes", and this is where `lint-staged` comes into play.

##  What is 🚫💩 [lint-staged](https://github.com/okonet/lint-staged)?

>Run linters against staged git files, and don't let 💩 slip into your codebase!

As the name suggests, `lint-staged` enables us to **lint** staged files. But we can leverage it to do more like formatting (`prettier`), type checking (`tsc`), ... etc.

Let's see how to add `lint-staged` and improve the workflow.

### Install

>Please refer to my [previous post](https://dev.to/davipon/add-commitint-commitizen-standard-version-and-husky-to-sveltekit-project-14pc) before we start or directly check the final [repo](https://github.com/davipon/conventional-commits-sveltekit). 

```zsh
# Install lint-staged
pnpm add -D lint-staged
```
### Setup

Create a new file called `.lintstagedrc` under the project root folder:

```JSON
{
  "*.{js,ts,svelte,css,scss,postcss,md,json}": [
    "prettier --write --plugin-search-dir=.",
    "prettier --check --plugin-search-dir=."
  ],
    "*.{js,ts,svelte}": "eslint"
}
```

Update `.husky/pre-commit`:

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

# npm
# npx lint-staged

# pnpm
pnpm exec lint-staged
```


You might be curious "Why not just use `pnpm format`, `pnpm lint` directly?". To explain that, let's take a look at our `script` tag in `package.json`:

```JSON
// package.json
// Default SvelteKit skeleton project with TypeScript, ESLint, Prettier
{
  ...
  "script": {
    ...
    "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
    "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .",
  }
}
```

Put simply, these two scripts would:
1. Ignore files listed in `.gitignore`
2. Run `eslint` & `prettier` against the whole project because we had `.` at the end of the each script

Remember we ONLY want to **lint** and **format** files that changed? 

We need to customize the script so `lint-staged` can perform correctly.

### Anatomy of the `.lintstagedrc`

```JSON
{
  "*.{js,ts,svelte,css,scss,postcss,md,json}": [ 
    "prettier --write --plugin-search-dir=.",
    "prettier --check --plugin-search-dir=."
  ],
    "*.{js,ts,svelte}": "eslint"
}
```

1. [Filtering Files](https://github.com/okonet/lint-staged#filtering-files): Which files do you want to lint or format? Ex: `*.{js,ts,svelte}`
2. [Ignoring Files](https://github.com/okonet/lint-staged#ignoring-files): Project uses `.gitignore` as its ignore path for each command. Since `lint-staged` only runs against staged files, we don't need to ignore files that have been already ignored by `.gitignore`. 
3. [Running multiple commands in a sequence](https://github.com/okonet/lint-staged#running-multiple-commands-in-a-sequence): We can run multiple commands in a sequence, just put them in an array.
4. [`--plugin-search-dir=.`](https://prettier.io/docs/en/plugins.html#using-plugins): Tell `prettier` to use plugins. We have `prettier-plugin-svelte` installed in the SvelteKit project.
5. `prettier --write` & `prettier --check` runs sequentially; `prettier` & `eslint` runs against filtering files concurrently.

> You can remove `prettier --check`, use `eslint --fix`, or customize it depending on your need.

### What about `svelte-check`?

In my previous post, I put `svelte-check` in my `pre-push` but not `pre-commit` hook.
The reason is that `svelte-check` needs to know the whole project to do valid checks.
Refs:
1. [Why is there no option to only check specific files (for example only staged files)?](https://github.com/sveltejs/language-tools/tree/master/packages/svelte-check#why-is-there-no-option-to-only-check-specific-files-for-example-only-staged-files)
2. [svelte-check: check only files mentioned as arguments #353](https://github.com/sveltejs/language-tools/issues/353)

## Wrapping Up

I have been experimenting with different configurations and toolings to optimize our workflow in the last few days.

**Conventional Commits** & **SemVer** help us forge a better code review process, but we also want to ensure the workflow is robust enough when the project scales.

Using `lint-staged` in `pre-commit` is one of the improvements to reduce the time and improve our DX.

That's all. Thank you for your reading! 🙌

Please leave a comment if you have any questions or share your experience. Love to connect with you and learn from you.

You can find me on Twitter: [@davipon](https://twitter.com/davipon)