Exploring Zero Configuration With Vue

Vue.js is a versatile and approachable framework for building modern JavaScript applications. You can start out by simply adding a script tag with some vanilla JS. Or you can go all-in and create a modern JavaScript application with all the trimmings using a build system such as Webpack. However, the latter requires a fair bit of configuration knowledge so it doesn't quite fit the mantra of easily approachable, especially for people who are just starting to explore the world of modern JavaScript applications.

In this article we are going to explore the new trend of Zero Configuration JavaScript (aka #0CJS) build systems in the Vue ecosystem.

Vue App

To start this journey we are going to create an extremely basic Vue application. We will then see how this same structure will work under different bundlers. Since we are exploring zero config build systems, we will focus on using the canonical Vue Single-File Components (SFC) approach.

# Make a new directory for our sample app
mkdir /path/to/vue-app
cd /path/to/vue-app

# Initialize a package.json
yarn init -y

# Add the vue npm package dependency
yarn add vue

We start by creating an App.vue root component (by convention) for our application.

// src/App.vue
<template>
  <h1>{{ title }}</h1>
</template>

<script>
import Vue from 'vue'

export default {
  name: 'app',
  data() {
    return { title: 'Hello Vue!' }
  },
}
</script>

Next, we add a typical entry point to bootstrap the Vue application.

// src/index.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  el: '#app',
  render: h => h(App),
});

Finally let's create an index.html file so we can actually see our Vue application running.

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello Vue!</title>
</head>
<body>
  <div id="app"></div>
  <script src="./src/index.js"></script>
</body>
</html>

At this point we have the shell of our simple Vue application. Let's view the app using a simple local web server.

# yarn global add lite-server
#  - or use equivalent (e.g. `serve`, etc.)
lite-server

If you check your browser you won't see anything, but the JavaScript console will show an error like this:

# In Chrome
Uncaught SyntaxError: Unexpected identifier ()

# In Firefox
SyntaxError: import declarations may only appear at top level of a module

You can probably already guess this is because we are trying to run the Vue application without a build system in place. One of the nice things about Vue is you could have just as easily built the equivalent app by using the Vue CDN and vanilla JS. However, this article is about using Vue Single-File Components (SFC), which requires some sort of build or bundling tool.

So the question is, how do we get this working? The answer is there are multiple ways with different tools (which you may already be familiar with). This is where you typically need to put in additional time and effort to get things in a state where you can actually run your application. Is it really worth the effort for such a simple application? Could there be a simpler way?

These days the JavaScript community has more or less settled on most of the common conventions. This means bundlers may work differently and/or require different configuration, but the final output is usually very similar. This allows zero configuration builds to finally be in our grasp to make modern JavaScript applications "just work". without extraneous setup.

Vue with Webpack

The Vue ecosystem embraces agnostic tooling. This means it can be easily used with most bundlers and build systems (or none at all). That said, the canonical way to build Vue applications is using Webpack. Currently Webpack is probably the most popular tool for building modern JavaScript applications. However, one of the common complaints is it can be like a choose your own adventure to get things setup.

The vue-loader Webpack plugin is responsible for the magic behind Vue Single-File Components (SFC) in Webpack.

Webpack 4 has recently been released with initial support for zero configuration builds. Let's explore what's involved in making our simple Vue application run with Webpack.

First, we will add the minimum dev dependencies to get our basic Vue app running.

# Install webpack dev dependencies
yarn add -D webpack webpack-cli webpack-dev-server

# Install webpack vue plugins
yarn add -D vue-loader vue-template-compiler

Now add some npm scripts to make running the Webpack commands easier. Webpack 4 adds the --mode option which include default behaviours for both development and production builds.

{
  "..."
  "scripts": {
    "start": "webpack-dev-server --mode development --open",
    "build:dev": "webpack --mode development",
    "build:prod": "webpack --mode production"
  }
  "..."
}

At this point if you tried to run yarn start you would see the following error:

ERROR in ./src/App.vue
Module parse failed: Unexpected token

This is because Webpack doesn't know how to process .vue files yet. To do this we actually need to break our zero configuration covenant and add a webpack.config.js file and register the vue-loader plugin to handle .vue files.

Let's go ahead and do that now. While we're at it, let's also configure the popular html-webpack-plugin to automatically inject the built JavaScript script tag for us.

// webpack.config.js
const HtmlWebPackPlugin = require('html-webpack-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
    ],
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: './index.html',
      filename: 'index.html',
    }),
  ]
}

Finally we remove the script tag in our index.html template and make use of the html-webpack-plugin.

<!-- index.html -->
<html>
<body>
  <div id="app"></div>
  <!-- Remove or comment out script since built files will be auto injected -->
  <!-- <script src="./src/index.js"></script> -->
