React: Bake from Scratch or Box (JavaScript Version): Part 1
A side-by-side comparison of creating React applications using a custom-built build solution (from scratch) versus using Create React App (from box).

TL;DR: If you want to jump to the conclusion, the final article in this series is React: Bake from Scratch or Box (JavaScript Version): Smackdown.
Purpose
I have been creating React applications since before there was a Create React App; as such I have always used a custom-built build solution.
At the same time, I understand the complexity involved in this approach and am re-evaluating using Create React App. When it first came out, I evaluated using it; there were some missing features that was a deal-breaker for me.
Even if we conclude that Create React App suits our needs, there is value in understanding what it is doing for us behind the scenes.
If you are going to follow along, you will need to install a modern version of Node.js (recommend 10.13.0 LTS).
Round 1: Just the Basics
note: The final version (of this round) of the custom-built build solution is available for download from the just-the-basics branch of the webpack-scratch-box repository.
In this round, we will focus on what it takes to get a React application off the ground; i.e., only considering building React JavaScript in both development and production environments.
Round 1: Just the Basics — Scratch
We will start with a webpack recommended configuration tool, Webpack Config Tool that provides instructions on creating a custom-build build solution. Duplicating the instructions here for clarity:
From a new folder, we initialize the package (creates package.json):
npm init -y
We then update it with a scripts section:
{
...
"scripts": {
"clean": "rm dist/bundle.js",
"build-dev": "webpack -d --mode development",
"build-prod": "webpack -p --mode production",
"start": "webpack-dev-server --hot --mode development"
},
...
}
We install the dependencies:
npm install --save-dev webpack webpack-cli babel-loader @babel/preset-react @babel/core @babel/preset-env webpack-dev-server
npm install react react-dom react-hot-loader
We create the webpack configuration file (webpack.config.js):
We create the babel configuration file (.babelrc):
We create the source folder (src) and the application’s entry point (src / index.js):
We create the distribution folder (dist) and the HTML template (dist / index.html):
For good measure, we will create the recommended README.md file (doubles as instructions on how to build the project):
At this point, we have completed all the steps provided by Webpack Config Tool. But, we immediately have some issues we need to correct.
First, it is common to not commit either the dependencies (node_modules) or build artifacts (dist / bundle.js). As a matter of fact, the entire dist folder is typically considered a build artifact.
The standard solution here is to use html-webpack-plugin to copy the HTML template file from a public folder into the dist folder (injecting the script tag in the process):
npm install --save-dev html-webpack-plugin
We then update webpack.config.js:
...
const HtmlWebpackPlugin = require('html-webpack-plugin');const config = {
...
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html'
})
]
}
...
We then move the HTML template file (index.html) from dist to public ( removing the script tag).
We then create a .gitignore file to avoid committing the folders:
With this change we need to update the clean script to remove the whole dist folder. Also, one annoying thing is that when running npm start, it does not automatically open a browser running the application (a common behavior) . The fix here is to update package.json:
{
...
"scripts": {
"clean": "rm -f -r dist",
...
"start": "webpack-dev-server --open --hot --mode development"
},
...
}
One important behavior that does work (because of the -p option in the build-prod script) is the replacement (in the code during the build) of process.env.NODE_ENV with a string, production. This is important as some libraries, including React, are optimized when built using this arrangement.
Also, some third-party libraries, e.g., Apollo Client, are using a new file extension (.mjs) for ECMAScript Modules. In order to build projects with these libraries we need to update webpack.config.js:
...
const config = {
...
resolve: {
extensions: [
'.mjs',
'.js',
'.jsx'
]
},
...
}
...
With these corrections, we have a solution for basic development and production builds.
Round 1: Just the Basics — Box
In this case, much of the complexity of configuring the build system is removed. We only need to run (to create a project folder my-app).
npx create-react-app my-app
From the output of this command (below), we see that we already for development and production builds.
Success! Created my-app at /Users/johntucker/Documents/my-app
Inside that directory, you can run several commands:yarn start
Starts the development server.yarn build
Bundles the app into static files for production.yarn test
Starts the test runner.yarn eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!We suggest that you begin by typing:cd my-app
yarn startHappy hacking!
A couple of observations:
- The project uses Yarn (needs to be globally installed) instead of npm for Node.js package management
- The project includes a .gitignore file that ignores, among other things, both the node_modules and build (its version of dist) folders
- The project includes an editable template public / index.html
- The command yarn start, automatically opens the application in a browser
- During the production build, yarn build, the process.env.NODE_ENV is replaced with production in the code; thus React is optimized
- Third-party libraries, e.g., Apollo Client, with ECMAScript Modules (.mjs) files build without error
- The resultant application does include additional code for a progressive web application; although not enabled by default
Round 1: Just the Basics — Conclusion
At this point, we can already see that Create React App is doing a lot of things for us behind the scenes; even things (like the ECMAScript Modules) that most people (including myself until recently) do not know about. This is a big win.
One minor downside is that Create React App still uses Yarn; modern versions of Node.js (npm) alleviate the need for Yarn.
Another minor downside is carrying around the extra (minimal and not enabled by default) progressive web application code. Of course, this is not a problem at all if one is actually creating a progressive web application.
As we consider just the basics, Create React App has a big benefit (simplicity) with few minor downsides.
Next Steps
In the next article, React: Bake from Scratch or Box (JavaScript Version): Part 2, we continue our side-by-side comparison by looking at how they handle polyfills.