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.
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.
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.)
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.
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'
}
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'
},
__dirname
and path
here to make sure everything ends up in the right place regardless of the current working directory.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
.build-[hash].js
) or use the chunk name (build-[name].js
) as well as other thingsI’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.
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:
.vue
extension. That makes sure vue-loader
only runs against .vue
files.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]'
}
}
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
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
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
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
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.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.
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.
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/