React Tooling Part 2
In this section, we will discuss about webpack and the ecosystem around it.
webpack
Modern JS applications usually involved hundreds, if not thousands of files/functions. It would be painful and error-prone if we just include <script>
tags in our html file manually.
This may not seems obvious to us now because currently we have only two components App
and Movie
. However, even with only two components, we need to bear in mind that React
and ReactDOM
are our dependencies and we need to ensure our code only run after they are loaded. Try to move our script tag above the React
and ReactDOM
script tags, and you realize our code no longer works because React code has not loaded yet.
Now imagine you have 50+ components and more than 20 dependencies, and there are some inter-dependencies between those files, manually analyzing and rearranging your script tags would be very painful, if not impossible.
webpack is the tools that solves this problem. It allows us to use JS module system like commonjs (e.g. const React = require('react')
) and ECMAScript Module (e.g. import React from 'react'
) by parsing them and create a single bundled output file. On top of that, webpack has been extended to handle all types of files that involved in a modern web application, e.g. css, images, html etc.
Bundling code with webpack
To use webpack in your project:
-
Install it in your project:
npm install -D webpack webpack-cli
-
Create a folder and call it
src
. Move yourscript.js
into this folder and rename it asindex.js
. -
Add the following npm scripts:
package.json{ "scripts": { //... "build": "webpack --mode=\"development\"" } }
-
Run
npm run build
- webpack will produces a file call
main.js
indist
folder.- webpack will includes some additional code (known as webpackBootstrap). The code is the runtime of webpack to manage your dependencies and perform some magic e.g. lazy-loading (which we will explain later).
- If you update your
index.html
to use script fromdist/main.js
, the code should works as before.
Now that webpack is processing our file, let’s utilize webpack by splitting App
and Movie
into their own module/file:
- Create a file alongside
index.js
and call itmovie.js
. - Cut and paste the
Movie
component intomovie.js
. - At the bottom of the
movie.js
, add the following statement:export default Movie;
- Create another file and call it
app.js
. - Cut and paste the
App
component intoapp.js
. - At the top of the
app.js
, add the following statement:import Movie from './movie';
- At the bottom of the
app.js
, add the following statement:export default App;
- All
App
andMovie
code should be removed fromindex.js
now. Add the following statement at the top ofindex.js
:import App from './app';
Functionally, our code still works the same. However, now we organize our code with modules and dependencies between them are managed by webpack.
import
statement is used to include code that the module depends on. For our example above:app.js
depends onmovie.js
index.js
depends onapp.js
export
statement is used to declare the code that is exposed to other modules.- there are two types of
export
statement, i.e. default export and named export. - We use default export in our files currently. You can read more about them here.
- there are two types of
Exercise
- Install webpack and configure build script based on the instruction above.
- Verify that your code still works as before.
- (Bonus) use named exports for
movie.js
.
Commit: 030-webpack-app-code
Bundling external dependencies
Now webpack is managing dependencies between our codes (index.js
, app.js
and movie.js
), let’s take one step further to utilize it to manage the our dependencies to React and ReactDOM.
-
Install React and ReactDOM:
npm install react react-dom
. Note that there is no-D
flag as we need React and ReactDOM in our application code, not tools during development/building. -
At the top of your
index.js
, add the following statements:src/index.jsimport * as React from 'react'; import ReactDOM from 'react-dom';
-
At the top of
app.js
andmovie.js
, add the following statement:import * as React from 'react';
-
Remove the unpkg script tags for React and ReactDOM in your
index.html
. -
Run
npm run build
again. Verify that your code still works as before.
Exercise
- Install React and ReactDOM as the dependencies of the project.
- Bundle them together by including changes as described above.
- Verify that your code still works as before.
Commit: 040-webpack-dependencies
webpack-dev-server and html-webpack-plugin
webpack-dev-server
is a webpack feature that would ease your development by starting up a web server to serve your code. We will use it with html-webpack-plugin
so that even our index.html
will be managed by webpack.
-
install required packages as devDependencies:
npm i -D webpack-dev-server html-webpack-plugin
-
create a file
webpack.config.js
with the following content.webpack.config.js
is the file that allows you to customize behavior of webpack. There are many configurations that you can do here, e.g. specify the entry points (if you entry point is notsrc/index.js
) or the output folder (if you want to put the output in the folder with name other thandist
).webpack.config.jsconst HtmlWebpackPlugin = require('html-webpack-plugin'); /** * @type {import('webpack').Configuration} */ module.exports = { devServer: { port: 9200, }, plugins: [ new HtmlWebpackPlugin({ template: 'index.html', }), ], };
-
remove
<script src="dist/main.js"></script>
fromindex.html
because webpack will help you to inject it. -
add a
start
npm script:package.json{ "scripts": { ... "start": "webpack-dev-server --mode=\"development\" --open" } }
-
run
npm start
(npm run start
works too). This should open your default browser with the url http://localhost:9200/. -
try modify your code now. webpack will help you to refresh browser.
Bundling for production
You may realize that the current output file of webpack has tons of comments and spaces, which is good for us to read and understand what it is doing, but bad to be included to production as those comments are not useful for our customers/users.
To create a production bundle for our app:
- Add the following npm script in
package.json
:package.json"build:prod": "webpack --mode=\"production\""
- run
npm run build:prod
Exercise
- configure webpack-dev-server as described and run it. Verify it recompile and refresh your browser when you make change to your code.
- configure build:prod npm scripts as described above. Verify the produced
main.js
is minified, and the file size is much smaller now.
Commit: 050-webpack-dev-server
Other features of webpack
Note that webpack is much more powerful than I’ve described above. The following common use cases of webpack are omitted here, but you should have a read after this workshop.
style-loader
andcss-loader
: allow you to import.css
file in your javascript file, which will be injected as<script>
tag in html.- Hot Module Replacement used in conjunction with
react-hot-loader
allows you to tweak React components while maintaining state. dotenv-webpack
allows you to define environment variables. This is very commonly used to set environment specific variables, e.g. API endpoints, variables to set logging behavior etc.webpack-merge
allows you to merge webpack configurations. This is useful when you have multiple webpack configurations for different purpose (development optimized for compilation time, production optimized for runtime performance), and this package allows you to merge webpack configurations instead of copy-pasting the configurations.
Babel
- In our current code, we use new JS feature/syntax like arrow-function,
const
andclass
. However, only modern browser recognize those JS syntax - it will cause parsing error in older browser like IE 11. You can confirm this by opening http://localhost:9200/ using IE. - Babel is the tool that allow us to write modern JS while maximizing backward compatibility.
- Babel is a compiler that compile your modern JS code (usually called ES2015+, or ES6+) to the older JS code.
- You can see Babel in action at https://babeljs.io/repl.
Compiling code with Babel
-
Babel allows few usage options, e.g. as a CLI tool or plugin to other build system.
-
Since we are using webpack, we will use
babel-loader
to integrate babel into our project. -
To include Babel as part of our webpack bundling process:
- Install required packages:
npm install -D @babel/core @babel/preset-env babel-loader
- Create a
.babelrc
file with the following content. This file tells Babel what transformation to perform..babelrc{ "presets": ["@babel/preset-env"] }
- Create a
.browserlistrc
file with the following content. This specifies what browser you supports.@babel/preset-env
use this information to decide what syntax it needs to transform and what can be leave it as-is..browserlistrc> 1% not ie <= 8
- Update
webpack.config.js
file by addingmodule.rules
section, like the following content. This configuration tells webpack to run through all file ends with.js
or.jsx
extension through thebabel-loader
.webpack.config.jsmodule.exports = { module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: ['babel-loader'], }, ], }, devServer: { port: 9200, }, plugins: [ new HtmlWebpackPlugin({ template: 'index.html', }), ], };
- Terminate your webpack-dev-server if it’s running by Ctrl+C. Run
npm start
start it again. This is because configuration change will not be picked up by webpack-dev-server while it is running. - Open http://localhost:9200/ with IE and verify your code should works now.
- Install required packages:
Exercise
- Configure Babel as described above.
- Restart webpack-dev-server and verify that the application works in IE.
Commit: 060-babel-integration
Cleaning Up
As our code has been reorganized, with introduction of webpack, let’s update our npm scripts accordingly. Update format
and lint
script in package.json
:
{
"scripts": {
...
"format": "prettier --write src/**/*.{js,jsx}",
"lint": "eslint src/**/*.{js,jsx} --quiet"
}
}