🔨 Webpack overview. From Grafikart.fr 'Comprendre Webpack'. The training was for version 3 but has been updated for version 4 using documentation.
Webpack allows you to break up and modulate Javascript.
For the React version with classes see my webpack-react-hot-reload repository.
For the React version with hooks see my webpack-react-hot-reload-hooks repository.
Webpack is a module builder. This is important to understand, as Webpack does not run during your page, it runs during your development.
Webpack is a tool wherein you use a configuration to explain to the builder how to load specific things. You describe to Webpack how to load *.js
files, or how it should look at .scss
files, etc. Then, when you run it, it goes into your entry point and walks up and down your program and figures out exactly what it needs, in what order it needs it, and what each piece depends on. It will then create bundles — as few as possible, as optimized as possible, that you include as the scripts in your application.
- Configuration
- Dev mode / prod mode (new in Webpack 4)
- webpack.config.js (several files: common, dev and prod)
- Lazy Loading / Code splitting
- Minification
- Babel
- CSS, Sass, CSS extraction
- Caching
- Url Loader
- Resolve / Aliases
- Eslint
- Dev Server
npm init -y
npm i -D webpack webpack-cli
Structure:
webpack-demo
|- package.json
+ |- index.html
+ |- /src
+ |- index.js
- Use:
./node_modules/.bin/webpack
- Or in package.json:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production",
"dev": "webpack --mode=development"
},
Then:
npm run dev
/ npm run build
- Or if installed globally use:
webpack
NB: it's not a good practise to install it globally.
Will create a 'dist' folder:
webpack-demo
|- package.json
+ |- index.html
+ |- /dist
+ |- main.js
+ |- /src
+ |- index.js
Providing the mode
configuration option tells webpack to use its built-in optimizations accordingly.
It's new in Webpack 4. So webpack provides a preconfiguration for testing and building.
string = 'production': 'none' | 'development' | 'production'
package.json
"scripts": {
"dev": "webpack --mode=development",
"prod": "webpack --mode=production",
"serve:dev": "webpack-dev-server --open --mode=development",
"serve:prod": "webpack-dev-server --open --mode=production"
},
We can still use a webpack.config.js file.
Here is an exemple with several configs according to the mode dev or prod, with a common part.
webpack-demo
|- package.json
|- webpack.config.js
+ |- index.html
+ |- /config
+ |- webpack.common.js
+ |- webpack.development.js
+ |- webpack.production.js
+ |- /build (prod)
+ |- main.js
+ |- /dist (dev)
+ |- main.js
+ |- /src
+ |- index.js
webpack.config.js
const { merge } = require('webpack-merge');
const commonConfig = require('./config/webpack.common');
module.exports = (env, options) => {
const config = require(`./config/webpack.${options.mode}`);
return merge(commonConfig, config);
};
config/webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: ['./src/css/app.scss', './src/index.js']
//the entry is the name given to the files in build, here 'main'
},
plugins: [
new HtmlWebpackPlugin({
hash: true,
title: 'Webpack overview',
myPageHeader: 'Webpack overview',
myEnv: 'Webpack environment: ',
template: './src/template.html',
filename: './index.html' //relative to root of the application
})
],
};
config/webpack.development.js
const path = require("path");
//Configuration
module.exports = {
watch: true,
mode: 'development',
output: {
path: path.resolve(__dirname, '..', "dist"),
filename: '[name].js'
},
devtool: "eval-cheap-module-source-map",
plugins: [
],//\plugins*/
module: {
rules: [
//...
]//\rules
}//\modules
}//\module export
config/webpack.production.js
const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
//...
//Configuration
module.exports = {
watch: true,
mode: 'production',
output: {
path: path.resolve(__dirname, '..', "build"),
filename: '[name].[chunkhash:8].js'
//Cache invalidation can be achieved by including a hash to the filenames
},
devtool: "source-map",
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].css',
}),
new TerserPlugin({
sourceMap: true
}),
//...
],//\plugins
module: {
rules: [
//..
]//\rules
}//\modules
}//\module export
It's also possible to use only one config file using conditionnals to filter what is applied on dev or prod.
package.json
"scripts": {
"dev": "NODE_ENV=dev webpack",
"start": "webpack-dev-server --open",
"prod": "webpack"
},
webpack.config.js
const path = require("path");
const TerserPlugin = require('terser-webpack-plugin');
const dev = process.env.NODE_ENV === "dev";
let cssLoaders = [
'style-loader',
'css-loader'
]
//Only use postcss-loader in production
if (!dev) {
cssLoaders.push({
loader: 'postcss-loader'
})
}
//Configuration
let config = {
entry: './src/index.js',
watch: dev,
mode: 'development',
output: {
path: path.resolve(__dirname, "build"),
filename: 'bundle.js'
},
//Ternary operator to filter the way to use of TerserPlugin
devtool: dev ? "eval-cheap-module-source-map" : "source-map",
plugins: [
//**
],//\plugins
module: {
rules: [
{//CSS used in dev mode
test: /\.css$/i,
sideEffects: true,
use: cssLoaders
},
{//SASS used in prod mode
test: /\.scss$/i,
sideEffects: true,
use: [
...cssLoaders,
'sass-loader'
]
}
]//\rules
}//\modules
}//\config
//Only use TerserPlugin (source map plugin) in production
if (!dev) {
config.plugins.push(new TerserPlugin({
sourceMap: true
}))
}
module.exports = config;
Allows deferred loading for some file to improve performances or if we don't always need a component. This practice essentially involves splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.
Use this plugin for dynamic imports:
npm i -D babel-plugin-syntax-dynamic-import
In .babelrc.json:
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "entry",
"modules": false,
"debug":true
}]
],
"plugins": ["syntax-dynamic-import"]
}
Exemple with an external component displaying a console log using a promise:
index.js
document.getElementById('button').addEventListener('click', function () {
//Async promise
import('./print').then(module => {
const print = module.default;
print();
})
})
print.js
console.log('The print.js module has loaded! See the network tab in dev tools...');
export default () => {
console.log('Button Clicked: Here\'s "some text"!');
};
Another exemple that display Web Assembly in client console, in index.js:
import('./test.wasm').then(function (module) {
//wasm script to add a number to 42
log(module._Z5add42i(20))// Output: 62
}).catch(console.log)
A resolver is a library which helps in locating a module by its absolute path. A module can be required as a dependency from another module as:
import foo from 'path/to/module';
// or
require('path/to/module');
The dependency module can be from the application code or a third-party library. The resolver helps webpack find the module code that needs to be included in the bundle for every such require
/import
statement. webpack uses enhanced-resolve to resolve file paths while bundling modules.
npm install enhanced-resolve
By default webpack uses Resolve.
Here an exemple to create aliases:
webpack.config.js
resolve: {
alias: {
'@Css': path.resolve(__dirname, '../src/css/'),
'@Img': path.resolve(__dirname, '../src/img/')
}
},
index.js
import webpackLogo from '@Css/app.scss';
It's important not to forget to use this alias everywhere:
app.scss
$background: #DDD;
body {
background: $background url(@Img/webpack-logo-horizontal.png);
background-size: 100% 100%;
}
DevServ: webpack-dev-server
Use webpack with a development server that provides live reloading. This should be used for development only.
npm install webpack-dev-server --save-dev
package.json
"scripts": {
"serve:dev": "webpack-dev-server --open --mode=development --hot",
"serve:prod": "webpack-dev-server --open --mode=production"
},
The flag '--hot' enable hot reload.
The flag '--open' opens the served page in the browser.
webpack.config.js
module.exports = {
entry: {
app: ['./assets/css/app.scss', './assets/js/app.js']
},
output: {
path: path.resolve(./public/assets),
filename: '[name].js',
publicPath: '/assets/'
},
devServer: {
contentBase: path.resolve('./public'), // path to the directory where pages are served
sockjsPrefix: '/assets', // use it if DevServ try to access websockets in root
overlay: true, //add an overlay with errors in the browser
// If problems with CORS (exemple if we use several domains during dev)
/*headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
},*/
// Or we can use a proxy
proxy: {
'/web': {// to distribute assets correctly
target: ''http://localhost:8000',', // launch PHP server on: 127.0.0.1:8000
pathRewrite: {'^/web' : ''}
}
}
}
//...
}
Hot reload for specific javascript libraries/frameworks are usually provided with the CLI.
The can be modified using Hot Module Replacement.
Dev server can be used with the html using html-webpack-plugin (cfr. infra in plugins section).
Dev server allows custom setup using webpack-dev-middleware.
Webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.
Babel: BabelLoader
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code (ex: JSX) into a backwards compatible version of JavaScript (vanilla) in current and older browsers or environments.
npm install --save-dev @babel/core @babel/register @babel/preset-env babel-loader
npm install --save @babel/polyfill
webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
}]
}
}
.babelrc.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "entry",
"debug":true
}]
]
}
Eslint: EslintLoader
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.
npm install eslint-loader --save-dev
Note: You also need to install eslint from npm, if you haven't already:
npm install eslint --save-dev
Following plugins/dependencies are also needed:
eslint-plugin-import: npm i eslint-plugin-import -D
eslint-plugin-node: npm i eslint-plugin-node -D
eslint-plugin-promise: npm i eslint-plugin-promise -D
eslint-plugin-standard: npm i eslint-plugin-standard -D
We have to load Eslint before Babel so for that we use the enforce
property:
webpack.config.js
rules: [
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
}
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
},
]
It's possible to add autofix in the config file, buts it's not advised. It's better to correct the errors by hand and learn to avoid them when coding.
options: {
fix: true,
},
A config file is mandatory to make Eslint working, .eslintrc in the root.
Let's use JavaScript Standard Style:
npm install eslint-config-standard-D
.eslintrc
{
"extends": "standard"
}
Preprocessor CSS (SASS): CssLoader, StyleLoader, SassLoader
css-loader: convert css to strings
npm install --save-dev css-loader
style-loader: inject strings into style tags
npm install --save-dev style-loader
sass-loader and node-sass: to use css preprocessor, here sass to css
npm install --save-dev sass-loader node-sass
webpack.config.js
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
//loader on the right loaded first, respect the logical order
},
{
test: /\.scss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
}
]
}
app.scss
$background: #DDD;
body {
background: $background;
}
index.js, import scss
import css from './app.scss'
SCSS will be automaticaly converted into CSS.
Postprocessor CSS (PostCSS): postcss-loader
PostCSS loader : a CSS post-processing tool that can transform your CSS in a lot of ways, like autoprefixing, linting and more!
npm i -D postcss-loader
If you use plugins, you have to install them.
Exemple:
postcss-preset-env: npm i postcss-preset-env
css nano: npm i cssnano
webpack.config.js
{
test: /\.scss$/i,
//use: ['style-loader', 'css-loader', 'sass-loader'],
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
{ loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('postcss-preset-env')({
browsers: "last 2 versions",
stage: 3,
features: {
"nesting-rules": true
}
}),
require('cssnano')(),
],
}
},
'sass-loader'
],
}
or with an external config file:
webpack.config.js
{
test: /\.scss$/i,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
}
postcss.config.js
module.exports = {
plugins: {
'postcss-preset-env': {
browsers: 'last 2 versions',
stage: 3,
features: {
"nesting-rules": true
}
},
cssnano: {},
},
};
url-loader: A loader for webpack which transforms files into base64 URIs.
npm install url-loader --save-dev
webpack.config.js
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: 'url-loader',//base 64 conversion
options: {
limit: 8192,//beyond the limit it will use 'file-loader' by default
name: '[name].[hash:7].[ext]'
},
},
],
},
file-loader: The file-loader resolves import/require() on a file into a url and emits the file into the output directory.
npm install file-loader --save-dev
webpack.config.js
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
loader: 'file-loader'
},
img-loader: Image minimizing loader for webpack 4, meant to be used with url-loader, file-loader, or raw-loader.
Meant to be used with url-loader, file-loader, or raw-loader
npm install img-loader --save-dev
img-loader can be used with a combination of other plugins, for instance imagemin:
imagemin: npm install imagemin
imagemin-mozjpeg: npm install imagemin-mozjpeg
imagemin-gifsicle: npm install imagemin-gifsicle
(if problem during install use: npm install imagemin-gifsicle@6.0.1
)
imagemin-pngquant: npm install imagemin-pngquant
imagemin-svgo: npm install imagemin-svgo
imagemin-webp: npm install imagemin-webp
webpack.config.js
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
'url-loader?limit=10000',
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
]
}
}
],
},
raw-loader: A loader for webpack that allows importing files as a String.
npm install raw-loader --save-dev
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.txt$/i,
use: 'raw-loader',
},
],
},
};
Webpack has a rich plugin interface. Most of the features within webpack itself use this plugin interface. This makes webpack flexible.
Minify code
npm i terser-webpack-plugin --save-dev
It's generally a good practice to minify and combine your assets (Javascript & CSS) when deploying to production. This process reduces the size of your assets and dramatically improves your website's load time. Source maps create a map from these compressed asset files back to the source files.
Whe will use Devtool to control if and how source maps are generated.
For instance in webpack.config.js for production:
module.exports = {
watch: true,
mode: 'production',
//...
},
devtool: "source-map",
}
In the browser, disable 'Enable JS source maps'.
Then we can use console.log
and debugger
even with the minified build files.
Warning: UglifyjsWebpackPlugin is deprecated
The HtmlWebpackPlugin simplifies creation of HTML files to serve your webpack bundles.
npm i -D webpack-dev-server html-webpack-plugin
webpack.config.js, exemple with a template call:
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HTMLPlugin({
hash: true,
title: 'My Awesome application',
myPageHeader: 'Hello World',
template: './src/template.html',
filename: './index.html' //relative to root of the application
})
]
}
In package.json, for instance with 'start':
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"dev": "webpack --mode development",
"watch": "Webpack --watch --mode none",
"start": "webpack-dev-server --mode development --open"
},
Exemple of template.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<h1> <%= htmlWebpackPlugin.options.myPageHeader %> </h1>
<h3>Welcome to the Awesome application</h3>
<my-app></my-app>
</body>
</html>
then: npm run start
/ http://localhost:8080/
This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.
To use in production.
npm install --save-dev mini-css-extract-plugin
webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].css',
}),
],//\plugins
module: {
{
test: /\.scss$/i,
sideEffects: true,
use: [
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: (resourcePath, context) => {
return path.relative(path.dirname(resourcePath), context) + '/';
},
},
},
'css-loader',
'postcss-loader',
'sass-loader'
],
}
]//\rules
};
It will search for CSS assets during the Webpack build and will optimize \ minimize the CSS (by default it uses cssnano but a custom CSS processor can be specified).
npm install --save-dev optimize-css-assets-webpack-plugin
webpack.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
//...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].css',
}),
new TerserPlugin({
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
],//\plugins
Webpack plugin for generating an asset manifest.
npm install --save-dev webpack-manifest-plugin
If we invalidate cache using hashes (chunckhash, contenthash), name of the files are generated with keys.
This plugin will create a 'manifest.json' that can be useful to manage the names, for instance in backend side.
const ManifestPlugin = require('webpack-manifest-plugin');
plugins: [
new ManifestPlugin()
],//\plugins
manifest.json
{
"main.css": "/build/main.96a6baa9.css",
"main.js": "/build/main.d6f62816.js",
"main.js.map": "/build/main.d6f62816.js.map",
"./index.html": "/build/./index.html"
}
A webpack plugin to remove/clean your build folder(s) before building again.
npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin({
verbose: true,//log
dry: false// true = test without delete, false = delete
})
],//\plugins
- Webpack 4: comprendre webpack
- Webpack
- What is Webpack and why should I care?
- Webpack 4: mode and optimization
- How to split dev/prod webpack configuration
- Modularize Your JavaScript with ES6 Modules and Webpack
- How to configure Webpack 4 from scratch for a basic website
- Code Splitting
- Débuter avec Webpack
- A mostly complete guide to webpack (2020)
- Les outils pour travailler côté front
- Working with Babel 7 and Webpack
- JavaScript Standard Style
- Devtool
- What are Javascript Source Maps?
- using html-webpack-plugin to generate index.html
- PostCSS Preset Env: Babel for CSS
- imagemin
- npm-install
- webpack-dev-server
- Comprendre WebAssembly en 5 minutes