lobo_tuerto's notes
for greatly improving DX
Blog
Notes
About

Build a Vue 3 + TypeScript dev environment with Vite

Production grade DX for all your web projects.

📅Date22 August 2021Updated28 August 2021
🏷️
frontendtailwindcsstypescriptvitevue 3

Table of contents

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

And more…

Let’s get started

Generate a new project:

yarn create vite

# introduce project name
# select vue
# select vue-ts

yarn configuration

Let’s setup our project with Yarn.
Enter the project directory then:

yarn install

Project configuration

common node types

To be able to use __dirname or import path from 'path':

yarn add --dev @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

To use imports like import HelloWorld from '@/components/HelloWorld.vue' —notice the @— we need to edit vite.config.ts and add a new resolve key to it, like this:

import vue from '@vitejs/plugin-vue'
import path from 'path'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '/src'),
    },
  },
})

For TypeScript to resolve this alias, and also allow us to autocomplete paths, let’s add paths and baseUrl in tsconfig.json under compilerOptions:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    // ...
    "skipLibCheck": true,
    //...

Let’s throw in there "skipLibCheck": true too.
Here is some context about why it might be useful for us.

Tools to elevate your DX

These dependencies aims to set up an advanced DX for web development.

prettier

Add prettier to the project:

yarn add --dev prettier

Create a .prettierrc:

{
  "semi": false,
  "singleQuote": true
}

Format project:

yarn prettier . --write

eslint

Let’s configure eslint + prettier + typescript support.
Add eslint and related dependencies to the project:

yarn add --dev eslint eslint-plugin-vue eslint-config-prettier
yarn add --dev @typescript-eslint/parser

Create a .eslintrc.js file:

module.exports = {
  env: { node: true },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'prettier',
  ],
  globals: {
    defineEmits: 'readonly',
    defineProps: 'readonly',
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    // sourceType: 'module',
  },
  rules: {},
}

Add the following scripts to your package.json file:

{
  // ...
  "scripts": {
    // ...
    "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src/",
    "format": "prettier . --write"
    // ...

Read more about eslint-plugin-vue over here.

stylelint

Add stylelint to the project:

yarn add --dev stylelint stylelint-config-standard stylelint-config-prettier

Create a .stylelintrc.json:

{
  "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
}

commitlint

Add commitlint to the project:

yarn add --dev @commitlint/cli @commitlint/config-conventional

Create a commitlint.config.js:

module.exports = {
  extends: ['@commitlint/config-conventional'],
}

Read more about commitlint over here.

husky

Add husky to the project:

yarn add --dev husky
yarn husky install
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
yarn husky add .husky/pre-commit 'yarn lint'

Whenever we clone a repository that contains husky hooks we need to run yarn husky install for git to pick 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:

yarn add vue-router@next

Routing example:

routes.ts:

export default [
  {
    component: () => import('./pages/HomePage.vue'),
    name: 'home',
    path: '/',
  },
  {
    component: () => import('./pages/AboutPage.vue'),
    name: 'about',
    path: '/about',
  },
]

main.ts:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

import App from './App.vue'
import routes from './routes'

const router = createRouter({
  history: createWebHistory(),
  routes,
})

createApp(App).use(router).mount('#app')

App.vue:

<template>
  <h1>The APP template</h1>
  <RouterLink to="/">Home</RouterLink>
  |
  <RouterLink to="/about">About</RouterLink>
  <RouterView></RouterView>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'

export default defineComponent({
  name: 'App',
  components: {
    HelloWorld,
  },
})
</script>

vuex

Add vuex to the project:

yarn add vuex@next

Add a src/store.ts file:

import { createStore } from 'vuex'

export interface State {
  count: number
}

export const store = createStore<State>({
  state() {
    return {
      count: 1,
    }
  },

  mutations: {
    add(state, amount) {
      state.count += amount
    },
  },
})

Then in you src/main.ts:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

import App from './App.vue'
import routes from './routes'
import { store } from './store'

const router = createRouter({
  history: createWebHistory(),
  routes,
})

createApp(App).use(router).use(store).mount('#app')

Use the store in a component, for example in src/pages/Home.vue:

<template>
  <h1>The HOME template -{{ count }}-</h1>
  <img alt="Vue logo" src="@/assets/logo.png" @click="addTwo" />
  <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue'

import HelloWorld from '@/components/HelloWorld.vue'
import { store } from '@/store'

export default defineComponent({
  name: 'Home',
  components: {
    HelloWorld,
  },
  setup() {
    const addTwo = () => store.commit('add', 2)
    const count = computed(() => store.state.count)

    return {
      addTwo,
      count,
    }
  },
})
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Further reading

Vuex + TypeScript:

HEAD management

Let’s add @vueuse/head to the project to handle SEO for our app:

Add vuex to the project:

yarn add @vueuse/head

In your src/main.ts:

import { createHead } from '@vueuse/head'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

import App from './App.vue'
import routes from './routes'
import { store } from './store'

const router = createRouter({
  history: createWebHistory(),
  routes,
})

const head = createHead()

createApp(App).use(head).use(router).use(store).mount('#app')

Use it in your src/App.vue or page components like this:

<template>
  <h1>The APP template</h1>
  <RouterLink to="/">Home</RouterLink>
  |
  <RouterLink to="/about">About</RouterLink>
  <RouterView></RouterView>
</template>

<script lang="ts">
import { useHead } from '@vueuse/head'
import { defineComponent } from 'vue'

import HelloWorld from '@/components/HelloWorld.vue'

export default defineComponent({
  name: 'App',

  components: {
    HelloWorld,
  },

  setup() {
    useHead({
      title: 'Default title',
      meta: [
        {
          name: 'description',
          content: 'This is a DEFAULT description',
        },
        {
          name: 'other-stuff',
          content: 'This is some OTHER stuff',
        },
      ],
    })
  },
})
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Extras

Setup TailwindCSS

Add TailwindCSS dependencies to the project, then generate some config files:

yarn add --dev tailwindcss@latest postcss@latest autoprefixer@latest
yarn tailwindcss init -p

Add some basic configuration to tailwind.config.js:

module.exports = {
  purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

And some to .stylelintrc.json:

{
  //...
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": ["tailwind"]
      }
    ]
  }
}

Add a src/index.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

And import it in your src/main.ts file:

import './index.css'

You can use TailwindCSS classes in your app now.

Reference


Got comments or feedback?
Follow me on
rev-7175614