We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
2021 / 08 / 22
2023 / 09 / 11
Build a Vue 3 + TypeScript dev environment with Vite
Production grade DX for all your web projects.
Introduction
This is an opinionated guide on how to set up a new Vue 3 project with the aforementioned tech stack plus some extras that can really help in the DX (Developer Experience) department.
Stuff like:
- Prettier
- Husky
- ESLint + styleLint + commitlint
- Maybe Tailwind CSS ;)
And more…
Here are a couple of cheat sheets I wrote for Vue 3 + TypeScript:
Prerequisites
- Install Node.js
Let’s get started
Generate a new project:
pnpm create vite
Then input:
- Project name
- Pick Vue
- Pick TypeScript
Let’s set up our project with pnpm.
Enter the project directory, then:
pnpm install
pnpm up --latest
If you see an error inside VSCode that reads:
Cannot find module './App.vue' or its corresponding type declarations
You’ll need to install the Volar VSCode extension, then you’ll need to enable Takeover Mode to get proper TS support for Vue components:
Basic project configuration
Create a prettier.config.cjs
file and put this content inside:
/** @type {import("prettier").Config} */
module.exports = {
semi: false,
singleQuote: true,
}
This is in case you have the VSCode extension installed, and you want to format code right away. ;)
We will finish setting it up later.
common node types
To be able to use __dirname
or import * as path from 'path'
:
pnpm add -D @types/node
Typed ENV vars
To type your environment variables in Vite, add a new
src/env.d.ts
file with typing information like:
interface ImportMetaEnv {
VITE_BASE_URL: string
}
This indicates that we will have a VITE_BASE_URL
env var
that will contain a string.
alias @ to src
Use @ in imports like import HelloWorld from '@/components/HelloWorld.vue'
.
Edit vite.config.ts
and add a new resolve
key to it, like this:
import vue from '@vitejs/plugin-vue'
import * as path from 'path'
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
Let’s allow TypeScript to resolve this alias, and also enable autocomplete on paths.
In tsconfig.json
add paths
and baseUrl
under compilerOptions
:
{
"compilerOptions": {
// ...
/* Alias resolving */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
As to why "skipLibCheck": true
, here is
some context
about how it might be useful.
Improve your DX
DX means Developer Experience
— lt
These dependencies are optional, and if you are just starting might want to skip them altogether.
Their aim is to set up an advanced DX for web development, and are essential when working in the context of a team.
prettier + Tailwind CSS plugin
Add Prettier to the project:
pnpm add -D prettier prettier-plugin-tailwindcss
Create a prettier.config.cjs
file:
/** @type {import("prettier").Config} */
module.exports = {
plugins: ['prettier-plugin-tailwindcss'],
semi: false,
singleQuote: true,
// trailingComma: "none" || "es5"
// tailwindConfig: './tailwind.config.js'
}
Create a .prettierignore
file:
coverage/
dist/
__snapshots__/
pnpm-lock.yaml
*.md
Format project:
pnpm prettier . --write
Add the following scripts to your package.json
file:
{
// ...
"scripts": {
// ...
"format": "prettier . --write",
"format:check": "prettier . --check",
// ...
Read more about Prettier over here.
And read more about the Tailwind CSS plugin over here.
ESLint
Let’s configure eslint + prettier + typescript support.
Add eslint and related dependencies to the project:
pnpm add -D eslint eslint-plugin-vue eslint-config-prettier
pnpm add -D vue-eslint-parser @typescript-eslint/parser
pnpm add -D @typescript-eslint/eslint-plugin
Create a .eslintrc.cjs
file:
module.exports = {
env: { node: true },
extends: [
'eslint:recommended', // this maybe causes errors in defineEmits<{}>() ???
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'prettier',
],
/* globals: {
defineEmits: 'readonly',
defineProps: 'readonly',
}, */
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
// plugins: ['@typescript-eslint'], // might not be needed
/* rules: {
'@typescript-eslint/explicit-function-return-type': 'warn',
}, */
}
Add the following scripts to your package.json
file:
{
// ...
"scripts": {
// ...
"js:lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src/",
"js:lint:check": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src/",
// ...
Read more about eslint-plugin-vue over here.
Stylelint
Add Stylelint to the project:
pnpm add -D stylelint stylelint-config-standard stylelint-config-prettier stylelint-config-recommended-vue postcss-html
Create a stylelint.config.cjs
:
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-recommended-vue',
'stylelint-config-prettier'
],
rules: {
'at-rule-no-unknown': [true, { ignoreAtRules: ['tailwind'] }],
'declaration-empty-line-before': null,
'function-no-unknown': [true, { ignoreFunctions: ['theme'] }],
'custom-property-empty-line-before': null,
'selector-class-pattern': null,
'value-keyword-case': null,
}
}
Read more about Stylelint over here.
Add the following scripts to your package.json
file:
{
// ...
"scripts": {
// ...
"css:lint": "stylelint ./src/**/*.{css,vue} --fix",
"css:lint:check": "stylelint ./src/**/*.{css,vue}",
// ...
commitlint
Add commitlint to the project:
pnpm add -D @commitlint/cli @commitlint/config-conventional
Create a commitlint.config.cjs
:
module.exports = {
extends: ['@commitlint/config-conventional'],
}
Read more about commitlint over here.
husky
Add husky to the project:
pnpm add -D husky
pnpm husky install
npm pkg set scripts.prepare="husky install"
pnpm husky add .husky/commit-msg 'pnpm commitlint --edit $1'
pnpm husky add .husky/pre-commit 'pnpm format:check'
pnpm husky add .husky/pre-commit 'pnpm js:lint:check'
pnpm husky add .husky/pre-commit 'pnpm css:lint:check'
Whenever we clone a repository that contains husky
git hooks we need to run pnpm husky install
—or, in
this case pnpm prepare
— for husky to set them up.
Common project dependencies
These are some really common dependencies that you’ll see basically in every project.
vue-router
Add vue-router to the project:
pnpm add vue-router
Set up router with example:
Add a src/pages/HomePage.vue
file:
<template>Home page</template>
Add a src/router.ts
file:
import { createRouter, createWebHistory } from 'vue-router'
import HomePage from '@/pages/HomePage.vue'
export const routes = [
{
path: '/',
name: 'home-page',
component: HomePage,
},
// For lazy loading components
/* {
path: '/',
name: 'home-page',
component: () => import('@/pages/HomePage.vue'),
}, */
]
export const router = createRouter({
history: createWebHistory(),
routes,
})
Adjust your src/main.ts
file:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).mount('#app')
Add a router outlet in you src/App.vue
file:
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
<!-- ROUTER OUTLET -->
<RouterView />
</template>
Adding a not found page to our router
Now let’s add a catch all route that’ll render a special 404 page.
Create a new NotFoundPage.vue
, e.g.:
<template>404 - Not found</template>
Then add a new entry to your routes
in the src/router.ts
file:
import NotFoundPage from '@/pages/NotFoundPage.vue'
export const routes = [
// ...
{
component: NotFoundPage,
name: 'not-found-page',
path: '/:pathMatch(.*)*',
},
]
Type router meta variables
Configure your router variables in src/vue-router.d.ts
:
export {}
declare module 'vue-router' {
interface RouteMeta {
authRequired?: boolean
unauthRequired?: boolean
}
}
Read more about it:
Other recommended packages/libraries
date-fns
A modern JavaScript date utility library.
Makes it easy to work with timezones.
pnpm add date-fns date-fns-tz
VueUse
The swiss army knife for Vue 3.
A collection of essential Vue composition utilities.
pnpm add @vueuse/core
Lodash
The swiss army knife for JavaScript/TypeScript.
A modern JavaScript utility library delivering modularity, performance & extras.
pnpm add lodash-es
pnpm add -D @types/lodash-es
Vuelidate
A simple, but powerful, lightweight model-based validation for Vue.
pnpm add @vuelidate/core @vuelidate/validators
Axios
Promise based HTTP client for the browser and node.js
pnpm add axios
Big.js
A small, fast JavaScript library for arbitrary-precision decimal arithmetic.
pnpm add big.js
pnpm add -D @types/big.js
Histoire
Write stories to showcase and document your components.
pnpm i -D histoire @histoire/plugin-vue
Add a histoire.config.ts
file:
import { defineConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'
export default defineConfig({
plugins: [HstVue()],
setupFile: 'src/histoire.setup.ts',
// If you intend to serve from a subdirectory...
vite: {
base: '/ui-library/',
},
})
Create a src/histoire.setup.ts
:
import { createPinia } from 'pinia'
import { defineSetupVue3 } from '@histoire/plugin-vue'
import './style.css'
export const setupVue3 = defineSetupVue3(({ app, story, variant }) => {
const pinia = createPinia()
app.use(pinia)
})
Then add these scripts to your package.json
:
{
"scripts": {
"story:dev": "histoire dev",
"story:build": "histoire build",
"story:preview": "histoire preview"
}
}
Create an env.d.ts
:
/// <reference types="@histoire/plugin-vue/components" />
And add it in the include
field of your tsconfig.json
:
{
"include": [
"env.d.ts",
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}
Let’s write an example story file for the src/components/HelloWorld.vue
component.
src/components/HelloWorld.story.vue
:
<script setup lang="ts">
import HelloWorld from './HelloWorld.vue'
</script>
<template>
<Story>
<HelloWorld msg="Hello!" />
</Story>
</template>
To see the results:
pnpm story:dev
# Now visit http://localhost:6006
:tada: