In this I will layout the steps needed to create your react app from scratch using webpack. I won't explain everything in detail, however I'll try to give you an understanding of concepts seperately and how you may use them. These steps assume that you have Node setup and atleast some bare bone knowledge of React.
React.Js
WebPack
WebPack & React
Dev Server
Webpack Loaders
Hot Reloading
Building For Production
Let's start with React and see how it works as a library. Follow the steps below.
-
Create a folder with your
project_name
&& cd into your project directory -
Run
npm init -y
to add a package.json file -
Run
npm i react react-dom
Note:
package-lock.json
will be created these enable new developers to get the same version as everyone else. -
Create a folder named dist at the root and add an index.hml file then add a script tag pointing to this React file in the node_modules just before the closing
</body>
tag. Should look like thisNote:
<script src="../node_modules/react/umd/react.development.js"></script>
-
Just below it add another script tag to create a element. Takes three args: an HTML tag name, props and children.
<script> console.log(React.createElement('h1', null, 'Hello from react!')) </script>
-
After reload this, it prints a Js object in the
clg
, can't be rendered to screen yet. That's what ReactDOM is going to do for us. -
ReactDOM has a render method which takes two arguments: A React Element (which we created earlier) and an HTML element container to wrap our React Element.
-
Just like React, we'll also add a script tag for ReactDOM. It's somehow similar
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
-
Add a
<div />
tag to thebody
and give it an id="root". This is ReactDOM render's second argument. -
So lets mashup React and ReactDOM to render some something on the screen.
<script>
ReactDOM.render(React.createElement('h1', null, 'Hello from react!'), document.getElementById("root"))
</script>
- Reload the screen and voila!!
We'll focus on
Webpack bundles all our js into static files which can easily be run by the browser while also minifing the code for optimization.
-
Create a new folder named src at the root of the project. We intend to bundle all our js in this src folder and inject it into the index.html
-
Install webpack as a dev-dependency
npm install webpack --save-dev
-
Lets put webpack to a test, add an
index.js
file to the src folder. Add the following logconsole.log("Hello Webpack")
. In your terminal enter the following commandnode_modules/.bin/webpack --mode=development src/index.js
. This command is divided into three- node_modules/.bin/webpack - Running webpack
- --mode=development - specify development mode
- src/index.js - specify what we are bundling
After running this command we will notice that
dist/main.js
is created at the root directory.dist/main.js
contains our bundled code, to run it simply require it with the<script />
tag inside ourdist/index.html
. Refresh and check your console "hello Webpack". An example to bundle multiple files, create another file insrc/
named maybeindex2.js
add any console.log statement, then require it insideindex.js
(addrequire(index2.js)
at the top of the file) then rerun the commandnode_modules/.bin/webpack --mode=development src/index.js
, refresh and check the console.
Now that we have seen React & Webpack work seperately, let's mash them up.
- Empty
src/index.js
and copy the React code from the script tag indist/index.html
tosrc/index.js
. Also remove all script tagsdist/index.html
except one with pathdist/main.js
. You may also delete index2.js if you created it. - At the top of
src/index.js
require React and ReactDOM i.e.
const React = require('react');
const ReactDOM = require('react-dom');
Configurations
- We won't have to run that webpack command all the time so lets automate.
- First we need an entry and output file. Entry file is that which requires all the other files (also files that require others and so on) and the output file is where our bundled code goes. We already have these files; entry -
src/index.js
, output -dist/main.js
- Create our configuration file a
webpack.config.js
at the root of the project and add.
module.exports = {
mode: 'development',
entry: __dirname + '/src/index.js',
output: {
path: __dirname + '/dist',
filename: 'main.js',
publicPath: '/',
}
}
we have extracted variables from our inline command (
node_modules/.bin/webpack --mode=development src/index.js
) and added them to our configuration.
- To run our command all we need is webpack command
node_modules/.bin/webpack
It may get tiring to reload the page manually whenever a change is made. DevServer makes it possible for our application to automatically reload when changes are to our code.
- First we'll install the dev server
npm install webpack-dev-server --save-dev
. - Add this to our
webpack.config.js
configuration file before the closing brace
devServer: {
contentBase: './dist',
historyApiFallback: true,
inline: true
}
contentBase - specifies the folder to serve
historyApiFallback - makes it possible for an SPA to have navigation with multiple pages
inline - Automatically refresh the page on file changes
- Add
"start": "node_modules/.bin/webpack-dev-server --open"
to package.jsonscript
. - Run
npm run start
and modify code - notice auto reloading
Babel loader - We will need to write ES6 as opposed to ES5 (what we've been writing). Unfortunately not all browsers support ES6, to enable this support we'll need a transpiler to convert our written ES6 to ES5 which is understandable by the other browsers. This is where babel comes in. We are going to add babel to our webpack build process so that we can transpile our code before bundling.
-
Firstly, run
npm i @babel/core babel-loader @babel/preset-env @babel/preset-react babel-plugin-transform-class-properties -D
to install the babel and other plugins.- babel-loader- is the most important among the bunch, it gets the code transpile it then pass it for bundling.
- babel-plugin-transform-class-properties- enables us to use arrow functions outside render and also limit the reference scope of
this
to the App not global. In other words prevents us from usingbind(this)
every where
- babel-loader- is the most important among the bunch, it gets the code transpile it then pass it for bundling.
-
Add a module
object
with arules
array to our webpack.config.js. In the rules array is where we add an object to specify the loader configurations.
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['@babel/preset-react', '@babel/preset-env'],
plugins: ['transform-class-properties']
}
}
]
},
test: Specifies the types of files to be transpiled
exclude: What should be skipped
loader: What our loader is called
query: What presets will be used by the loader
- Run
npm run start
, if there are no errors - lets proceed add React code - In our
src/index.js
change theReact.createElement(...)
to<h1>...</h1>
. also convert the require statements to import statements. - From now on you are free to use React.
Imaging reloading our application while preserving the current state and not reloading its entirety. Hot reloading enables you to update specific parts of the UI without reloading everything on the web app.
We'll be using react-hot-loader
Steps to add hot reloading
-
Install react-hot-loader
npm install react-hot-loader
-
Add
react-hot-loader/babel
to babel plugins inwebpack.config.js
to make sure that our hot reloaded files are compiled by babelmodule.exports = { ... module: { rules: [ { ... query: { ... plugins: ['transform-class-properties', 'react-hot-loader/babel'] } } ] }
-
Make the root component hot exported - this will send a new version of our app to ReactDOM.render when our files change
import React from 'react' import { hot } from 'react-hot-loader/root'; const App = () => { return ( <div> <h1>Hello Webpack</h1> </div> ) } export default hot(App)
-
Finally to turn on hot reloading, add the
--hot
flag to your start script inpackage.json
// package.json "scripts": { ... "start": "node_modules/.bin/webpack-dev-server --open --hot" },
Or add hot: true
to devServer in you webpack.config.js
// webpack.config.js
devServer: {
...
hot: true
},
- Hot reloading should be working now. Try editing the text, you notice that it will update without reloading the page.
But that's not all, because it does not support React Hooks. Lets add that support. We'll use @hot-loader/react-dom
to replace react-dom because it has support for hot loading. Here is how.
-
Install the package
npm i @hot-loader/react-dom -D
-
Add it to webpack alias in
webpack.config.js
module.exports = { ... resolve: { alias: { 'react-dom': '@hot-loader/react-dom', }, } }
After this Hot reloading should be working fine with React Hooks. >For more check the documentation
We've so far been working with development, now let's focus on deployment to production. When deploying we need to focus on the bare minimum of files required by the remote server to run our app.
- An index.html file (Minified)
- A CSS file (Minified)
- A Javascript file (Minified)
- All image assets
- An asset manifest
Firstly, we'll use webpack to minify the index.html file and automatically add the appropriate style links and script tags, then add it to a build folder. To do this make a copy of our webpack.config.js
file, name the new copy webpack.config.prod.js
, as you might have already guessed this will contain our webpack configurations for production. We are going to make a few tweaks to this file since it contains things that may not be needed for production such as the dev-server and hot-reloading.
- Remove all things concerning the devServer and hot-reloading i.e. the devServer key and it's object value, also the
react-hot-loader/babel
value in the babel plugins array. - Since we are using
build
this time we'll change the pathname in output to__dirname + '/dist'
and optionally filename tobundle.js
- Set the environment to production to avoid react warnings. require
webpack
and add this to the plugins array at the root our webpack.config.prod.js.
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
]
- Run
npm i html-webpack-plugin -D
to installhtml-webpack-plugin
which will make the html minification for us. - Require
html-webpack-plugin
in ourwebpack.config.prod.js
and add this below to our plugins array at the root of the object.
new HtmlWebpackPlugin({
inject: true,
template: __dirname + "/dist/index.html",
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
inject
- simply inserts a script tag inside out minified code. as we'll see
template
- Points to what we are minifying
minify
- minification specifications
-
Let's see how it works. first, change the build command in the
package.json
to"webpack --config webpack.config.prod.js"
. The--config
fag specifies the configuration file to use. Runnpm run build
to see what happens.You'll notice a directory
build/index.html
created, When you openindex.html
. you'll the minified code however the problem is that it has two identical script tags. This is because of HtmlWebpackPlugin's inject key. To remove this problem, head back to yourdist/index.html
and delete the script tag, since as we already noticed one is injected for us automatically. Also copy our HtmlWebpackPlugin towebpack.config.js
but without theminify
key then rerunnpm run build
.
If you haven't noticed already your app can't render css. Let's enable this.
- Run
npm i css-loader style-loader -D
check the documentation for more here - Add this to
rules
in both webpack config files underbabel-loader
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
> What happens here is that the loader grab our css files and inject there into our javascript
Now to our javscript in bundle.js, you'll notice that it spans a number of lines. We are going to limit it to only one line to make it faster. To achieve this, we are going to npm i uglifyjs-webpack-plugin -D
, require it and then this to the root of out object
optimization: {
minimizer: [
new UglifyJsPlugin({
sourceMap: true,
uglifyOptions: {
warnings: false,
output: {
comments: false,
},
},
}),
],
},
- Run
npm run build
, you'll notice a single line of js in bundle.js
We want to make sure that our assets are copied from dist
to build
, to make sure they are accessible
- Let's run
npm i file-loader -D
to install the file-loader - Add this to our webpack files
{
test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader',
}
File-loader only deals with files required by Javascript, but how about those require by our index.html
like the favicon. To achieve this we'll not use webpack but javascript.
- Create a folder
scripts
at the root of the project and a filecopy_scripts.js
- Run
npm i fs-extra
to install fs-extra, require it in ourcopy_scripts.js
- Add a script to copy all files in
dist
exceptindex.html
tobuild
.copy_assets.js
should look like this
const fs = require('fs-extra');
fs.copySync('dist', 'build', {
dereference: true,
filter: file => file !== 'dist/index.html'
})
- Change build in
package.json
tonode scripts/copy_assets.js && webpack --config webpack.config.prod.js
and runnpm run build
Add an Asset Manifest We want to list all static assets that we are generating, this will be useful when we start caching them to save load times.
- Run
npm i webpack-manifest-plugin -D
, require webpack-manifest-plugin and then use it in our plugins
new ManifestPlugin({
fileName: 'asset-manifest.json',
})
- Run
npm run build
an asset-manifest.json will be created in build. openingbuild/index.html
will produce the same outcome as when you runnpm run start