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.
Tooling:
- 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.
Vite
pnpm create vite <project-name> --template preact-ts
cd <project-name>
pnpm install
ESLint & prettier
pnpm i -D eslint eslint-config-prettier \
prettier \
@typescript-eslint/parser
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")/_/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 format.
Example:
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'
Vitest
Installation:
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'
// 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.
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'
expect.extend(domMatchers)
Here I add the vitest-dom
extra matchers. You can add more setup logic if needed.