The github repo, react-webpack, has the minimal scaffolding for a React web app with Webpack 4. The tutorial explains how the project was built.
New project
Create a new project
yarn init
Install Webpack as devDependency.
yarn add webpack webpack-cli webpack-dev-server --dev
Create Webpack configuration file:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/'
}
}
Add two scripts to package.json
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
}
Place an index.js file in src folder which serves as input to the build process.
console.log('Hello world');
Run the build command. This creates a dist folder with bundle.js file.
Next, we will create React components and process it using babel.
Processing React components
Install React.
yarn add react react-dom
Create two folders, src and dist. The src folder contains our app. And dist folder contains the output of the build process. In src folder, we create the index.js file, the entry point to the build process.
import React from 'react';
import ReactDOM from 'react-dom';
import Home from './Home';
ReactDOM.render(<Home />, document.getElementById('root'));
React DOM renders the Home component in the div element with id root. Create a new file, Home.js.
import React, { Component } from 'react';
export default class Home extends Component {
render() {
return (
<h1>Hello world</h1>
);
}
}
Babel transpiles ES2015+ code to JavaScript 5. It also has a react preset to convert JSX code in React to plain JavaScript. Add babel packages as devDependency.
yarn add @babel/core babel-loader @babel/preset-env @babel/preset-react --dev
In the build configuration file, use the babel-loader to process JSX files. In webpack config, there are rules to specify how to process a module (file). Specify a rule to process JS files.
module: {
rules: [
{
test: /.js$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
]
}
There are two babel presets. To convert new JS syntax to browser compatible JS syntax, we use env preset. To convert React components in JSX to plain JavaScript, we use react preset.
Add an index.html file to src folder.
<html>
<head>
<title>React with Webpack</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="root"></div>
</body>
</html>
There is a HTML webpack plugin. This plugin moves the HTML file to the output folder and inserts script tags for the output bundle. Install the plugin.
yarn add html-webpack-plugin --dev
In webpack.config, add a require statement at the top.
const HtmlWebpackPlugin = require('html-webpack-plugin');
Add a new plugins config section below the module config.
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
Do a build. In the dist folder, there is a new index.html file. Run the start command. Webpack spins a new devServer. Browse to localhost:8080 and view the webapp. You should see the Hello world text prominently.
yarn build
yarn start
Process styles
We will continue our exploration of Webpack. In Home.js, add a className to the div tag.
import React, { Component } from 'react';
export default class Home extends Component {
render() {
return (
<div className="container">
<h1>Hello world</h1>
</div>
);
}
}
Create a new file, home.scss.
.container {
min-height: 100vh;
background-color: #eee;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column
}
Add an import statement to the top of Home.js
import './home.scss';
To process scss file, we need more loaders.
The container has the background image. The background image is available in the images folder placed along the src
folder.
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
These loaders process the module (scss file) from right to left. sass-loader
takes the help of node-sass
package to convert scss syntax to css syntax understood by browsers. css-loader
processes the resultant css. style-loader
drops all the styles in the browser in the header element.
yarn add node-sass sass-loader css-loader style-loader --dev
Process images
Download the following image to the project.
Rename the file as trek.jpeg
in a new images folder (peer to src). Add the image to Home.js
.
import Trek from '../images/trek.jpeg';
Insert the image tag below the h1 tag.
<img src={Trek} />
To process images in the build, we need another loader, file-loader
.
yarn add file-loader --dev
Add a new rule to process images.
{
test: /\.(jpg|jpeg|png|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}
]
}
The file-loader
emits the image file in the output folder. By default, it will emit the file in the output folder as [hash].[ext]
to maintain uniqueness. We can override the behaviour by providing a name option. With the option specified above, the file will be emitted in the output folder with its original name and path.
Start the webpack server and open localhost:8080.
Next, we will explore how hot reloading works.
Hot reloading
By default, Webpack does live reloading. When the contents of the app changes, the page reloads. For a good development experience, the page should not reload. Rather, only the affected components should be updated. Hot reloading does that. There is a lot of theory behind how hot reloading works. In this tutorial, we will configure our web app so that both styles and javascript are “hot reloaded” without a browser refresh.
Import webpack at the top of webpack config.
const webpack = require('webpack');
Add the HotModuleReplacementPlugin
to the plugins section.
new webpack.HotModuleReplacementPlugin()
Add a devServer config section.
devServer: {
contentBase: path.join(__dirname, 'dist'),
hot: true
}
Webpack is configured with hot reloading. By default, the style-loader
has HMR (hot module replacement) enabled. So, all your style changes will be reflected in the page instantaneously. Next, we enable HMR for React components.
Install react-hot-loader
package.
yarn add react-hot-loader
Add a babel plugin (react-hot-loader/babel) to process JavaScript files.
{
test: /.jsx?$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['react-hot-loader/babel']
}
},
Import hot from the root component (Home.js).
import { hot } from 'react-hot-loader/root'
Wrap the root component with hot.
export default hot(Home);
Additional steps
In addition to the above webpack configuration, I will recommend some more:
- Linting using
eslint-loader
. - Add postcss-loader and autoprefixer to prefix CSS.
- Provide sourcemaps for debugging.
Production configuration
For generating the production build, create a new configuration file, webpack.production.config.js
. We do not need the following from the development config.
- Hot reloading
- DevServer
Use the following config as starting point.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(jpg|jpeg|png|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
};
For production, we won’t use webpack-dev-server
. Instead, we will host the dist folder in any static website hosting provider like Firebase hosting or Netlify or Github pages.
Update the build script to use the production config.
webpack -p --config webpack.production.config.js
Extract CSS
With style-loader
, CSS is injected into a style tag in the head section of the HTML document. For production, the CSS should be in a separate file.
Add mini-css-extract-plugin
yarn add mini-css-extract-plugin --dev
Import the plugin at the top of the config file.
const ExtractPlugin = require('mini-css-extract-plugin');
Add the plugin to the plugins section.
new ExtractPlugin(),
Use the loader for processing SCSS files.
{
test: /\.scss$/,
use: [ExtractPlugin.loader, 'css-loader', 'sass-loader']
},
Do a yarn build. And you should see the following dist folder.
All the CSS is available in main.css. For following the tutorial, please use the Git repo.