How To Add Webpack To An Auto-Created React App

Mike Pottebaum
7 min readNov 9, 2020

--

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:

  1. React — This one’s pretty obvious at face value, but what exactly is react and what is necessary to use it?
  2. Bundler — This is really where create-react-app does 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.
  3. Server — You need a server to host your app during development, preferably one with hot reload enabled. I’ll use Webpack’s development server here, but in the near future I’ll post a follow-up to this article explaining how to build your own JavaScript server with Express and use it for development. (UPDATE: I followed-through)
  4. 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:

mkdir bare-bones-react
cd bare-bones-react
npm init

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.

React

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 and 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 index.js file:

index.js

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 root:

index.html

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)

The next piece that we need is a bundler. What is a bundler? It bundles all of your JavaScript, including the modules, into one file. The reason this is important is that ultimately your JavaScript will need to be loaded into an HTML document and loading <script> tags is very expensive. A bundler allows you to condense all of your JavaScript into one file (along with CSS if you like) so it can be loaded in with one <script> tag.

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:

node_modules
src
App.js
index.html
index.js
package.json
package-lock.json

I moved App.js, index.html and 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:

webpack.config.js

And let’s see what all of this is doing:

  • "mode" is self-explanatory. We’re telling Webpack were in development.
  • "entry" is our main JavaScript file, or the ‘entry point’. In React, this is the file where we use our renderer.
  • "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 /dir in the root directory (__dirname is a Node variable representing the current directory path) and create a file within it named bundle.js.
  • "devtool" allows us to utilize development tools provided by Webpack. I’m using source-map because, after Webpack compiles our project, all of our code will be located in a single file we did not create. source-map builds a separate file that maps the source of code in our bundled file to the JavaScript files we actually created.
  • "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-plugin to 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:

.babelrc

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-env converts the latest JavaScript syntax and @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:

.eslintrc.js

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 npx webpack.

After you’ve successfully compiled your project, you can alias the webpack command in your package.json file under the "build" script:

package.json (omitting everything but scripts section)

Development Server

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 instead:

npx webpack-dev-server --mode development --open --hot

The --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 create-react-app).

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:

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.

--

--