One way to bootstrap Vue3 project

EJEJ
5 min read

npm create vite@latest

$ npm create vite@latest
...

○ → npm create vite@latest

> npx
> create-vite

│
◇  Project name:
│  vite-project
│
◇  Select a framework:
│  Vue
│
◇  Select a variant:
│  TypeScript
│
◇  Scaffolding project in /Users/.../workspace/kerja/vite-project...
│
└  Done. Now run:

  cd vite-project
  npm install
  npm run dev

The scaffolded project is very bare bone. It is missing a few crucial components which we will install later in this page:

  • Code error finding formatting. We will use ESLint.

  • Unit testing library. We will use Vitest.

  • UI framework. We will use Vuetify.

  • State management. We will use Pinia

Install and setup ESLint

Add the following libraries

$ npm i -D eslint

$ npm i -D eslint-config-vuetify

Because we will use Vuetify as well, we use and configure eslint-config-vuetify. Which is an opinionated configuration that will find error and provide formatting as well.

And add the config file eslint.config.js to the root of the project with the following content:

import vuetify from 'eslint-config-vuetify'

export default vuetify()

Then enable the Automatic ESLint configuration in WebStorm

Install vitest for unit test

First, create a new empty component in the src/components directory

Once created, we can use TDD to implement the component.

Install the following libraries:

$ npm i -D vitest

$ npm i -D @vue/test-utils

vitest will give you the infrastructure to run the test just like Jest

@vue/test-utils will give you mount() function among other things

Then add a test in tests/components called Rando.spec.ts

import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import Rando from '../../src/components/Rando.vue'

describe('Rando tests', () => {
  it('should render component', async () => {
    // action
    const wrapper = mount(Rando, {
      props: {
        msg: 'Message from Rando',
      },
    })

    // assert
    expect(wrapper.text()).toContain('Message from Rando')
  })
})

When you run the test, it will error

ReferenceError: document is not defined

This can be fixed by installing and configuring jsdom library:

$ npm i -D jsdom

Then add the following in the vite.config.ts file:

export default defineConfig({
  ...
  test: {
    environment: 'jsdom',
  },
})

To fix the “no overload matches this call” error (actually just a warning):

// replace
import { defineConfig } from 'vite'

// with
import { defineConfig } from 'vitest/config'

Run the test again, and the error will allow you to do the implementation:

AssertionError: expected '' to contain 'Message from Rando'
Expected :Message from Rando
Actual   :

Implementation for Rando.vue to “fix” the test:

<script setup lang="ts">
  defineProps<{ msg: string }>()
</script>

<template>
  <p>{{ msg }}</p>
</template>

The test should now pass

Install and configure Vuetify

Follow the documentation: https://vuetifyjs.com/en/getting-started/installation/#existing-projects

$ npm i vuetify

And update the main.ts

Before:

import { createApp } from 'vue'
import App from './App.vue'
import './style.css'

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

After:

import { createApp } from 'vue'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import App from './App.vue'
import './style.css'

const vuetify = createVuetify({
  components,
  directives,
})

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

To make sure main.ts is clean, you can also do the following:

...
import { vuetify } from './plugins/vuetify.ts'
...
createApp(App).use(vuetify).mount('#app')

And create new file /src/plugins/vuetify.ts that contain all the definitions:

import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { mdi } from 'vuetify/iconsets/mdi-svg'
import 'vuetify/styles/main.css'

export const vuetify = createVuetify({
  components,
  directives,
  icons: {
    defaultSet: 'mdi',
    sets: { mdi },
  },
})

Add a similar test and component like Rando as above, but instead of <p>, use vuetify component such as <v-card>

When you run the test, will get following warning:

[Vue warn]: Failed to resolve component: v-card-title
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
  at <Morning msg="Good Morning" ref="VTU_COMPONENT" > 
  at <VTUROOT>

To fix this, update the mount to pass the vuetify:

...
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
...

const wrapper = mount(Morning, {
  props: {
    msg: 'Good Morning',
  },
  global: {
    plugins: [createVuetify({ components, directives })],
  },
})

Now when you re-run the test, you will get the following error:

TypeError: Unknown file extension ".css" for /Users/erfanjap/workspace/kerja/vite-2/node_modules/vuetify/lib/components/VCode/VCode.css
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:219:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:245:36)
    at defaultLoad (node:internal/modules/esm/load:120:22)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:580:32)

To fix this, update vite.config.ts with the following:

export default defineConfig({
  ...
  test: {
    ...
    server: {
      deps: {
        inline: ['vuetify'],
      },
    },
  },
})

Run the test again, and now it should be OK

Extra:

...
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

// add these imports to import vuetify icons and css/styles 
import { mdi } from 'vuetify/iconsets/mdi-svg'
import 'vuetify/styles/main.css'
...

export const vuetify = createVuetify({
  components,
  directives,
  icons: {
    defaultSet: 'mdi',
    sets: { mdi },
  },
})

Install and setup Pinia

First, install Pinia

$ npm i pinia

Then set it up in main.ts

import { createPinia } from 'pinia'
...
const pinia = createPinia()
...
createApp(App)
  ...
  .use(pinia)
  .mount('#app')

Then define a store in a directory src/stores

// src/stores/app.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useAppStore = defineStore('app', () => {
  const someName = ref('John Doe')

  return { someName }
})

To use the app store, in the component:

<script setup lang="ts">
  import { useAppStore } from '../stores/app'

  const store = useAppStore()
</script>

<template>
  <p>{{ store.someName }}</p>
</template>

That’s All :-)

0
Subscribe to my newsletter

Read articles from EJ directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

EJ
EJ