Creating a Modern Frontend Development Environment (2018): Webpack 4
2018. 4. 16.

Recently, I started a new project and decided to fully adopt Vue (hereafter referred to as Vue), which I had only briefly explored before. Although the team initially preferred React, the use of Vue was rapidly increasing within the company, necessitating expertise in Vue within the team. Since Vue and React are tools that influence and evolve alongside each other, they share many similarities, making adaptation relatively easy. For this new project, we not only used Vue but also upgraded other development environments to modern standards. This included using Webpack 4 and switching from Karma (previously the team's standard test runner) to Jest, which has gained significant popularity.
This post is the first in a three-part series about setting up a development environment with Webpack 4 + ES6 + Vue 2 + Jest. The content is tailored for both developers who have used Webpack before and those encountering it for the first time.
Changes in Webpack 4
Webpack 4 was released a few months ago, bringing significant changes. The most notable addition is the introduction of pre-configured modes tailored for development (Development mode
) and production (Production mode
). This seems to incorporate Parcel's simplicity. The options applied vary depending on the mode. Here's a summary of the changes:
- Faster build speeds: The development team claims build speeds are up to 98% faster. Considering multi-core processing isn't involved, this optimization is impressive.
- Separation of
webpack
core andwebpack-cli
: These are now distributed separately. - Modes for zero-configuration builds: Builds can now be performed without configuration files by adhering to certain rules, thanks to the
Production
andDevelopment
modes. This is referred to as "0CJS" (Zero Configuration JavaScript). - Replacement of CommonsChunkPlugin: It has been deprecated in favor of SplitChunksPlugin, with the addition of an
optimization.splitChunks
option. - Direct import of WebAssembly files: WebAssembly (wasm) files can be imported directly without special handling. While experimental in Webpack 4, stable support is expected in Webpack 5.
Basic Bundling Environment
For this project, we aimed to use Webpack to set up a development server for active work and create production builds once unit development was completed. Let's start with setting up a basic bundling environment. Create a project directory with a suitable name and initialize a Node.js project:
npm init
As usual with npm init
, you can repeatedly press Enter for default settings. If you prefer skipping this step entirely, use the -y
option.
npm install --save-dev webpack webpack-cli
Install webpack
and webpack-cli
. Avoid installing them globally to ensure proper bundling within your project. To test zero-configuration builds using modes, write some simple code:
// src/index.js
import sayHello from './sayHello';
console.log(sayHello());
// src/sayHello.js
export default function sayHello() {
return 'HELLO WEBPACK4';
}
Create a src
directory and add these files inside it. Now let's build without configuration for different environments:
npx webpack --mode development
The npx
command allows you to run dependencies installed locally within your project as if they were globally available. Here, we specify the mode as development
.
The result is a bundled file created at dist/main.js
. Since it's built in development
mode, the file isn't compressed but includes source maps. To bundle in production
mode, simply set the mode option to production
.
Using Modes with webpack.config.js
While zero-configuration builds are convenient for small projects or prototypes, detailed configuration is often necessary for practical use cases. In Webpack 4, the mode option is mandatory—not just for zero-configuration builds but also when using custom configurations via webpack.config.js
. Each mode has default settings that can be overridden with custom configurations.
Here's a basic configuration:
const path = require('path');
module.exports = {
entry: {
app: ['./src/index.js']
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
The main difference from zero-config builds is that the bundled file is named app.bundle.js
instead of main.js
. This naming convention feels more appropriate and will be useful later when separating dependency modules into chunks.
When using command-line options like --mode
, ensure they align with your configuration file settings. For example:
// webpack.config.dev.js
// ...
module.exports = {
mode: 'development', // Use 'production' for production settings.
// ...
}
This eliminates the need to specify the mode via command-line options every time you build. However, if you do provide options via the command line, they will override settings in your configuration file.
To use specific configuration files during builds:
npx webpack --config webpack.config.dev.js
Alternatively, you can create shared configurations that adapt based on command-line options:
module.exports = (env, options) => {
const config = {
entry: {
app: ['./src/index.js']
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
if(options.mode === 'development') {
// Development-specific settings.
} else {
// Production-specific settings.
}
return config;
}
This approach lets you dynamically adjust configurations based on build modes (development
or production
) while maintaining flexibility.
Production Build Settings
For production builds in this project, code minification was the primary consideration. Previously, developers had to install and configure UglifyWebpackPlugin
. In Webpack 4, however, this plugin is built-in and automatically enabled when using production
mode—no additional installation or configuration required.
To clean up previous build directories during production builds, install a plugin:
npm i --save-dev clean-webpack-plugin
Add its configuration:
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = (env, options) => {
if(options.mode === 'production') {
config.plugins = [
new CleanWebpackPlugin(['dist'])
];
}
return config;
}
With this setup, the dist
directory will be cleared before every production build.
Development Build Settings
Development builds focus on setting up a development server rather than creating separate builds. For this purpose:
-
Install necessary plugins:
npm i --save-dev html-webpack-plugin webpack-dev-server
-
Configure:
const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = (env, options) => { if(options.mode === 'development') { config.plugins = [ new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ title: 'Development', showErrors: true // Displays error messages directly in the browser. }) ]; config.devtool = 'inline-source-map'; config.devServer = { hot: true, host: '0.0.0.0', // Allows external access; default is "localhost". contentBase: './dist', stats: { color: true } }; } return config; }
This setup includes Hot Module Replacement (HMR), source maps for debugging, and dynamic generation of an index.html file during server startup.
splitChunks
The functionality previously handled by CommonsChunkPlugin can now be achieved using splitChunks options in Webpack 4. This feature allows strategic splitting of large bundle files based on criteria like file size or asynchronous requests.
Here’s an example configuration for separating dependency modules from implementation code:
module.exports = (env, options) => {
const config = {
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
return config;
}
This separates files from the node_modules
directory into their own chunk named vendors.bundle.js
.
Adding Scripts to package.json
Finally, add scripts to your package.json file for easier execution:
"scripts": {
"build-dev": "webpack --mode development",
"build": "webpack --mode production",
"dev": "webpack-dev-server --open --mode development"
}
Now you can run commands like:
npm run dev
: Starts the development server and opens it in your browser.npm run build
: Creates a production build.npm run build-dev
: Performs a development build.
Final Configuration File
Here’s the complete configuration file (webpack.config.js
) after implementing all these features:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, options) => {
const config = {
entry: { app: ['./src/index.js'] },
output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') },
optimization: { splitChunks: { cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } }
};
if(options.mode === 'development') {
config.plugins = [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({ title: 'Development', showErrors: true })
];
config.devtool = 'inline-source-map';
config.devServer = { hot: true, host: '0.0.0.0', contentBase: './dist', stats: { color:true }};
} else {
config.plugins = [new CleanWebpackPlugin(['dist'])];
}
return config;
};
Conclusion
Using Webpack 4 simplifies bundling configurations significantly while enhancing usability. Although plugin support was initially limited upon release, most commonly used plugins and loaders are now fully supported as of version 4.6. In future posts, we’ll explore integrating ES6 and Vue2 into this environment.
with kakaopay
Recommend Post
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.