A Single Page App Vite Starter Template Created To Easily Bootstrap

Vite 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

npm init vite@latest my-vue-app --template vanilla

npm 7+ (extra double-dash is needed)

npm init vite@latest my-vue-app -- --template vanilla

Install plugins needed for development

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 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

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

<!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

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

<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

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 so that unnecessary styles will be stripped out of the final build.

postcss.config.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

// 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

$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

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

<template>
  <div class="container">
    <div class="text-center">
      <h1>This is the home page</h1>
    </div>
  </div>
</template>

src/views/About.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

npm run dev

This will start the dev server on http://localhost:3000

For bundling our app for production we use the following command

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, which also includes ESLint, Prettier, semantic-release, Jest for testing, and more…

Thanks for reading!

Author: Giannis Koutsaftakis