Vue.js + Brunch: The Webpack Alternative You've Been Hungry For

Published Nov 27, 2017Last updated Dec 13, 2017
Vue.js + Brunch: The Webpack Alternative You've Been Hungry For

Imagine if there was a build tool that you could use for Vue.js projects that compiled faster than Webpack, gave you a smaller bundle size and required only a few lines of configuration.

Brunch is that tool. In this article, I'll show you how incredibly easy it is to set up a Vue.js + Brunch project, cover some of the pros and cons of Brunch, and serve up a few tasty brunch puns on the way.

To whet your appetite, take a look at this side-by-side comparison of a Webpack config and a Brunch config for the same, simple Vue.js project:

Note: this article was originally posted here on the Vue.js Developers blog on 2017/08/20

Webpack blues

Webpack is a crucial tool for building sophisticated, highly optimized web applications, and is encouraged by many influential web developers.

However, for newer developers, or those working on less ambitious projects, Webpack mostly occurs as a barrier. For Vue.js projects, users feel they must choose either to develop in a lo-fi ES5 environment without the cool features like single-file components, or, to develop in a highly sophisticated environment with as much time spent on build config as on app development.

Make room for Brunch

Brunch has been around since 2012, and while it is the default build tool for the Phoenix Elixir framework, it is still surprisingly obscure. That said, it has a dedicated group of users who love it for its core philosophy of "simplicity and speed", something that Vue.js users may identify with.

Despite its obscurity, there are almost 80 community made plugins for most imaginable automations like transpiling, linting, optimizing etc. Brunch can do most of the things you'll need in a Vue project too: wrap files as modules, concatenate them into a build file, compile Vue files, transpile JS and SASS and so on.

To be clear, Brunch is not as full-featured as Webpack and has certain limitations. For example, Brunch does not yet support dynamic imports, and it doesn't process images and fonts.

Convention over configuration

A defining feature of Brunch is that it is opinionated and favours convention over configuration. If you're willing to structure your project the "Brunch way" and you're happy with the standard settings of most plugins, you may need only a handful of lines of configuration for a surprisingly sophisticated build.

Take the example of precompiling SASS. With Webpack, each project must declare loaders for the file type that will be processed. A typical SASS configuration would be this:

webpack.config.js

module.exports = {
  ...
  module: {
    rules: [{
      test: /\.scss$/,
      use: [{
        loader: "style-loader"
      }, {
        loader: "css-loader"
      }, {
        loader: "sass-loader"
      }]
    }]
  }
};

With Brunch, however, all you need to do is install the Brunch SASS plugin. Brunch will scan your package.json when a build is triggered, and seeing that you've installed the plugin, will take care of it entirely.

Brunch taste test

To see what Brunch can do, I've installed a Vue.js project using the Vue CLI webpack-simple project template. After building the boilerplate code with Webpack I get this:

I'll now migrate this project to Brunch and attempt to recreate the same build features and processes as Webpack, so as to highlight any similarities and differences.

Feel free to download the completed code from this repo and follow along.

Installation

Like Webpack, it's best to install Brunch globally so the Brunch CLI can be run from anywhere.

$ npm i -g brunch

And also install it locally:

$ npm i --save-dev brunch

Config

Brunch has a similar declarative configuration to Webpack. The following is the minimal config to get Brunch to run. All it currently will do is modularize and concatenate any JavaScript files in the watch directory to the output file app.js.

brunch-config.js

module.exports = {
  files: {
    javascripts: {
      joinTo: 'app.js'
    }
  }
};

Unlike Webpack, Brunch does not require an entry file. You instead have a watch directory, and Brunch will simply process every file there, if it can.

Despite the lost pun opportunity, the default watch directory in Brunch is app, not src. Using app in this project will take advantage of Brunch's minimal configuration, so I'll move all the project files there:

$ mv src app

Build

With that done, I can run the first build:

$ brunch build

That results in this pleasantly brief output message:

14:32:19 - info: compiled main.js into app.js, copied logo.png in 466 ms

And a new public directory is created containing the following build files:

public
  - app.js
  - app.js.map
  - logo.png

JavaScript build file

Inspecting the main JavaScript build file, public/app.js, the first 149 lines are Brunch bootstrapping code that will be in every build. After that is code from main.js, the only JavaScript file in the watch folder:

