How to create a Vue.js 2 / Bootstrap 4 project with Vite
Tags: vue, vite, webdev, javascript
Published: 2021-08-25 20:19:59


[Vite](https://vitejs.dev/) is a lightning-fast dev environment and a pre-configured bundler into one. While it was first created to facilitate Vue.js 3 development, it can also be used for React, Svelte, Vanilla JS, and Vue.js 2.

There are plenty of tutorials for using Vite with Vue.js 3 and a lot of ready-made starter templates as well. We will be focusing on **Vue.js 2** and see how we can create a base for a new project that: 
- Uses Bootstrap 4.6 CSS for layout and styling
- Uses vue-router for client-side routing
- Supports global SCSS variables inside Vue components
- Is IE11 compatible
- Removes unused CSS rules from the production bundle

**Let's get right to it then!**

## Create a new Vite project
Although Vite contains many template presets out-of-the-box, it doesn't have one for Vue.js 2. We'll use the `vanilla` preset and then add the necessary plugins for Vue.js 2 development on top.

Navigate to your projects' directory and run:

> Make sure you replace `my-vue-app` with your project's name.

**npm 6.x**
```bash
npm init vite@latest my-vue-app --template vanilla
```

**npm 7+ (extra double-dash is needed)**
```bash
npm init vite@latest my-vue-app -- --template vanilla
```

### Install plugins needed for development

```bash
npm i -D vite-plugin-vue2 @vitejs/plugin-legacy vite-plugin-html vue-template-compiler sass@~1.32.13 postcss @fullhuman/postcss-purgecss autoprefixer
```

- **vite-plugin-vue2** - Vue.js 2 plugin for Vite
- **@vitejs/plugin-legacy** - Support for legacy browsers (IE11)
- **vite-plugin-html** - Minification and EJS template-based functionality for `index.html`
- **vue-template-compiler** - Pre-compiles Vue.js 2 templates into render functions
- **sass** - Pre-processor for SCSS, we need version 1.32 to avoid a [SASS deprecation warning](https://github.com/twbs/bootstrap/issues/34051) that affects Bootstrap 4
- **postcss** - Transforms styles with JS plugins 
- **@fullhuman/postcss-purgecss** - PostCSS plugin that removes unused selectors from our CSS
- **autoprefixer** - PostCSS plugin that adds vendor prefixes to CSS rules, also needed by Bootstrap

### Install Dependencies

Install Vue.js, Vue Router, and Bootstrap

```bash
npm i vue vue-router bootstrap@4.6.0
```

> We are using Bootstrap 4 since we want our project to be compatible with IE11

## Setup file structure

It's time to adjust our project's structure so that it resembles that of a `vue-cli` project.

Remove `style.css` and move `main.js` out of the root folder and inside the `src` folder. Then create the following file/folder structure. 

> Don't worry about the files' contents for now, as we are going to fill them up as we go, just create empty files making sure they are named correctly.

```
├── src
│   │
│   ├── components
│   │   └── <-- store your project's components here
│   │ 
│   ├── router
│   │   └── index.js
│   │ 
│   ├── scss
│   │   ├── app.scss
│   │ 	└── variables.scss
│   │ 
│   ├── views
│   │   ├── About.vue
│   │ 	└── Home.vue
│   │ 
│   ├── App.vue
│   └── main.js
│
├── favicon.svg
├── index.html
├── postcss.config.js
└── vite.config.js
```

## index.html, main.js, and App.vue

Vite uses `index.html` as the entry point of the application, We'll replace the contents of `index.html` with the markup below. Notice the EJS style variables `title` and `description` as we're going to set them in `vite.config.js` next.

We include `src/main.js` as the only `script` tag and Vite will resolve our JS source code.

`index.html`
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="<%- description %>">
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <title><%- title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%- title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
```

`src/main.js`
```js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

new Vue({
  router,
  render: (h) => h(App)
}).$mount('#app')
```

`src/App.vue`
```vue
<template>
  <div id="app">
    <ul class="nav nav-pills container pt-2">
      <li class="nav-item">
        <router-link to="/" exact exact-active-class="active" class="nav-link">
          Home
        </router-link>
      </li>
      <li class="nav-item">
        <router-link to="/about" class="nav-link">
          About
        </router-link>
      </li>
    </ul>
    <router-view />
  </div>
</template>

<script>
import '@/scss/app.scss'
</script>
```

## Vite config

The config file for Vite resides in the project's root. Here we're initializing the plugins for Vue.js 2 and IE11 compatibility as well as setting the title and description for our project.

We're also setting an alias of `@` for the `src` folder and injecting SCSS variables globally so that they are accessible from inside Vue components. 

`vite.config.js`
```js
import { minifyHtml, injectHtml } from 'vite-plugin-html'
import legacy from '@vitejs/plugin-legacy'
const path = require('path')
const { createVuePlugin } = require('vite-plugin-vue2')

module.exports = {
  plugins: [
    createVuePlugin(),
    minifyHtml(),
    injectHtml({
      injectData: {
        title: 'ProjectName',
        description: 'A single page application created using Vue.js'
      }
    }),
    legacy({
      targets: ['ie >= 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    })
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '/src'),
      '~bootstrap': 'bootstrap'
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/scss/variables";`
      }
    }
  }
}
```

## PurgeCSS

Bootstrap contains a lot of classes, but since we usually use a small set of the framework a lot of unused styles will be included in our CSS file. Let's configure [PurgeCSS](https://purgecss.com/) so that unnecessary styles will be stripped out of the final build.

`postcss.config.js`
```js
const IN_PRODUCTION = process.env.NODE_ENV === 'production'

module.exports = {
  plugins: [
    require('autoprefixer'),
    IN_PRODUCTION &&
      require('@fullhuman/postcss-purgecss')({
        content: ['./**/*.html', './src/**/*.vue'],
        defaultExtractor(content) {
          const contentWithoutStyleBlocks = content.replace(/<style[^]+?<\/style>/gi, '')
          return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
        },
        whitelist: [],
        whitelistPatterns: [
          /-(leave|enter|appear)(|-(to|from|active))$/,
          /^(?!(|.*?:)cursor-move).+-move$/,
          /^router-link(|-exact)-active$/,
          /data-v-.*/
        ]
      })
  ]
}
```

## Bootstrap SCSS

Our app's SCSS is located in `scss/app.scss` file. Here we include all Bootstrap SCSS except `functions`, `variables`, and `mixins`, as these will go inside `scss/variables.scss` so that we can use them inside our Vue.js components without explicitly importing them.

`scss/app.scss`
```scss
// Bootstrap source files (except functions, variables, mixins)
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/code";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/input-group";
@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/jumbotron";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/media";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/toasts";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/popover";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/spinners";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/print";

// Other application-wide SCSS rules here...
```
Our `variables.scss` contains overrides of Bootstrap SCSS variables as well as our own.

`scss/variables.scss`
```scss
$primary: #42b983;
$body-color: #304455;
$info:    #73abfe;
$gray-100: #f6f6f6;
$text-muted: #4e6e8e;
$gray-900: #273849;
$dark: #273849;

@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";

$navbar-dark-color: rgba($white, .7);
```

## Routing and Views

Finally, let's set up our routing and the contents of our two sample views `Home` and `About`.

`src/router/index.js`
```js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    component: () => import('@/views/About.vue')
  }
]

const router = new VueRouter({
  linkActiveClass: 'active',
  routes
})

export default router
```

`src/views/Home.vue`
```vue
<template>
  <div class="container">
    <div class="text-center">
      <h1>This is the home page</h1>
    </div>
  </div>
</template>
```

`src/views/About.vue`
```vue
<template>
  <div class="container">
    <div class="text-center">
      <h1>This is the about page</h1>
    </div>
  </div>
</template>
```

## Develop and Build

In order to start developing we run the command
```bash
npm run dev
```
This will start the dev server on http://localhost:3000

For bundling our app for production we use the following command
```bash
npm run build
```

This will create all the build assets inside the `dist` folder ready for us to deploy anywhere we like.

**Finally**
If you found this how-to useful, make sure you check out [vue-vite-starter-template](https://github.com/kouts/vue-vite-starter-template), which also includes ESLint, Prettier, semantic-release, Jest for testing, and more...

Thanks for reading!