This repo includes basic setup for Webpack to use in React project
The guide will be divided in to small sections, each section will contain setup for 1 module or functionality. For final configuration, you can check the repo.
(The code in the repo is for webpack 3 on branch master, the default branch here is webpack 4)
You need to have in your machine:
Nodejs
- gitignore
- eslint
- base webpack config
- html, css loaders and plugins
- dev-server
- Babel
- Splitchunk
- Naming modules and chunks
- Clean up dist folder
- Responsive images
- Support legacy modules
- Using env variable
- Minify js
- npm script
- Async component
TODO: 16. Service worker 17. Webpack PWA plugin
- Webpack - Module bundler
- Babel - Transpile JSX and "future" javascript features
- Eslint - Make your Js code "healthier"
First, add .gitignore file to your project root directory
# Bower dependency directory
bower_components
# Npm Dependency directories
node_modules/
# webpack's output directory
dist/
# dotenv environment variables file
.env
# some platform/IDE specific files
.DS_STORE
.idea
*.iml
Create package.json file, -y mean accept default values
npm init -y
Install eslint
npm install --save-dev eslint
Set up eslint config
./node_modules/.bin/eslint --init
Choose options that suiltable for you, if you say 'y' to React rule (and you should, this is react app, right?), add this to the .eslintrc
file to remove JSX unused vars error
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
If you use object spread operator (eg. {...props}) then add this to .eslintrc
"parserOptions": {
"ecmaFeatures": {
......
"experimentalObjectRestSpread": true
},
.....
},
Install webpack and webpack-cli (save or save-dev is based on your project)
npm install --save webpack webpack-cli
Create webpack.config.js
file in your project root directory, add the following content to the file
const path = require("path");
module.exports = {
mode : "development",
context : path.resolve(__dirname), // make all relative path relative to this instead of cwd
entry : {
main : "./src/index.js" // chunkname : "path to start bundling this chunk"
},
output : {
filename : "[name].[chunkhash].js", // name of the outputed files
path : path.resolve(__dirname, "dist"), // where to put those files
publicPath : "/" // the address seen from the web URL, after the domain
},
devtool: "eval" // source map
};
npm install --save html-webpack-plugin css-loader mini-css-extract-plugin postcss-loader
- html-webpack-plugin: inject 'bundle.js' script tag to html template automatically for us
- css-loader: allow webpack to handle css file
- mini-css-extract-plugin: extract css out into external css files
- postcss-loader: prefix and minify css files
Add to the config file
.....
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
....
module : {
rules : [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true, // turn css selectors into hashes
importLoaders: 1, // 1 loader will be applied before css-loader
camelCase: true,
sourceMap: true
}
},
{
loader: "postcss-loader",
options: {
config: {
ctx: {
autoprefixer: {
browsers: "last 2 versions" //only support last 2 versions of browser
}
}
}
}
}
]
}
]
},
plugins : [
new MiniCssExtractPlugin({
filename: "[name].css", // sync chunk
chunkFilename: "[id].css" // async chunk
}),
new HtmlWebpackPlugin({
template: "public/index.html",
//favicon: `public/favicon.ico` // if you have one
}),
]
}
Create postcss.config.js
file and add the following content
module.exports = {
plugins: [require('autoprefixer')]
};
Config webpack-dev-server, use to serve the app without coding the backend
npm install --save webpack-dev-server
Add to the config file
module.exports = {
......
devServer: {
publicPath : "/", // this need to be the same as output.publicPath
host: "localhost", // combine with port, will server your app through localhost:8080
port: 8080,
historyApiFallback: true
},
}
Babel is used to transpile JSX and ES201x js code to older js standard for some old browser Install
npm install --save @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
- babel-core: core dependency for babel
- babel-loader: using babel with webpack
- babel-preset-env: transpile ES2015+ code
- babel-preset-react: transpile JSX code
- babel-plugin-transform-class-properties: using class properties in javascript class
- babel-plugin-transform-object-rest-spread: handle object spread operator
Create .babelrc
in your project root directory, this is the babel configuration file
//.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-class-properties"]
}
Add rule to webpack.config.js
file
module.exports = {
......
module : {
rules : [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
.....
]
}
}
In webpack 4, optimization.splitChunks
is used instead of CommonsChunkPlugin and is turned on by default in production mode.
To split out the vendor code, add the following config
optimization : {
runtimeChunk : true, // create a chunk containing webpack runtime coode
splitChunks : {
cacheGroups : {
vendors : {
test : /[\\/]node_modules[\\/]/, // get all modules in node_modules
priority: -10,
chunks : "all",
name : "vendor"
}
}
}
},
Give each chunk and module a name instead of numeric id, which might break your long term caching (different id for the same chunk) This is turned on by default in development mode
optimization : {
namedModules : true,
namedChunks : true,
},
Since you will change your code during development, sometime webpack outputted files to dist folder is different, and old files remained in the folder, we want to clean that up, so we will use Clean-webpack-plugin
Install
npm install --save clean-webpack-plugin
In webpack.config.js
:
.....
const CleanWebpackPlugin = require("clean-webpack-plugin");
....
module.exports = {
.....
plugins : [
new CleanWebpackPlugin(path.resolve(__dirname, "./dist")),
]
}
This section is about processing images through webpack so that it is "responsive" Install
npm install --save responsive-loader sharp
In webpack.config.js
module.exports = {
......
module : {
rules : [
.....
{
test: /\.(jpe?g|png)$/i,
loader: 'responsive-loader',
options: {
sizes: [360, 800, 1200, 1400], // the width of the output images, you should adapt to your app
placeholder: true,
adapter: require('responsive-loader/sharp'),
name: './assets/images/[hash]-[width].[ext]'
}
}
]
}
}
This section is about dealing with legacy modules with rely on global variable we will use webpack built-in ProvidePlugin
In webpack.config.js
module.exports = {
.....
plugins : [
.....
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
]
}
For example, the above config will import jquery to global context so that you and legacy library can use it with import it
Usually, you won't hard-code directly some variable such as REST endpoint, auth key, you will provide it through environment variable
In backend, you can achieve this with .env
file and dot-env package, but in frontend, you also need to pass it through webpack by DefinePlugin
In webpack.config.js
module.exports = {
.....
plugins : [
.....
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL),
})
]
}
This config is depend on the host/platform you use, so the above code is just an example.
In webpack 4, minify js is enabled by default in production mode, so no need for configuration. If you want to tweak some config, then Install plugin
npm install --save uglifyjs-webpack-plugin
Add in webpack.config.js
....
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
......
optimization : {
minimizer : [
new UglifyJsPlugin({
sourceMap : true,
cache : true
})
]
}
}
For running cli command easier, we could add some to npm script
For example, in package.json
"scripts" : {
"build": "webpack --config webpack.prod.config.js",
"build-dev" : "webpack-dev-server"
}
For the code to be interpreted correctly by webpack and babel, add the following babel plugins and config Install
npm install --save babel-plugin-syntax-dynamic-import
In .babelrc
plugins : ["syntax-dynamic-import"],
comments : true
comments : true so that babel doesn't strip away comment like /* webpackChunkName : foo */
- Nguyen Duc Anh - Dauto98
Some great articles which help me to finish this setup guide
Predictable long term caching with Webpack