Creating a React app using the
create-react-app command can seem like magic. If you’ve used React before, you might be vaguely aware of the presence of things like Webpack or Babel under the hood, but you aren’t required to know anything about them to successfully construct a highly functional application.
But what exactly is all that stuff doing under the hood? And is all of it necessary for every application? These are some of the questions I had after using
create-react-app for awhile, so I set out to find some answers by building a React app from scratch.
I think the best way to begin is by laying out everything we absolutely need for development. Here is a short list:
- React — This one’s pretty obvious at face value, but what exactly is
reactand what is necessary to use it?
- Bundler — This is really where
create-react-appdoes a lot of the heavy lifting for you. It’s also where you might be able to trim the most fat when it comes to setting up your development build. I’ll explain what is absolutely necessary for a React app and why.
- Tests — Of course, we can’t forget tests. For brevity’s sake, I won’t include testing in this article. Also, installing Jest is covered very well in the documentation, and I would basically be paraphrasing their instructions.
So how do we actually create this bare-bones project without
create-react-app? By simply creating a project directory and initializing
npm so we can install our packages:
npm will then ask you some questions about your project to setup your
package.json file. The default answers will be displayed in parentheses. Most of the defaults should be fine to start, and you can always change them in the
package.json file later if you need to.
Now that we have our project directory setup with
npm, let’s look into installing React. React itself (the package called
react) is essentially a library of references to the React API. To actually render React components, you also need a renderer that will inject functionality into those references and render them to the DOM.
Therefore, to utilize React in our app, we need to install two packages:
react-dom. This separation might seem odd at first, but it’s part of what makes React great. If you don’t like
react-dom, you can use a different renderer and still have access to the entire React API.
I’m going to use
react-dom because I haven’t yet looked into another renderer. So to install React, we’ll type the following into the command line:
npm i react react-dom
And then we also need to tell
react-dom what to render and where to render it. For this we’ll need an
Here, we’re telling
react-dom to render our
App component inside of a DOM node with the id
root. This means we’ll also need an
index.html file with an element that has the id
And, we’ll also need to define the
App component so we don’t get an error. I’ll leave that up to you.
Bundler (Webpack, Babel, ESLint)
We’ll use Webpack as our bundler, and we’ll configure it to use both Babel and ESLint during the bundling process.
Babel is necessary to include in React apps because it transpiles ES6 and JSX into backwards compatible code. ESLint is not necessary, but it is very helpful to have a linter during development.
Linters basically analyze your code and warn you about stuff that won’t necessarily break your application but could present problems later. It will warn you about all kinds of things, things like accidentally declaring global variables or not following code style conventions.
To use all of this stuff in our project, we’ll have to install quite a few different packages. Let’s start with just Webpack:
npm i --save-dev webpack webpack-cli html-webpack-plugin
We’re installing Webpack along with
webpack-cli, which allows us to compile our project from the command line.
html-webpack-plugin will generate a new
index.html file with the necessary
<script> tag based on the template
index.html file we created in the last part.
I also want to install some loaders for Webpack to use when it’s compiling our project:
npm i --save-dev html-loader css-loader style-loader
Next we’ll install Babel and it’s related packages:
npm i --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
Lastly, we’ll add ESLint and the additional packages necessary to use it with Webpack and Babel:
npm i --save-dev eslint eslint-loader eslint-plugin-react babel-eslint
All right, that’s a lot of stuff we just installed. It might feel a little overwhelming at this point, but just remember that all of these different packages are working together to achieve one purpose: generating bundled, backwards-compatible code.
Plus, we’ll get a better idea of how Webpack is using all of this when we start configuring it.
Configuring Webpack, Babel and ESLint
Before I get into configuration, I should mention that, at this point, my directory structure looks like this:
index.js into a
src directory because our root directory is about to fill up with configuration files. You can organize your directory however you want to, but keep in mind that you’ll need to provide Webpack with paths to some of these files. If you organize your project differently, you’ll need to adjust those file paths accordingly.
Let’s start with Webpack. Create a file in the root directory of your project with the name
webpack.config.js. From this file, we’ll export our configuration settings like so:
And let’s see what all of this is doing:
"mode"is self-explanatory. We’re telling Webpack were in development.
"output"tells Webpack where to put our bundled code and what to name the file. Here, we’re telling Webpack to create a directory named
/dirin the root directory (
__dirnameis a Node variable representing the current directory path) and create a file within it named
"devtool"allows us to utilize development tools provided by Webpack. I’m using
source-mapbecause, after Webpack compiles our project, all of our code will be located in a single file we did not create.
"module"is where we tell Webpack to use all of those loaders we installed before. We’re using regex to tell each loader which file extensions to target. Also, we’re telling Webpack to run ESLint before Babel translates our code (
"enforce": “pre",), so it can warn us about stuff in our source code, not the compiled code.
"plugins"is where we tell Webpack which plugins we want to use along with any configuration for those plugins. Here, we’re using the
html-webpack-pluginto generate an HTML file with everything we need to load our code.
That’s a lot of configuration. Luckily, the other configuration files are much simpler. Next up is Babel.
To configure Babel, create a file in your root directory with the name
.babelrc, and include the following:
Babel configuration files can be more complicated than this, but for our purposes, all we need to do is tell Babel to use the presets we installed. In a nutshell,
@babel/preset-react converts the JSX.
Lastly, we’ll configure ESLint. Create a file in the root directory called
.eslintrc.js, and include the following:
What we’re essentially doing here is notifying ESLint that we’re using Babel and React. We also want to tell ESLint that we’re working in the browser environment. Otherwise, it will incorrectly warn us that global variables like
document are not defined.
Now that everything is configured, we should be able to compile our project. If you installed
webpack-cli, you should be able to compile your project by simply typing
webpack into your terminal and hitting enter. If that doesn’t work, try
After you’ve successfully compiled your project, you can alias the
webpack command in your
package.json file under the
package.json (omitting everything but scripts section)
The last thing we need to start developing our application is a server. Lucky for us, Webpack has it’s own development server that integrates with Webpack out-of-the-box. All we need to do is install it:
npm i --save-dev webpack-dev-server
Then to run it, type the following command:
webpack-dev-server --mode development --open --hot
As with the
webpack command, you might need to use
npx webpack-dev-server --mode development --open --hot
--open flag is telling the server to open the browser once the server is ready. The
--hot flag tells the server to use hot reload, meaning the server will refresh the page every time you save a change to one of your project files (something you’re accustomed to if you’ve been using
You should now be able to load your application in your browser. You can alias this command as
npm start by adding it to the
scripts section of your
package.json (omitting everything but scripts section)
There you have it: a fully functioning React app built without using
create-react-app. I hope this helps clarify at least some of what’s happening under the hood of the React apps you’ve already built.