CHAPTER-7
Using Webpack with Create React App
In most of our earlier projects, we loaded React with script tags in our apps’ index.html files:
<script src='vendor/react.js'></script>
<script src='vendor/react-dom.js'></script>
Because we’ve been using ES6, we’ve also been loading the Babel library with script tags:
<script src='vendor/babel-standalone.js'></script>
With this setup we’ve been able to load in any ES6 JavaScript file we wanted in index.html, specifying that its type is text/babel:
<script type='text/babel' src='./client.js'></script>
Babel would handle the loading of the file, transpiling our ES6 JavaScript to browserready ES5 JavaScript.
If you need a refresher on our setup strategy so far, we detail it in Chapter 1.
We began with this setup strategy because it’s the simplest. You can begin writing React components in ES6 with little setup.
However, this approach has limitations. For our purposes, the most pressing limitation is the lack of support for JavaScript modules.
JavaScript modules
We saw modules in earlier apps. For instance, the time tracking app had a Client module. That module’s file defined a few functions, like getTimers(). It then set window.client to an object that “exposed” each function as a property. That object looked like this:
// `window.client` was set to this object
// Each property is a function
{
getTimers,
createTimer,
updateTimer,
startTimer,
stopTimer,
deleteTimer,
};
This Client module only exposed these functions. These are the Client module’s public methods. The file public/js/client.js also contained other function definitions, like checkStatus(), which verifies that the server returned a 2xx response code. While each of the public methods uses checkStatus() internally, checkStatus() is kept private. It is only accessible from within the module.
That’s the idea behind a module in software. You have some self-contained component of a software system that is responsible for some discrete functionality. The module exposes a limited interface to the rest of the system, ideally the minimum viable interface the rest of the system needs to effectively use the module.
In React, we can think of each of our individual components as their own modules. Each component is responsible for some discrete part of our interface. React components might contain their own state or perform complex operations, but the interface for all of them is the same: they accept inputs (props) and output their DOM representation (render). Users of a React component need not know any
of the internal details.
In order for our React components to be truly modular, we’d ideally have them live in their own files. In the upper scope of that file, the component might define a styles object or helper functions that only the component uses. But we want our component-module to only expose the component itself.
Until ES6, modules were not natively supported in JavaScript. Developers would use a variety of different techniques to make modular JavaScript. Some solutions only work in the browser, relying on the browser environment (like the presence of window). Others only work in Node.js.
Browsers don’t yet support ES6 modules. But ES6 modules are the future. The syntax is intuitive, we avoid bizarre tactics employed in ES5, and they work both in and outside of the browser. Because of this, the React community has quickly adopted ES6 modules.
If you look at time_tracking_app/public/js/client.js, you’ll get an idea of how strange the techniques for creating ES5 JavaScript modules are.
However, due to the complexity of module systems, we can’t simply use ES6’s import/export syntax and expect it to “just work” in the browser, even with Babel. More tooling is needed.
For this reason and more, the JavaScript community has widely adopted JavaScript bundlers. As we’ll see, JavaScript bundlers allow us to write modular ES6 JavaScript that works seamlessly in the browser. But that’s not all. Bundlers pack numerous advantages. Bundlers provide a strategy for both organizing and distributing web apps. They have powerful toolchains for both iterating in development and producing production-optimized builds.
While there are several options for JavaScript bundlers, the React community’s favorite is Webpack.
However, bundlers like Webpack come with a significant trade-off: They add complexity to the setup of your web application. Initial configuration can be difficult and you ultimately end up with an app that has more moving pieces.
In response to setup and configuration woes, the community has created loads of boilerplates and libraries developers can use to get started with more advanced React apps. But the React core team recognized that as long as there wasn’t a core team sanctioned solution, the community was likely to remain splintered. The first steps for a bundler-powered React setup can be confusing for novice and experienced developers alike.
The React core team responded by producing the Create React App project.
Create React App
The create-react-app⁶⁵ library provides a command you can use to initiate a new Webpack-powered React app:
$ create-react-app my-app-name
The library will configure a “black box” Webpack setup for you. It provides you with the benefits of a Webpack setup while abstracting away the configuration details.
Create React App is a great way to get started with a Webpack-React app using standard conventions. Therefore, we’ll use it in all of our forthcoming WebpackReact apps.
In this chapter, we’ll:
- See what a React component looks like when represented as an ES6 module
- Examine the setup of an app managed by Create React App
- Take a close look at how Webpack works
- Explore some of the numerous advantages that Webpack provides for both
development and production use - Peek under the hood of Create React App
- Figure out how to get a Webpack-React app to work alongside an API
The idea of a “black box” controlling the inner-workings of your app might be scary. This is a valid concern. Later in the chapter, we’ll explore a feature of Create React App, eject, which should hopefully assuage some of this fear.
Exploring Create React App
Let’s install Create React App and then use it to initialize a Webpack-React app. We can install it globally from the command line using the -g flag. You can run this command anywhere on your system:
$ npm i -g create-react-app@1.4.1
The @1.4.1 above is used to specify a version number. We recommend using this version as it is the same version we tested the code in this book with.
Now, anywhere on your system, you can run the create-react-app command to initiate the setup for a new Webpack-powered React app.
Let’s create a new app. We’ll do this inside of the code download that came with the book. From the root of the code folder, change into the directory for this chapter:
$ cd webpack
That directory already has three folders:
$ ls
es6-modules/
food-lookup/
heart-webpack-complete/
The completed version of the code for this next section is available in heart-webpack-complete. Run the following command to initiate a new React app in a folder called heart-webpack:
$ create-react-app heart-webpack --scripts-version=1.0.14
This will create the boilerplate for the new app and install the app’s dependencies. This might take a while.
The –scripts-version flag above is important. We want to ensure your react-scripts version is the same as the one we’re using here in the book. We’ll see what the react-scripts package is in a moment.
When Create React App is finished, cd into the new directory:
$ cd heart-webpack
$ ls
README.md
node_modules/
package.json
public/
src/
Inside src/ is a sample React app that Create React App has provided for demonstration purposes. Inside of public/ is an index.html, which we’ll look at first.
public/index.html
Opening public/index.html in a text editor:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
... comment omitted ...
-->
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
... comment omitted ...
-->
</body>
</html>
The stark difference from the index.html we’ve used in previous apps: there are no script tags here. That means this file is not loading any external JavaScript files. We’ll see why this is soon.
package.json
Looking inside of the project’s package.json, we see a few dependencies and some script definitions:
{
"name": "heart-webpack",
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-scripts": "1.1.1",
"concurrently": "3.4.0"
},
"dependencies": {
"react": "16.12.0",
"react-dom": "16.12.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Let’s break it down.
react-scripts
package.json specifies a single development dependency, react-scripts:
"devDependencies": {
"react-scripts": "1.1.1",
"concurrently": "3.4.0"
Create React App is just a boilerplate generator. That command produced the
folder structure of our new React app, inserted a sample app, and specified our
package.json. It’s actually the react-scripts package that makes everything work.
react-scripts specifies all of our app’s development dependencies, like Webpack
and Babel. Furthermore, it contains scripts that “glue” all of these dependencies
together in a conventional manner.
Create React App is just a boilerplate generator. The react-scripts package, specified in package.json, is the engine that will make everything work.
Even though react-scripts is the engine, throughout the chapter we’ll continue to refer to the overall project as Create React App.
react and react-dom
Under dependencies, we see react and react-dom listed:
},
"dependencies": {
"react": "16.12.0",
"react-dom": "16.12.0"
In our first two projects, we loaded in react and react-dom via script tags in index.html. As we saw, those libraries were not specified in this project’s index.html.
Webpack gives us the ability to use npm packages in the browser. We can specify external libraries that we’d like to use in package.json. This is incredibly helpful. Not only do we now have easy access to a vast library of packages. We also get to use npm to manage all the libraries that our app uses. We’ll see in a bit how this all works.
Scripts
package.json specifies four commands under scripts. Each executes a command with react-scripts. Over this chapter and the next we’ll cover each of these commands in depth, but at a high-level:
- start: Boots the Webpack development HTTP server. This server will handle requests from our web browser.
- build: For use in production, this command creates an optimized, static bundle of all our assets.
- test: Executes the app’s test suite, if present.
- eject: Moves the innards of react-scripts into your project’s directory. This enables you to abandon the configuration that react-scripts provides, tweaking the configuration to your liking.
For those weary of the black box that react-scripts provides, that last command is comforting. You have an escape hatch should your project “outgrow” react-scripts or should you need some special configuration.
In a package.json, you can specify which packages are necessary in which environment. Note that react-scripts is specified under
devDependencies.
When you run npm i, npm will check the environment variable NODE_ENV to see if it’s installing packages in a production environment. In production, npm only installs packages listed under dependencies (in our case, react and react-dom). In development, npm installs all packages. This speeds the process in production, foregoing the installation of unneeded packages like linters or testing libraries.
Given this, you might wonder: Why is react-scripts listed as a development dependency? How will the app work in a production environment without it? We’ll see why this is after taking a look at how Webpack prepares production builds.
src/
Inside of src/, we see some JavaScript files:
$ ls src
App.css
App.js
App.test.js
index.css
index.js
logo.svg
Create React App has created a boilerplate React app to demonstrate how files can be organized. This app has a single component, App, which lives inside App.js.
App.js
Looking inside src/App.js:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to relo\
ad.
</p>
</div>
);
}
}
export default App;
There are a few noteworthy features here.
The import statements
We import React and Component at the top of the file:
import React, { Component } from 'react';
This is the ES6 module import syntax. Webpack will infer that by ‘react’ we are referring to the npm package specified in our package.json.
If ES6 modules are new to you, check out the entry in “Appendix B.”
The next two imports may have surprised you:
import logo from './logo.svg';
import './App.css';
We’re using import on files that aren’t JavaScript! Webpack has you specify all your dependencies using this syntax. We’ll see later how this comes into play. Because the paths are relative (they are preceded with ./), Webpack knows we’re referring to local files and not npm packages.
App is an ES6 module
The App component itself is simple and does not employ state or props. Its return method is just markup, which we’ll see rendered in a moment.
What’s special about the App component is that it’s an ES6 module. Our App component lives inside its own dedicated App.js. At the top of this file, it specifies its dependencies and at the bottom it specifies its export:
export default App;
Our React component is entirely self-contained in this module. Any additional libraries, styles, and images could be specified at the top. Any developer could open this file and quickly reason what dependencies this component has. We could define helper functions that are private to the component, inaccessible to the outside.
Furthermore, recall that there is another file in src/ related to App besides App.css:
App.test.js. So, we have three files corresponding to our component: The component itself (an ES6 module), a dedicated stylesheet, and a dedicated test file.
Create React App has suggested a powerful organization paradigm for our React app. While perhaps not obvious in our single-component app, you can imagine how this modular component model is intended to scale well as the number of components grows to the hundreds or thousands.
We know where our modular component is defined. But we’re missing a critical piece: Where is the component written to the DOM?
The answer lies inside src/index.js.
index.js
Open src/index.js now:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);
Stepping through this file, we first import both react and react-dom. Because we specified App as the default export from App.js, we can import it here. Note again that the relative path (./App) signals to Webpack that we’re referring to a local file, not an npm package.
At this point, we can use our App component just as we have in the past. We make a call to ReactDOM.render(), rendering the component to the DOM on the root div. This div tag is the one and only div present in index.html.
This layout is certainly more complicated than the one we used in our first couple of projects. Instead of just rendering App right below where we define it, we have another file that we’re importing App into and making the ReactDOM.render() call in. Again, this setup is intended to keep our code modular. App.js is restricted to only defining a React component. It does not carry the additional responsibility of rendering that component. Following from this pattern, we could comfortably import and render this component anywhere in our app.
We now know where the ReactDOM.render() call is located. But the way this new setup works is still opaque. index.html does not appear to load in any JavaScript.
How do our JavaScript modules make it to the browser?
Let’s boot the app and then explore how everything fits together.
Why do we import React at the top of the file? It doesn’t apparently get referenced anywhere.
React actually is referenced later in the file, we just can’t see it because of a layer of indirection. We’re referencing App using JSX. So this line in JSX:
<App />
Is actually this underneath the JSX abstraction:
React.createElement(App, null);
Booting the app
From the root of heart-webpack, run the start command:
$ npm start
This boots the Webpack development server. We dig into the details of this server momentarily.
The App component is clearly present on the page. We see both the logo and text that the component specifies. How did it get there?
Let’s view the source code behind this page. In both Chrome and Firefox, you can type view-source:http://localhost:3000/ into the address bar to do so:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<!--
... comment omitted ...
-->
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
... comment omitted ...
-->
<script type="text/javascript" src="/static/js/bundle.js"></script></\
body>
</html>
This index.html looks the same as the one we looked at earlier, save for one key difference: There is a script tag appended to the bottom of the body. This script tag references a bundle.js. As we’ll see, the App component from App.js and the ReactDOM.render() call from index.js both live inside of that file.
The Webpack development server inserted this line into our index.html. To understand what bundle.js is, let’s dig into how Webpack works.
This script defaults the server’s port to 3000. However, if it detects that 3000 is occupied, it will choose another. The script will tell you where the server is running, so check the console if it appears that it is not on http://localhost:3000/.
If you’re running OS X, this script will automatically open a browser window pointing to http://localhost:3000/.
Webpack basics
In our first app (the voting app), we used the library http-server to serve our static assets, like index.html, our JavaScript files, and our images.
In the second app (the timers app), we used a small Node server to serve our static assets. We defined a server in server.js which both provided a set of API endpoints and served all assets under public/. Our API server and our static asset server were one in the same.
With Create React App, our static assets are served by the Webpack development server that is booted when we run npm start. At the moment, we’re not working with an API.
As we saw, the original index.html did not contain any references to the React app. Webpack inserted a reference to bundle.js in index.html before serving it to our browser. If you look around on disk, bundle.js doesn’t exist anywhere. The Webpack development server produces this file on the fly and keeps it in memory. When our browser makes a request to localhost:3000/, Webpack is serving its modified version of index.html and bundle.js from memory.
From the page view-source:http://localhost:3000/, you can click on /static/js/bundle.js to open that file in your browser. It will probably take a few seconds to open. It’s a gigantic file.
bundle.js contains all the JavaScript code that our app needs to run. Not only does it contain the entire source for App.js — it contains the entire source for the React library!
You can search for the string ./src/App.js in this file. Webpack demarcates each separate file it has included with a special comment. What you’ll find is quite messy:
If you do a little hunting, you can see recognizable bits and pieces of App.js amidst the chaos. This is indeed our component. But it looks nothing like it.
Webpack has performed some transformation on all the included JavaScript. Notably, it used Babel to transpile our ES6 code to an ES5-compatible format.
If you look at the comment header for App.js, it has a number. In the screenshot above, that number was 258:
/* 258 */
/*!********************!*\
!*** ./src/App.js ***!
\********************/
Your module IDs might be different than the ones here in the text.
The module itself is encapsulated inside of a function that looks like this:
function(module, exports, __webpack_require__) {
// The chaotic `App.js` code here
}
Each module of our web app is encapsulated inside of a function with this signature. Webpack has given each of our app’s modules this function container as well as a module ID (in the case of App.js, 258).
But “module” here is not limited to JavaScript modules.
Remember how we imported the logo in App.js, like this:
import logo from './logo.svg';
And then in the component’s markup it was used to set the src on an img tag:
<img src={logo} className="App-logo" alt="logo" />
Here’s what the variable declaration of logo looks like inside the chaos of the App.js Webpack module:
var _logo = __webpack_require__(/*! ./logo.svg */ 259);
This looks quite strange, mostly due to the in-line comment that Webpack provides for debugging purposes. Removing that comment:
var _logo = __webpack_require__(259);
Instead of an import statement, we have plain old ES5 code. What is this doing though?
To find the answer, search for ./src/logo.svg in this file. (It should appear directly below App.js). The SVG is represented inside bundle.js, too!
Looking at the header for this module:
/* 259 */
/*!**********************!*\
!*** ./src/logo.svg ***!
\**********************/
Note that its module ID is 259, the same integer passed to webpack_require() above.
Webpack treats everything as a module, including image assets like logo.svg. We can get an idea of what’s going on by picking out a path in the mess of the logo.svg module. Your path might be different, but it will look like this:
static/media/logo.5d5d9eef.svg
If you open a new browser tab and plug in this address:
http://localhost:3000/static/media/logo.5d5d9eef.svg
You should get the React logo:
So Webpack created a Webpack module for logo.svg by defining a function. While the implementation details of this function are opaque, we know it refers to the path to the SVG on the Webpack development server. Because of this modular paradigm, it was able to intelligently compile a statement like this:
import logo from './logo.svg';
Into this ES5 statement:
var _logo = __webpack_require__(259);
webpack_require() is Webpack’s special module loader. This call refers to the Webpack module corresponding to logo.svg, number 259. That module returns the string path to the logo’s location on the Webpack development server, static/media/logo.5d5d9eef.svg:
var _logo = __webpack_require__(259);
console.log(_logo);
// -> "static/media/logo.5d5d9eef.svg"
What about our CSS assets? Yep, everything is a module in Webpack. Search for the string ./src/App.css:
Webpack’s index.html didn’t include any references to CSS. That’s because Webpack is including our CSS here via bundle.js. When our app loads, this cryptic Webpack module function dumps the contents of App.css into style tags on the page.
So we know what is happening: Webpack has rolled up every conceivable “module” for our app into bundle.js. You might be asking: Why?
The first motivation is universal to JavaScript bundlers. Webpack has converted all our ES6 modules into its own bespoke ES5-compatible module syntax.
Furthermore, Webpack, like other bundlers, consolidated all our JavaScript modules into a single file. While it could keep JavaScript modules in separate files, having a single file maximizes performance. The initiation and conclusion of each file transfer over HTTP adds overhead. Bundling up hundreds or thousands of smaller files into one bigger one produces a notable speed-up.
Webpack takes this module paradigm further than other bundlers, however. As we saw, it applies the same modular treatment to image assets, CSS, and npm packages (like React and ReactDOM). This modular paradigm unleashes a lot of power. We touch on aspects of that power throughout the rest of this chapter.
With our nascent understanding of how Webpack works, let’s turn our attention back to the sample app. We’ll make some modifications and see first-hand how the Webpack development process works.
Making modifications to the sample app
We’ve been checking out the bundle.js produced by the Webpack development server in our browser. Recall that to boot this server, we ran the following command:
$ npm start
As we saw, this command was defined in package.json:
What exactly is going on here?
The react-scripts package defines a start script. Think of this start script as a special interface to Webpack that contains some features and conventions provided by Create React App. At a high-level, the start script:
- Sets up the Webpack configuration
- Provides some nice formatting and coloring for Webpack’s console output
- Launches a web browser if you’re on OS X
Let’s take a look at what the development cycle of our Webpack-powered React app looks like.
Hot reloading
If the server is not already running, go ahead and run the start command to boot it:
$ npm start
Again, our app launches at http://localhost:3000/. The Webpack development server is listening on this port and serves the development bundle when our server makes a request.
One compelling development feature Webpack gives us is hot reloading. Hot reloading enables certain files in a web app to be hot-swapped on the fly whenever changes are detected without requiring a full page reload.
At the moment, Create React App only sets up hot reloading for CSS. This is because the React-specific hot reloader is not considered stable enough for the default setup.
Hot reloading for CSS is wonderful. With your browser window open, make an edit to App.css and witness as the app updates without a refresh.
For example, you can change the speed at which the logo spins. Here, we changed it from 20s to 1s:
.App-logo {
animation: App-logo-spin infinite 1s linear;
height: 80px;
}
Or you can change the color of the header’s text. Here, we changed it from white to purple:
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: purple;
}
How hot reloading works
Webpack includes client-side code to perform hot reloading inside bundle.js. The Webpack client maintains an open socket with the server. Whenever the bundle is modified, the client is notified via this websocket. The client then makes a request to the server, asking for a patch to the bundle. Instead of fetching the whole bundle, the server will just send the client the code that client needs to execute to “hot swap”
the asset.
Webpack’s modular paradigm makes hot reloading of assets possible. Recall that Webpack inserts CSS into the DOM inside style tags. To swap out a modified CSS asset, the client removes the previous style tags and inserts the new one. The browser renders the modification for the user, all without a page reload.
Auto-reloading
Even though hot reloading is not supported for our JavaScript files, Webpack will still auto-reload the page whenever it detects changes.
With our browser window still open, let’s make a minor edit to src/App.js. We’ll change the text in the p tag:
<p className="App-intro">
I just made a change to <code>src/App.js</code>!
</p>
Save the file. You’ll note the page refreshes shortly after you save and your change is reflected.
Because Webpack is at heart a platform for JavaScript development and deployment, there is an ever-growing ecosystem of plug-ins and tools for Webpack-powered apps.
For development, hot- and auto-reloading are two of the most compelling plugins that come configured with Create React App. In the later section on eject (“Ejecting”), we’ll point to the Create React App configuration file that sets up Webpack for development so that you can see the rest.
For deployment, Create React App has configured Webpack with a variety of plug-ins that produce a production-level optimized build. We’ll take a look at the production build process next.
Creating a production build
So far, we’ve been using the Webpack development server. In our investigation, we saw that this server produces a modified index.html which loads a bundle.js. Webpack produces and serves this file from memory — nothing is written to disk.
For production, we want Webpack to write a bundle to disk. We’ll end up with a production-optimized build of our HTML, CSS, and JavaScript. We could then serve these assets using whatever HTTP server we wanted. To share our app with the world, we’d just need to upload this build to an asset host, like Amazon’s S3.
Let’s take a look at what a production build looks like.
Quit the server if it’s running with CTRL+C. From the command line, run the build command that we saw in package.json earlier:
$ npm run build
When this finishes, you’ll note a new folder is created in the project’s root: build. cd into that directory and see what’s inside:
$ cd build
$ ls
favicon.ico
index.html
static/
If you look at this index.html, you’ll note that Webpack has performed some additional processing that it did not perform in development. Most notably: there are no newlines. The entire file is on a single line. Newlines are not necessary in HTML and are just extra bytes. We don’t need them in production.
Here’s what that exact file looks like in a human readable format:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1" name="viewport">
<link href="/favicon.ico?fd73a6eb" rel="shortcut icon">
<title>React App</title>
<link href="/static/css/main.9a0fe4f1.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script src="/static/js/main.590bf8bb.js" type="text/javascript">
</script>
</body>
</html>
Instead of referencing a bundle.js, this index.html references a file in static/ which we’ll look at momentarily. What’s more, this production index.html now has a link tag to a CSS bundle. As we saw, in development Webpack inserts CSS via bundle.js. This feature enables hot reloading. In production, hot reloading capability is irrelevant. Therefore, Webpack deploys CSS normally.
Webpack versions assets. We can see above that our JavaScript bundle has a different name and is versioned (main..js).
Asset versioning is useful when dealing with browser caches in production. If a file is changed, the version of that file will be changed as well. Client browsers will be forced to fetch the latest version.
Note that your versions (or digests) for the files above may be different.
The static/ folder is organized as follows:
$ ls static
css/
js/
media/
Checking out the folders individually:
$ ls static/css
main.9a0fe4f1.css
main.9a0fe4f1.css.map
$ ls static/js
main.f7b2704e.js
main.f7b2704e.js.map
$ ls static/media
logo.5d5d9eef.svg
Feel free to open up both the .css file and the .js file in your text editor. We refrain from printing them here in the book due to their size.
Be careful about opening these files — you might crash your editor due to their size!
If you open the CSS file, you’ll see it’s just two lines: The first line is all of our app’s CSS, stripped of all superfluous whitespace. We could have hundreds of different CSS files in our app and they would end up on this single line. The second line is a special comment declaring the location of the map file.
The JavaScript file is even more packed. In development, bundle.js has some structure. You can pick apart where the individual modules live. The production build does not have this structure. What’s more, our code has been both minified and uglified. If you’re unfamiliar with minification or uglification, see the aside “Minification, uglification, and source maps.”
Last, the media folder will contain all of our app’s other static files, like images and videos. This app only has one image, the React logo SVG file.
Again, this bundle is entirely self-contained and ready to go. If we wanted to, we could install the same http-server package that we used in the first application and use it to serve this folder, like this:
http-server ./build -p 3000
Without the Webpack development server, you can imagine the development cycle would be a bit painful:
- Modify the app
- Run npm run build to generate the Webpack bundle
- Boot/restart the HTTP server
This is why there is no way to “build” anything other than a bundle intended for production. The Webpack server services our needs for development.
Minification, uglification, and source maps
For production environments, we can significantly reduce the size of JavaScript files by converting them from a human-readable format to a more compact one that behaves exactly the same. The basic strategy is stripping all excess characters, like spaces. This process is called minification.
Uglification (or obfuscation) is the process of deliberately modifying JavaScript files so that they are harder for humans to read. Again, the actual behavior of the app is unchanged. Ideally, this process slows down the ability for outside developers to understand your codebase.
Both the .css and .js files are accompanied by a companion file ending in .map. The .map file is a source map that provides debugging assistance for production builds. Because they’ve been minified and uglified, the CSS and JavaScript for a production app are difficult to work with. If you encounter a JavaScript bug on production, for example, your browser will direct you to a cryptic line of this obscure code.
Through a source map, you can map this puzzling area of the codebase back to its original, un-built form. For more info on source maps and how to use them, see the blog post “Introduction to JavaScript Source Maps.”
Ejecting
When first introducing Create React App at the beginning of this chapter, we noted that the project provided a mechanism for “ejecting” your app.
This is comforting. You might find yourself in a position in the future where you would like further control over your React-Webpack setup. An eject will copy all the scripts and configuration encapsulated in react-scripts into your project’s directory. It opens up the “black box,” handing full control of your app back over to you.
Performing an eject is also a nice way to strip some of the “magic” from Create React App. We’ll perform an eject in this section and take a quick look around.
There is no backing out from an eject. Be careful when using this command. Should you decide to eject in the future, make sure your app
is checked in to source control.
If you’ve been adding to the app inside heart-webpack, you might consider duplicating that directory before proceeding. For example, you can do this:
cp -r heart-webpack heart-webpack-ejected
The node_modules folder does not behave well when it’s moved wholesale like this, so you’ll need to remove node_modules and re-install:
cd heart-webpack-ejected
rm -rf node_modules
npm i
You can then perform the steps in this section inside of heart-webpack-ejected and preserve heart-webpack.
Buckle up
From the root of heart-webpack, run the eject command:
$ npm run eject
Confirm you’d like to eject by typing y and hitting enter.
After all the files are copied from react-scripts into your directory, npm install will run. This is because, as we’ll see, all the dependencies for react-scripts have been dumped into our package.json.
When the npm install is finished, take a look at our project’s directory:
$ ls
README.md
build/
config/
node_modules/
package.json
public/
scripts/
src/
We have two new folders: config/ and scripts/. If you looked inside src/ you’d note that, as expected, it is unchanged.
Take a look at package.json. There are loads of dependencies. Some of these dependencies are necessary, like Babel and React. Others — like eslint and whatwg-fetch — are more “nice-to-haves.” This reflects the ethos of the Create React App project: an opinionated starter kit for the React developer.
Check out scripts/ next:
$ ls scripts
build.js
start.js
test.js
When we ran npm start and npm run build earlier, we were executing the scripts start.js and build.js, respectively. We won’t look at these files here in the book, but feel free to peruse them. While complicated, they are well-annotated with comments. Simply reading through the comments can give you a good idea of what each of these scripts are doing (and what they are giving you “for free”).
Finally, check out config/ next:
$ ls config
env.js
jest/
paths.js
polyfills.js
webpack.config.dev.js
webpack.config.prod.js
react-scripts provided sensible defaults for the tools that it provides. In package.json, it specifies configuration for Babel. Here, it specifies configuration for Webpack and Jest (the testing library we use in the next chapter).
Of particular noteworthiness are the configuration files for Webpack. Again, we won’t dive into those here. But these files are well-commented. Reading through the comments can give you a good idea of what the Webpack development and production pipelines look like and what plug-ins are used. In the future, if you’re ever curious about how react-scripts has configured Webpack in development or
production, you can refer to the comments inside these files.
Hopefully seeing the “guts” of react-scripts reduces a bit of its mysticism. Testing out eject as we have here gives you an idea of what the process looks like to abandon react-scripts should you need to in the future.
So far in this chapter, we’ve covered the fundamentals of Webpack and Create React App’s interface to it. Specifically, we’ve seen:
- How the interface for Create React App works
- The general layout for a Webpack-powered React app
- How Webpack works (and some of the power it provides)
- How Create React App and Webpack help us generate production-optimized builds
- What an ejected Create React App project looks like
There’s one essential element our demo Webpack-React app is missing, however.
In our second project (the timers app) we had a React app that interfaced with an API. The node server both served our static assets (HTML/CSS/JS) and provided a set of API endpoints that we used to persist data about our running timers.
As we’ve seen in this chapter, when using Webpack via Create React App we boot a Webpack development server. This server is responsible for serving our static assets.
What if we wanted our React app to interface with an API? We’d still want the Webpack development server to serve our static assets. Therefore, we can imagine we’d boot our API and Webpack servers separately. Our challenge then is getting the two to cooperate.
Using Create React App with an API server
In this section, we’ll investigate a strategy for running a Webpack development server alongside an API server. Before digging into this strategy, let’s take a look at the app we’ll be working with.
The completed app
food-lookup-complete is in the root of the book’s code download. Getting there from heart-webpack:
$ cd ../..
$ cd food-lookup-complete
Take a look at the folder’s structure:
$ ls
README.md
client/
db/
node_modules/
package.json
server.js
start-client.js
start-server.js
In the root of the project is where the server lives. There’s a package.json here along with a server.js file. Inside of client/ is where the React app lives. The client/ folder was generated with Create React App.
Look inside of client/ now:
If you’re on macOS or Linux run:
$ ls -a client
Windows users can run:
$ ls client
And you’ll see:
.babelrc
.gitignore
node_modules/
package.json
public/
src/
tests/
In OSX and Unix, the -a flag for the ls command displays all files, including “hidden” files that are preceded by a . like .babelrc. Windows
displays hidden files by default.
So, we have two package.json files. One sits in the root and specifies the packages that the server needs. And the other lives in client/ and specifies the packages that the React app needs. While co-existing in this folder, we have two entirely independent apps.
.babelrc
A noteworthy file inside cilent/ is .babelrc. The contents of that file:
// client/.babelrc
{
"plugins": ["transform-class-properties"]
}
As you may recall, this plugin gives us the property initializer syntax which we used at the end of the first chapter. In that project, we specified we wanted to use this plugin by setting the data-plugins attribute on the script tag for app.js, like this:
<script
type="text/babel"
data-plugins="transform-class-properties"
src="./js/app.js"
></script>
Now, for this project, Babel is being included and managed by react-scripts. To specify plugins we’d like Babel to use for our Create React App projects, we must first include the plugin in our package.json. It’s already included:
"dependencies": {
We then just have to specify we’d like Babel to use the plugin in our .babelrc.
Running the app
In order to boot our app, we need to install the packages for both the server and client. We’ll run npm i inside both directories:
$ npm i
$ cd client
$ npm i
$ cd ..
With the packages for both the server and client installed, we can run the app. Be sure to do this from the top-level directory of the project (where the server lives):
$ npm start
As things are booting, you’ll see some console output from both the server and the client. Once the app is booted, you can visit localhost:3000 to see the app:
The app provides a search field for looking up nutritional info in a food database. Typing a value into the search field performs a live search. You can click on food items to add them to the top table of totals:
The app’s components
The app consists of three components:
- App: The parent container for the application.
- SelectedFoods: A table that lists selected foods. Clicking on a food item removes it.
- FoodSearch: Table that provides a live search field. Clicking on a food item in the table adds it to the total (SelectedFoods).
In this chapter, we won’t dig into the details of any of these components. Instead, we’re just going to focus on how we got this existing Webpack-React app to cooperate with a Node server.
How the app is organized
Now that we’ve seen the completed app, let’s see how we got this to work.
Kill the app if it’s running then change to the food-lookup directory (the noncomplete version) inside webpack. Getting there from food-lookup-complete:
cd webpack/food-lookup
Again, we have to install the npm packages for both server and client:
$ npm i
$ cd client
$ npm i
$ cd ..
The server
Let’s boot just the server and see how that works. In the completed version, we used npm start to start both the server and the client. If you check package.json in this directory, you’ll see that this command is not yet defined. We can boot just the server with this:
$ npm run server
This server provides a single API endpoint, /api/food. It expects a single parameter, q, the food we are searching for.
You can give it a whirl yourself. You can use your browser to perform a search or use curl:
$ curl localhost:3001/api/food?q=hash+browns
[
{
"description": "Fast foods, potatoes, hash browns, rnd pieces or pa\
tty",
"kcal": 272,
"protein_g": 2.58,
"carbohydrate_g": 28.88,
"sugar_g": 0.56
},
{
"description": "Chick-fil-a, hash browns",
"kcal": 301,
"protein_g": 3,
"carbohydrate_g": 30.51,
"sugar_g": 0.54
},
{
"description": "Denny's, hash browns",
"kcal": 197,
"protein_g": 2.49,
"carbohydrate_g": 26.59,
"sugar_g": 1.38
},
{
"description": "Restaurant, family style, hash browns",
"kcal": 197,
"protein_g": 2.49,
"carbohydrate_g": 26.59,
"sugar_g": 1.38
}
]
Now that we understand how this endpoint works, let’s take a look at the one area it is called in the client. Kill the server with CTRL+C.
Client
The FoodSearch component makes the call to /api/foods. It performs a request every time the user changes the search field. It uses a library, Client, to make the request.
The Client module is defined in client/src/Client.js. It exports an object with one method, search(). Looking just at the search() function:
function search(query, cb) {
return fetch(`http://localhost:3001/api/food?q=${query}`, {
accept: 'application/json',
}).then(checkStatus)
.then(parseJSON)
.then(cb);
}
The search() function is the one touch point between the client and the server. search() makes a call to localhost:3001, the default location of the server.
So, we have two different servers we need to have running in order for our app to work. We need the API server running (at localhost:3001) and the Webpack development server running (at localhost:3000). If we have both servers running, they should presumably be able to communicate.
We could use two terminal windows, but there’s a better solution.
If you need a review on the Fetch API, we use it in Chapter 3: Components and Servers.
Concurrently
Concurrently⁶⁶ is a utility for running multiple processes. We’ll see how it works by implementing it.
Concurrently is already included in the server’s package.json:
},
"devDependencies": {
"concurrently": "3.1.0"
We want concurrently to execute two commands, one to boot the API server and one to boot the Webpack development server. You boot multiple commands by passing them to concurrently in quotes like this:
# Example of using `concurrently`
$ concurrently "command1" "command2"
If you were writing your app to just work on Mac or Unix machines, you could do something like this:
$ concurrently "npm run server" "cd client && npm start"
Note the second command for booting the client changes into the client directory and then runs npm start.
However, the && operator is not cross-platform and won’t work on Windows. As such, we’ve included a start-client.js script with the project. This script will boot the client from the top-level directory.
Given this start script, you can boot the client app from the top-level directory like this:
$ babel-node start-client.js
We’ll add a client command to our package.json. That way, the method for booting the server and the client will look the same:
# Boot the server
$ npm run server
# Boot the client
$ npm run client
Therefore, using concurrently will look like this:
$ concurrently "npm run server" "npm run client"
Let’s add the start and client commands to our package.json now:
"scripts": {
"start": "concurrently \"npm run server\" \"npm run client\"",
"server": "babel-node start-server.js",
"client": "babel-node start-client.js"
},
For start, we execute both commands, escaping the quotes because we’re in a JSON file.
Save and close package.json. Now we can boot both servers by running npm start. Go ahead and do this now:
$ npm start
You’ll see output for both the server and the client logged to the console. Concurrently has executed both run commands simultaneously.
When it appears everything has booted, visit localhost:3000. And then start typing some stuff in. Strangely, nothing appears to happen:
Popping open the developer console, we see that it is littered with errors:
Picking out one of them:
Fetch API cannot load http://localhost:3001/api/food?q=c. No 'Access-Co\
ntrol-Allow-Origin' header is present on the requested resource. Origin\
'http://localhost:3000' is therefore not allowed access. If an opaque \
response serves your needs, set the request's mode to 'no-cors' to fetc\
h the resource with CORS disabled.
Our browser prevented our React app (hosted at localhost:3000) from loading a resource from a different origin (localhost:3001). We attempted to perform CrossOrigin Resource Sharing (or CORS). The browser prevents these types of requests from scripts for security reasons.
Note: If this issue didn’t occur for you, you may want to verify your browser security settings are sound. Not restricting CORS leaves you open to significant security risk.
This is the primary difficulty with our two-server solution. But the two-server setup is common in development. Common enough that Create React App has a solution readily available for us to use.
Using the Webpack development proxy
Create React App enables you to setup the Webpack development serverto proxy requests to your API. Instead of making a request to the API server at localhost:3001, our React app can make requests to localhost:3000. We can then have Webpack proxy those requests to our API server.
So, our original approach was to have the user’s browser interact directly with both servers, like this:
However, we want the browser to just interact with the Webpack development server at localhost:3000. Webpack will forward along requests intended for the API, like this:
This proxy feature allows our React app to interact exclusively with the Webpack development server, eliminating any issues with CORS.
To do this, let’s first modify client/src/Client.js. Remove the base URL, localhost:3001:
function search(query, cb) {
return fetch(`/api/food?q=${query}`, {
accept: 'application/json',
}).then(checkStatus)
Now, search() will make calls to localhost:3000.
Next, in our client’s package.json, we can set a special property, proxy. Add this property to client/package.json now:
// Inside client/package.json
"proxy": "http://localhost:3001/",
Make sure to add that line to the client’s package.json, not the server’s.
This property is special to Create React App, and instructs Create React App to setup our Webpack development server to proxy API requests to localhost:3001. The Webpack development server will infer what traffic to proxy. It will proxy a request to our API if the URL is not recognized or if the request is not loading static assets (like HTML, CSS, or JavaScript).
Try it out
Use Concurrently to boot both processes:
$ npm start
Visiting localhost:3000, we see everything working. Because our browser is interfacing with the API through localhost:3000, we have no issues with CORS.
Webpack at large
As a platform for JavaScript applications, Webpack packs numerous features. We witnessed some of these features in this chapter. Webpack’s abilities can be considered more broadly underneath two categories:
Optimization
Webpack’s optimization toolset for production environments is vast.
One immediate optimization Webpack provides is reducing the number of files the client browser has to fetch. For large JavaScript apps composed of many different files, serving a handful of bundle files (like bundle.js) is faster than serving tons of small files.
Code splitting is another optimization which builds off of the concept of a bundle. You can configure Webpack so that it only serves the JavaScript and CSS assets that are relevant to the page that a user is viewing. While your multi-page app might have hundreds of React components, you can have Webpack only serve the necessary components and CSS that the client needs to render whatever page that they are on.
Tooling
As with optimization, the ecosystem around Webpack’s tooling is vast.
For development, we saw Webpack’s handy hot- and auto-reloading features. In addition, Create React App configures other niceties in the development pipeline, like auto-linting your JavaScript code.
For production, we saw how we can configure Webpack to execute plug-ins that optimize our production build.
When to use Webpack/Create React App
So, given Webpack’s power, you might ask: Should I just use Webpack/Create React App for all my future React projects?
It depends.
Loading React and Babel in script tags as we did in the first couple of chapters is still a completely sane approach. For some projects, the simplicity of this setup might be preferable. Plus, you can always start simple and then move a project to the more complex Webpack setup in the future.
What’s more, if you’re looking to roll React out inside an existing application this simple approach is your best bet. You don’t have to adopt an entirely new build or deployment pipeline for your app. Instead, you can roll React components out oneby-one, all by simply ensuring that the React library is included in your app.
But, for many developers and many types of projects, Webpack is a compelling option with features that are too good to miss. If you’re planning on writing a sizeable React application with many different components, Webpack’s support for ES6 modules will help keep your codebase sensible. Support for npm packages is great. And thanks to Create React App, you get tons of tooling for development and production for free.
There is one additional deal breaker in favor of Webpack: testing. In the next chapter, we learn how to write tests for our React apps. As we’ll see, Webpack provides a platform for easily executing our test suite in the console, outside of a browser.