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 adjusted to my needs.


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

All my commands use pnpm, feel free to replace them with npm or yarn.


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

ESLint & prettier

pnpm i -D eslint eslint-config-prettier \
          prettier \

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

  "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:

  "trailingComma": "es5",
  "semi": false,
  "singleQuote": true

Let’s adjust package.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.

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.

  "*.{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.

We initialize it with a script:

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:

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

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


commitlint checks if your commit messages meet the conventional commit format.


chore: run tests on travis ci

I personally find it quite useful to enforce a uniform commit style.
commitlint pairs well with husky.

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

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

  "extends": ["@commitlint/config-conventional"]

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

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



pnpm add -D vitest vitest-dom happy-dom

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.

Configuring vitest with the .vite.config.ts:

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

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.

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

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


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