Tutorial

Demistifying Vue's Webpack Config

Published on October 9, 2017
author

Joshua Bemenderfer

Demistifying Vue's Webpack Config

Like it or not, webpack is a staple of the modern front-end JavaScript toolset, turning countless little files into one (or more) cohesive bundles. Yet to many, it’s workings are an enigma. The rise of CLI tools that produce complex ready-made configurations has helped both assuage and exacerbate this problem. But when all is said and done, do you really understand what that myriad of configuration files are even doing? Would you be able to build your own configuration by hand? Well hopefully, after this article, you will be.

Starting Off

Let’s create a quick project with vue-cli’s webpack-simple template.

The webpack-simple template is about as basic of a webpack setup as you can get away with for Vue. It is, as the name implies, pretty simple, but it gets the job done remarkably well. I have to admit that I’ve used it in production once even though you’re not supposed to…

# Install vue-cli globally if you haven't already.
$ npm install -g vue-cli

# Create a new project called "demistify-project" with the "webpack-simple" template.
$ vue init webpack-simple demistify-project

# Install the dependencies for the project
$ cd demistify-project
$ npm install

Alright, now, if you want, fire up the development build with npm run dev and play around with the code, hot-reload, and whatnot. Don’t worry, I’ll wait.

The ✨Mystical Config✨

You done? Alright, let’s go ahead and take a peek at webpack.config.js, the file that does most of the heavy-lifting. I’ll paste the file I have here for good measure. (Generated with version 1.0.0 of the webpack-simple template.)

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: {
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  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
    })
  ])
}

Okay, so there’s quite a bit to take in there. And honestly, you’d do well to leave most of it alone. I’ll try and explain it to the best of my ability though, so if you really do feel like messing with something, you’ll at least have a vague idea of what you’re messing with.

Anyway, webpack.config.js is just a file that exports an object that is interpreted by webpack to build your project. That means you can use any node modules in it and compose it from as many files and places as you want. That makes Webpack incredibly flexible and powerful, but it also means that almost no-one uses the exact same conventions when writing their build configurations.

Entry

Documentation

Stepping through that object, the first property we hit is entry. This tells Webpack which files to start the bundling process from. You can also pass an array of strings, or use an object to specify different chunks.

entry: './src/main.js',

// Different chunks:
entry: {
  main: './src/main.js',
  vendor: './vendor/index.js'
}

Output

Documentation

The next section is a bit more complicated and confusing. The output property is where we specify where (and how) the generated bundle(s) end up. As it’s used in the template, the output section looks like this:

output: {
  path: path.resolve(__dirname, './dist'),
  publicPath: '/dist/',
  filename: 'build.js'
},
  • path - The root directory for all generated assets. We use __dirname and path here to make sure everything ends up in the right place regardless of the current working directory.
  • publicPath - This is the path the development HTTP server will serve the contents of output.path on. You might need to change this a bit for your particular setup. Basically: ./dist/build.js in the filesystem becomes /dist/build.js.
  • filename - The name of the file to bundle everything into. This can be customized to include a hash (build-[hash].js) or use the chunk name (build-[name].js) as well as other things

I’ve found that the majority of my problems stem from mis-configured entry and output sections, so it’s a good idea to get real familiar with those, and look there first if something goes wrong.

Module, rules, and loaders. Oh my!

This bit here is essentially the core of a webpack build. The file loaders. Loaders are essentially packages that transform whatever files are routed through them into some other form. This is how all those cool pre-processors like babel and vue-loader work with webpack.

This section looks complicated but to be honest it’s really predictable and easy to understand.

Let’s go ahead and step through each of the rules:

{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    loaders: {
    }
    // other vue-loader options go here
  }
},

Ah yes, good 'ol vue-loader the secret sauce of Vue.js that turns Single-File-Components into JS and render functions.

Here’s what each property does:

  • test - A regular expression that checks if a filename ends with the .vue extension. That makes sure vue-loader only runs against .vue files.
  • loader - The name of the loader to use. No surprises here.
  • options - Options you can pass to the loader.