</body>
</html>

Now we can build and view our simple Vue application by running:

yarn start

Great! Let's add some style to our application by inverting the background and foreground colours.

// src/App.vue
<template>...</template>
<script>...</script>

<style>
html {
  background: black;
  color: white;
}
</style>

If you're watching your command shell, you probably noticed that Webpack failed with the following error:

ERROR in ./src/App.vue
Module not found: Error: Can't resolve 'css-loader'

We are starting to see the current state of zero configuration in Webpack isn't so #0CJS. In order to fix this we would need to add the css-loader as a dev dependency and configure it in the webpack.config.js file. The same would apply if we wanted to use SCSS, TypeScript or various other Webpack loaders and plugins. There isn't anything special about doing this compared to previously configuring Webpack, but at this time, it's not really the zero configuration dream we were hoping for.

The good news is the Webpack team has indicated their approach is to allow "others to extend" rather than bake it all into the core. They are currently working on finishing up their new presets design, which should allow the community to extend #OCJS further. Given some time, I suspect that true zero configuration with Webpack will become a reality (or closer to it).

Vue with Parcel

In stark contrast to Webpack, Parcel aims to hide all of the complexity and configuration required with a "batteries included" approach.

Update: Parcel v1.7 was just released which includes native Vue support. I took a quick spin of the new release and it seems to alleviate some of concerns I had from the following setup. It's looking much more like a serious contender in the zero configuration landscape.

Parcel is a relatively new alternative bundler, which aims to simplify building web applications. Parcel works by reading an entry point file (e.g. index.html) and then transforming and bundling all the required HTML and various asset types into a dist directory. It also has native support for common asset types and transformers like SCSS, Babel, TypeScript, and PostCSS. Additionally, it includes handy development features like live reloading, hot module replacement (HMR), and code splitting.

Wow, could this be pure zero configuration bliss? Let us see by converting our Vue app from Webpack to parcel.

Since Vue support for Parcel core is still under development we will leverage the unofficial parcel-plugin-vue plugin (think vue-loader for Webpack).

# Install parcel, vue plugin, and required babel dependencies
yarn add -D parcel-bundler parcel-plugin-vue babel-plugin-module-resolver babel-plugin-transform-runtime babel-preset-es2015

# Add the parcel bundler as a global command (note: you could also use the dev dependency and create an npm script)
yarn gobal add parcel-bundler

Make sure you check out the docs for this plugin as there may be some additional configuration and unsupported features to look out for (at this time).

There is currently an open feature request to add native Vue support to Parcel which will make the parcel-plugin-vue plugin obsolete and further simplify this approach.

After we have our dependencies installed we need to update our index.html file again. This time we will be adding back the script tag we removed for Webpack. However, in this case along with the help of the parcel-plugin-vue plugin, we won't have any additional configuration (i.e webpack.config.js). This is because Parcel will parse the index file, install any additional required dev dependencies, and bundle everything into a dist directory for us.

<!-- index.html -->
<html>
<body>
  <div id="app"></div>
  <!-- Parcel will use this to build your app -->
  <script src="./src/index.js"></script>
</body>
</html>

After that, all we need to do is run parcel and we should see our application running in our browser.

parcel index.html --open

As you can see this was extremely simple compared to the Webpack approach. We also got some additional features like hashed filenames for cache busting. At this point, we could also add an SCSS file and link to it in the index file to automatically get SASS support (among other asset types). Parcel takes care of figuring out what your intention is based on the asset types and common conventions!

However … chances are if you started extending this Vue application (as one would do), you may run into some annoying issues along the way. Parcel is pretty neat, but the currently useful parcel-plugin-vue library is more of a stopgap until native Vue support is added to Parcel. It works when you stay within the supported feature set, but buyer beware if you stray off the trail a little bit.

Based on this, I would hesitate to recommend going this route for anything beyond a simple toy app like this one. However, when Parcel finally has native support for Vue, I could see this being a viable option for building Vue apps very quickly. The potential for zero configuration is within sight and the healthy competition with Webpack is always (or at least probably) a good thing.

The Vue team is working on the vue-component-compiler, which is a bundler agnostic API to centralize the core processing logic for Vue Single-File Components. This should allow better support across different bundling tools (Webpack, Parcel, Rollup, etc).

Hey What About … ?

Yes, yes there are other bundlers and build systems with varying abilities. However, I think we have been so focused on build systems that we've left out some other very important factors.

Up until now, we've only got our simple Vue application bundling and running in the browser. This is really only a portion of what makes up a typical modern JavaScript application. Most projects also make use of linters, unit tests, e2e tests, code coverage, etc. All of these important things require additional dependencies, configuration and setup.