require.register("main.js", function(exports, require, module) {
  import Vue from 'vue'
  import App from './App.vue'

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

Brunch has wrapped main.js as a CommonJS module. It hasn't imported vue or App.vue, though, and has not transpiled down to ES5. We'll need some additional plugins for those tasks.

Assets

Another convention of Brunch is that any directory called assets will be recursively copied to the public folder without any processing, which is why you see logo.png in the output.

Brunch does not load image or font files like Webpack does, so copying to the output folder is probably the best option.

Plugins

To process the project files, I'll need to add some plugins to Brunch. There's ES6 code as well as the Vue file, which include SASS, so I'll install the appropriate plugins for those file types:

$ npm i --save-dev babel-brunch babel-preset-es2015 vue-brunch sass-brunch

I also installed babel-preset-es2015 so that I get browser-friendly JavaScript. I'll need to update the .babelrc file to indicate this, as Webpack has a more sophisticated means of knowing what environment to build for:

.babelrc

{
  "presets": [
    [ "es2015" ]
  ]
}

Amazingly, that's all that's required. When I build again I get this output:

15:05:57 - info: compiled 4 files into app.js, copied logo.png in 1.5 sec

Checking the build file public/app.js again, there's a whole lot more code. This is because Brunch transpiled the ES6 in main.js, found the dependency of Vue and added that, and has also processed and imported App.vue.

How can Brunch do this without any configuration? It sees these plugins in the dependencies in package.json and simply registers them with default settings.

Serving Brunch

I've built all the project code now, so it's time to go to the browser and see what I've got.

Like Webpack, Brunch has an in-built development server that I can use to serve the project. It will also watch any files for changes and automatically build the changes (very fast too, I might add).

Before I run the server, though, I'll move index.html to the assets so directory so it gets copied into the public folder and can be served as well::

$ mv index.html ./app/assets

I can now run the server:

$ brunch watch --server

And I see this terminal output:

15:16:40 - info: application started on http://localhost:3333/
15:16:40 - info: compiled 4 files into app.js, copied 2 in 1.7 sec

Calling main.js

When I check the browser, though, all I get is a blank screen. The issue is that since there's no entry file specified, the project won't immediately run like you'd expect with Webpack. The entry file must be manually called.

Remember that Brunch wraps all files into CommonJS modules in the output like this:

require.register("main.js", function(exports, require, module) {
  // Contents of main.js
}

The convention is that the module is named by its file name minus the extension, so main.js, is just main. I need to now call that module in index.html after the script has been downloaded:

app/assets/index.html

<body>
  <div id="app"></div>
  <script type="text/javascript" src="/app.js"></script>
  <script type="text/javascript">require('main');</script>
</body>

vue-brunch

Okay, almost there. Refreshing the browser again, I get this error:

Uncaught Error: Cannot find module 'vueify/lib/insert-css' from 'App.vue'

This is because the Brunch plugin API is not quite as powerful as Webpack, and to be able to inline the CSS at runtime, vue-brunch requires the vueify-insert-css module to be available.

This will need to be imported at the top of main.js:

import 'vueify/lib/insert-css';

With that done, I've got myself a functioning Vue app again:

Production options

Before I can compare the Brunch approach to Webpack, though, I also need to set some production optimizations to ensure both approaches are producing an equivalent output.

Webpack has a configuration option to turn off the annoying development mode message automatically. As far as I know, that can't be done with Brunch, so I'll have to add this line to main.js after importing Vue:

main.js

import Vue from 'vue';
Vue.config.productionTip = false;

I also want to uglify the JavaScript build file so it's nice and compact. I'll install brunch-uglify-js for this:

$ npm i --save-dev brunch-uglify-js

As you might guess, no further configuration is required. All I need to do is add the -p (production) switch to the Brunch command when I build, and the output will be uglified.

$ brunch build -p

Easy as pie!

Comparison

I've now successfully replaced the build functionality of webpack-simple with Brunch. Let's now compare the difference in configuration files.

Firstly, Webpack:

webpack.config.js

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
            // the "scss" and "sass" values for the lang attribute to the right configs here.
            // other preprocessors should work out of the box, no loader config like this necessary.
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map'
}

if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

(Note that the above has no file-loader configuration, since that can't be accomplished in Brunch).

And now, Brunch:

brunch-config.js

module.exports = {
  files: {
    javascripts: {
      joinTo: 'app.js'
    }
  }
};

As you can see, Brunch requires significantly less configuration if you follow its conventions. To be fair, with Brunch I did have to add two extra lines of code to main.js and one extra line to index.html to get equivalent functionality.

Benchmarks

And what about size and performance? Comparing a production build from both tools:

Tool Bundle size Compilation speed
Webpack 87K 4.1 sec
Brunch 64K 1.3 sec

Amazingly, Brunch has a smaller bundle size, and compiles more than 3 times faster than Webpack.

Conclusion

I think Brunch is a great choice for simple Vue projects. It's not only easier to set up, but also faster, and for this use case, provides a smaller bundle file.

However, this doesn't mean Brunch is universally better than Webpack for Vue.js projects. There are many things Brunch can't do, for example dynamic imports which are essential for building PWAs.

The point is that while Webpack definitely has its place, Brunch should as well.

Cook up your own Brunch

Just like with Vue CLI, you can create Brunch project skeletons. I recommend you first try brunch-vue-barebones which is very similar to what I've setup.

You should also check out the Brunch docs or this great community guide for more food for thought, and of course, more brunch puns.

Bon appetit!

Get the latest Vue.js articles, tutorials and cool projects in your inbox with the Vue.js Developers Newsletter

Discover and read more posts from Anthony Gore
get started