Setting Up a Modern Preact Application With Typescript, Vite and Vitest
Tags: preact, typescript, vite, vitest
Published: 2022-10-16 11:55:29

---
title: Setting Up a Modern Preact Application With Typescript, Vite and Vitest
published: true
date: 2022-10-16 11:56:00 UTC
tags: Preact, TypeScript, Vite, Vitest
cover_image: https://images.unsplash.com/photo-1535369643553-a33e0d1ac81d?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=420&w=1000
canonical_url: https://www.rockyourcode.com/setting-up-a-modern-preact-application-with-typescript-vite-and-vitest/
---


> Wiring up a TypeScript environment with Preact, Vite and Vitest and vitest-dom

I have heard good things about Vite and Vitest. When I gave them a test-drive, I stumbled over some minor annoyances in getting the whole suite running.

I'm writing down the steps I took, maybe they help you.

The article is basically a re-write of [a blog post by Tomoki Miyaci][miyauci] adjusted to my needs.

Tooling:

- TypeScript
- Vite
- Vitest (with vitest-dom)
- Preact
- Prettier
- ESLint
- husky & lint-staged
- commitlint

All my commands use [pnpm](https://pnpm.io), feel free to replace them with npm or yarn.

## Vite

```sh
pnpm create vite <project-name> --template preact-ts
cd <project-name>
pnpm install
```

## ESLint & prettier

```sh
pnpm i -D eslint eslint-config-prettier \
          prettier \
          @typescript-eslint/parser
```

Create a new file called `.eslintrc` with the following content:

```json
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": ["eslint:recommended", "preact", "prettier"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "settings": {
    "jest": {
      "version": 27
    }
  },
  "ignorePatterns": ["*.d.ts"],
  "rules": {}
}
```

One wrinkle was the Jest settings option. I know I wanted to use Vite, but eslint needs to know the Jest version for some of its tests.

Here is my Prettier configuration (`.prettierrc`), adjust to your needs:

```json
{
  "trailingComma": "es5",
  "semi": false,
  "singleQuote": true
}
```

Let's adjust `package.json`:

```json
{
  "scripts": {
    "lint:fix": "eslint --fix --ext .ts,tsx --ignore-path .gitignore .",
    "prettier:write": "prettier -u -w --ignore-path .gitignore \"*.{ts,tsx,css,html}\"",
}
```

## husky & lint-staged

Install [lint-staged](https://www.npmjs.com/package/lint-staged).

```sh
pnpm add -D lint-staged
```
Create a `.lintstagedrc.json` file in your project root folder.  
Here you can add the commands that lint-staged should run on staging your files.

```json
{
    "*.{ts,tsx}": ["pnpm run lint:fix", "pnpm run prettier:write"],
    "*.{html,css,js,json,md}": "pnpm run prettier:write"
}
```

We run ESLint and prettier on TypeScript files in sequential order. For other files, prettier suffices.

Now we need [husky](https://typicode.github.io/husky/#/).

We initialize it with a script:

```sh
pnpm dlx husky-init && pnpm install
```

The above command will setup the tool and create the necessary files and hooks.

The default hook runs before committing the files to the staging area.  
You can find it under `.husky/pre-commit`:

```sh
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm exec lint-staged
## if you want to run your tests before commiting,
## uncomment next line
# pnpm exec vitest run
```

## commitlint

> commitlint checks if your commit messages meet the [conventional commit](https://conventionalcommits.org/) format.

Example:

> chore: run tests on travis ci

I personally find it quite useful to enforce a uniform commit style.  
[commitlint](https://github.com/conventional-changelog/commitlint/) pairs well with husky.

```sh
pnpm add -D @commitlint/{config-conventional,cli}
```

Let's add a configuration file (`.commitlintrc.json`):

```sh
{
  "extends": ["@commitlint/config-conventional"]
}
```

Now we need a hook for husky. Run the following command in your terminal:

```sh
pnpm dlx husky add \
  .husky/commit-msg 'pnpm exec commitlint --edit'
```

## Vitest

Installation:

```sh
pnpm add -D vitest vitest-dom happy-dom
```

[vitest-dom](https://github.com/chaance/vitest-dom) extends the standard Jest matchers with convenient methods like `.toBeDisabled`.  
Now you can write tests that assert on the state of the DOM.  
The package is a fork of [@testing-library/jest-dom](https://github.com/testing-library/jest-dom).

Configuring vitest with the `.vite.config.ts`:

```ts
/// <reference types="vitest" />
import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'

// https://vitejs.dev/config/
export default defineConfig({
  define: {
    'import.meta.vitest': 'undefined',
  },
  plugins: [preact()],
  test: {
    environment: 'happy-dom',
    setupFiles: ['./__test__/test-setup.ts'],
    includeSource: ['src/**/*.{ts,tsx}'],
    coverage: {
      reporter: ['text-summary', 'text'],
    },
    mockReset: true,
    restoreMocks: true,
  },
})
```

The code section `import.meta.vitest` allows you to [run tests within your source code](https://vitest.dev/guide/in-source.html).

For my test setup I've made a separate `__test__` folder with a file called `test-setup.ts`:

```ts
import 'vitest-dom/extend-expect'
import * as domMatchers from 'vitest-dom/matchers'
import { expect } from 'vitest'

expect.extend(domMatchers)
```

Here I add the `vitest-dom` extra matchers. You can add more setup logic if needed.

## Sources

- [Building a Typescript Environment for Preact with Vite][miyauci]
- [Diving Into Husky and lint-staged][laurie]

[laurie]: https://laurieontech.com/posts/husky/
[miyauci]: https://miyauchi.dev/posts/vite-preact-typescript/