Both the Webpack and Parcel approaches demonstrated would require additional setup to add support for these additional things. This is where we enter the realm of another framework trend, the Command Line Interface (CLI). The idea behind these handy tools is to be like a command center for your JavaScript application. A single unifying place to scaffold and run your applications - the batteries included approach.

One of the pioneers in the space was the Ember CLI. Now the majority of front-end frameworks have some sort of CLI to help developers get up and running quickly in their framework (e.g. create-react-app, @angular/cli, etc.).

The Vue CLI

The Vue CLI (along with others) helps remove the overhead and effort involved in setting up a modern JavaScript application. This is done by scaffolding out all the required files to get a skeleton app with all the bells and whistles. However, you are still typically left with various configuration files to maintain and update along with the application.

Some CLI's also hide some of the complexity by abstracting the build system being used to achieve zero config approach. This is great to start, but you are also limited to what the CLI can do, and what customizations it allows. This is why some of them offer an eject option to opt out when the CLI starts getting in the way.

The Vue CLI v3 (currently in beta) brings official zero configuration to Vue. Part of the rationale is to make it simpler to upgrade applications built with the CLI. The other I suspect, is to re-emphasize Vue's already approachable nature. Let's take the new CLI for a test drive and see what it has to offer in our quest for zero configuration.

# Install vue-cli v3+ globally
yarn global add @vue/cli

# Create a new project (prompts for preset options)
# For this article we chose the default settings
vue create /path/to/vue-cli-project

# Now open the project
cd /path/to/vue-cli-project

We end up with the structure below. If you look closely, you'll see very few configuration files. This is partly because we chose to store common configuration files (.babelrc, .eslintrc, .postcss.json, etc) in the package.json file.

Take note that there is there is no Webpack configuration files anywhere. That's because the CLI abstracts all the complexities of a feature rich Webpack configuration from the developer. This is not different than other CLI's really. You can argue for or against this approach, but the fact is it's zero configuration.

├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── main.js
└── yarn.lock

"Now wait a minute" you say, there are no tests included. Yes, that's true. It was intentional in this case. I wanted to show how you can easily add them in afterwards. To do that run the following commands:

# Add mocha/chai for unit tests (note: you can also use Jest if you like)
vue add @vue/cli-plugin-unit-mocha

# Add cypress for e2e tests
@vue/cli-plugin-e2e-cypress

This adds the appropriate dependencies and invokes their respective generators to add npm scripts to the package.json file and scaffold out the appropriate files under a new tests directory.

├── tests
│   ├── e2e
│   │   ├── plugins
│   │   │   └── index.js
│   │   ├── specs
│   │   │   └── test.js
│   │   └── support
│   │       ├── commands.js
│   │       └── index.js
│   └── unit
│       └── HelloWorld.spec.js

You can do the same for many of the other preset options available. Now we have npm scripts in the package.json to control many of the basic tasks required for development.


{
  "..."
  "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "test": "vue-cli-service test",
    "e2e": "vue-cli-service e2e",
    "lint": "vue-cli-service lint",
    "e2e:open": "vue-cli-service e2e:open"
  },
  "..."
}

A Vue CLI project can also be tweaked further using a special vue.config.js file. For instance, adding arbitrary Webpack plugins on top of the CLI's base setup avoids having to use eject just to gain access to non standard features. This flexibility is something that is currently not possible with some of the other framework CLI's available.

The vue inspect command allows you to see the underlying webpack configuration. If you ever needed to eject you could simple run vue inspect > webpack.config.js.

What else does the new Vue CLI support? Turns out they have thought a lot about what developers want. Here is a short list of what's included:

  • PostCSS, Autoprefixer and CSS Modules (by default)
  • TypeScript and Class-Style Component Support
  • Progressive Web Application (PWA) Support
  • Support for environment variables with .env (dotenv) files
  • Proxy support for the HTTP server
  • Extensibility and plugin support via @vue/cli-service
  • Customization using a vue.config.js file

I have to say, the new Vue CLI has been well thought out and seems to do an amazing job balancing the goal of zero configuration while supporting customizations and extensions. I'm excited to see how it will progress and further help promote the amazing Vue framework and extended ecosystem.

Conclusion

We have just taken a journey through some of the modern JavaScript build tools that can be used with Vue. Some of these tools don't quite hold up to the reputation of zero configuration; however, they are all making strides in that general direction and bringing some healthy competition to the space.

You may have also noticed that all of the approaches demonstrated here follow very similar conventions in relation to the structure and output of the final builds. This is a sign that much of the current JavaScript build tooling is converging on common practices and conventions. This should ultimately lead to more options for developers to choose from while providing a standard mental model when switching between them. It is hard to deny that zero configuration might just be here to stay!