Since vue-loader lets you add in other loaders as pre-processors for parts of templates, hence the options.loaders property.

The format of options.loaders is as follows:

loaders: {
  preprocessorName: Loader Definition | Array<Loader Definition>
}

For example, if you wanted to use SCSS in Vue components, you’d set options.loaders like so:

loaders: {
  sass: [
    {
      loader: 'css-loader'
    },
    {
      loader: 'sass-loader',
      options: {
        indentedSyntax: false // Set to true to use indented SASS syntax.
      }
    }
  ]
}

That first processes the styles with sass-loader to turn them into valid CSS, then lets webpack’s css-loader do whatever it needs to with them.

Okay, moving on to the next rule. The one for babel-loader.

As specified by test, webpack will run all .js files through babel-loader.

We also see a new property, exclude . This is a regex that tells webpack what patterns to ignore. In this case, it excludes any .js files in node_modules.

{
  test: /\.js$/,
  loader: 'babel-loader',
  exclude: /node_modules/
},

The final loader is file-loader it takes any files imported or linked to and outputs them in the output directory.

In this configuration, it will run for four common image formats, outputting them in the build directory with the hash of the files contents appended. (This makes it easier to know when a file has changed.) It also modifies any references to these files so they include the hash.

For example, ./src/alligator.svg might become ./dist/alligator.svg?uqTyWCLN8jVxGHFU4kiN1DXB0G6qzDae4Y4kFxZaP4g=

{
  test: /\.(png|jpg|gif|svg)$/,
  loader: 'file-loader',
  options: {
    name: '[name].[ext]?[hash]'
  }
}

Miscellaneous bits & bobs

At the end of the configuration object are a few more properties. These are more related to optional features of webpack, but let’s go through them anyway.

resolve: {
  alias: {
    'vue$': 'vue/dist/vue.esm.js'
  }
},
devServer: {
  historyApiFallback: true,
  noInfo: true
},
performance: {
  hints: false
},
devtool: '#eval-source-map'
  • resolve / alias - The resolve object allows you to configure how webpack’s module resolution works. In this case, we’re aliasing the package vue to vue/dist/vue.esm.js, which provides Vue in ES2017 Module format.
  • devServer - The devServer allows us to configure webpack’s development server. In this case, we’re just telling it to fall back to sending index.html for 404 errors (historyApiFallback). All noInfo does it tell webpack to not output a bunch of stuff we don’t need to the terminal each time a hot-reload happens.
  • performance - A property like performance looks important right? Sorry to disappoint. All it really does is configure webpack’s performance hints, which mostly tell us when we’re bundling files that are kinda big. We’re disabling that here for now.
  • devtool - The devtool property lets you decide which method of source mapping to use. There are like twelve different options for this which vary in speed, quality, and production-readiness. We’re using eval-source-map here which honestly isn’t great, but doesn’t create any new files or modify the source.

Production configuration

Congratulations! We’re almost done! The very last bit of the file are changes to the configuration that are made if we’re building the production version of the code. It looks a lot scarier than it is. In fact, I think I’ll just throw comments in it instead of writing everything down separately.

// Make sure to set the NODE_ENV environment variable to 'production'
// when building for production!
if (process.env.NODE_ENV === 'production') {
  // Use standard source mapping instead of eval-source-map.
  module.exports.devtool = '#source-map'

  // http://vue-loader.vuejs.org/en/workflow/production.html
  // Add these plugins:
  module.exports.plugins = (module.exports.plugins || []).concat([
    // Let's your app access the NODE_ENV variable via. window.process.env.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),

    // Crumples your bundled source code into a tiny little ball.
    // (Minifies it.)
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),

    // This is just here for compatibility for legacy webpack plugins
    // with an options format that isn't compatible with Webpack 2.x
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

There you go! Hopefully you now know enough to create your own Webpack + Vue setup.

When in doubt, don’t shy away from reading the docs. They’ve improved quite a bit recently.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Joshua Bemenderfer

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Why bother if you are not supposed to use it in production? And why shouldn’t you/

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.