Back in early 2017, I wrote a blog tutorial Step-by-Step Minimalistic React Web App with Just the Bare Essentials which is compatible with the legacy Webpack 1, but since then Webpack 1 has become obsolete, and so that tutorial has become obsolete as well. So I wrote another tutorial Step-by-Step Minimalistic React Web App with Webpack 3 and Just the Bare Essentials that is compatible with Webpack 3, which is now outdated / borderline obsolete since the introduction of Webpack 4 in February 2018. This new blog tutorial provides instructions on how to produce a minimalistic React app with the currently latest (June 2018) Webpack version 4.
Proper Webpack configuration is absolutely crucial to having a working React app, and the procedure and various dependencies differ slightly between the different versions of Webpack. Most of the steps for developing a React app with Webpack 4 are exactly the same as with Webpack 3, however, some specifics are different. I list these out in the Summary section. Also, just like the Webpack 3 tutorial, this tutorial has been developed on Ubuntu 18.04 with Node 8.10.0 and NPM 3.5.2.
For anybody unfamiliar with React, I recommend that you first read the following sections from the original tutorial:
The philosophy and purpose behind React has not changed.
The Node.js is the runtime environment inside which all of our other tools will be running. So we need to natively install Node.js onto the operating system of the computer on which we’ll be doing our development. The official installation instructions for Node.js are here on their website.
On my Ubuntu 18.04 test machine, I just did
apt-get install nodejs with
root permissions. This installed Node 8.10.0.
So install Node.js as the first step of this tutorial. This has been documented in the example repo commit
The NPM Node Package Manager will be used to install Webpack 4 and Babel. On some OSes NPM also gets installed as a dependency of Node.js, but on my Ubuntu 18.04 test system this was not the case. I had to install it manually with
apt-get install npm with
root permissions. If you’re not on Ubuntu 18.04, please refer to the official installing NPM page. To verify that you have NPM installed, run
npm -v inside your console. I’m personally currently running NPM version 3.5.2.
This has been documented in the example repo commit
The Webpack module bundler, specifically version 4 for this tutorial, will manage the whole build process for our app, (including running the Babel transpiler,) so we need to install it next. This time we’ll be installing with NPM rather than with the native OS package manager. In fact, from this point on, we’ll only be using NPM whenever we need to install any additional software. The NPM package for Webpack is
webpack. Run the command:
npm install webpack@^4.12.1 Notice the version specifier
@^4.12.1 at the end. This specifier is crucial to insure that Webpack 4 is installed, and not 5 or some newer version when it gets released in the future. The specific version
4.12.1 is the very latest at the time this is written, but the
^ caret operator will allow this command to download newer versions as they are released, as long as the major version number still remains 4.
This step is documented in the example repo commit
Webpack 4 needs a separate CLI package to be explicitly installed alongside it, or otherwise it will complain and hang until such a package is installed. This package can be
webpack-cli. The command to install it is:
npm install webpack-cli
This step is documented in the example repo commit
At this point, a new directory
node_modules/ will appear at the root of your project directory, and it will contain the logic for Webpack and all its dependencies. This directory and all its contents currently take up 114 MB on the Ubuntu 18.04 system I’m using. We don’t have to install Webpack inside our project, in fact, NPM allows installing all packages globally; however, for the purpose of this tutorial we’re installing all packages locally to get a better idea of what we need to install, and also not to mess with the global system configuration.
However, even though we’re going to keep
node_modules/ locally, we’re not going to keep track of its contents with our Git repository, or at least I’m not. So I recommend you create a
node_modules/ in it. This is done in the example repo commit
Now that we have Webpack 4 installed, we could immediately continue to installing Babel, but not just yet. First, we’re going to introduce some very basic Webpack 4 configuration without Babel, and then when we install Babel, we’re going to add the Babel-specific configuration on top of that initial configuration. This way we’ll get to see only the bare minimum of what Webpack can operate with, and what Babel requires.
So Webpack is a module bundler. What that means is that it takes in several files (modules), and spits out a single file with all those modules combined. Actually the output does not always have to be a single file, but that’s what it will be for the purpose of this tutorial. We bundle several files into 1 to make the logic they contain faster for the browser to download.
By convention, we will keep our Webpack input files in the subdirectory
webpack_in, and our output file will be dumped by Webpack into
So by using this nomenclature, our initial entry file will be
webpack_in/entry.js. For now, all the logic it will contain will be a console log to indicate that it has been interpreted when we test it in the browser. I have prepared this initial logic in the example repo commit
webpack_out/minimalistic_react.js For now we’re processing just 1 file and outputting just 1 file, but that’s just for the sake of this tutorial, to allow us to see the most basic Webpack configuration. Pretty soon we’ll be processing a lot more input files than just one. The output file name
minimalistic_react.js is just an arbitrary name I chose.
We communicate our parameters to Webpack via a special configuration file, which we’re going to call
As you can see, this time the configuration logic is 9 lines rather than 8 as for Webpack 1 and 3. As before, we import Node.js API
path to be able to join directory names with the host OS directory separation token, next, we specify our entry file
webpack_in/entry.js, and then specify our output to be
webpack_out/minimalistic_react.js. But then, there’s this additional configuration with the field
mode being set to
development. This additional configuration specifies that for now we want to do an unminified development build for quicker testing and easier debugging. We did not need to specify this with versions 1 and 3, because they do development build by default, but with Webpack 4 there’s a major change in that it does the production build by default.
Time to run Webpack with our just-prepared config! This is done with the command:
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js This step has been documented in the example repo commit
--watch command line parameter. To use it, open a separate terminal window in the project directory, and enter the command:
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js --watch I documented this in the commit
The Webpack process will not exit, but will just sit there and wait for new changes. If you’ll be running Webpack in this mode, then make sure to periodically check its output for reports of any syntax errors you might accidentally introduce into your input code.
webpack_out/minimalistic_react.js. This file is currently 4628 bytes on my system, while the input file
webpack_in/entry.js is only 282 bytes. If we open the generated file, we can see that Webpack added a whole bunch of additional lines denoted by commented-out asterisks. This is normal, and since right now we’re working with a development build it is not going to be a problem. Later on in this tutorial, after we actually implement our basic React app, we’re going to experiment with minified production builds, at which time we’ll add additional Webpack configuration to prevent this superfluous generated bloat.
The generated output file is bulky and will keep changing everytime we make a change to our input files. So it makes sense to add it to
.gitignore as well to keep our Git history clean. This step has been done in the example repo commit
ef897500c922dc39ecbc7601b4af7fffa96e60de, and we can try it here.
webpack_out/minimalistic_react.js not inside the
<head> tag as is so common with using
<script> tags, but at the very bottom of the
<script> inside the
Once again we’ll be using NPM to install 2 packages —
babel-loader. (Same as for Webpack 3). The command for that is:
npm install babel-core babel-loader (or 2 separate commands
npm install babel-core and
npm install babel-loader). At this time the size of my
node_modules subdirectory is 140 MB, and previously it was 114 MB. I documented this step in the commit
We have Babel installed, but Webpack does not know about it yet. To tell Webpack to use Babel to transpile ES6+/JSX, we need to tell it to do so in that Webpack configuration file we worked with earlier in step 7 —
webpack.config.js. I did this in the example repo commit
f2714318ade2ad67fca226777627ed40a14d93cc. Notice that this change to the Webpack 4 config is very different from the corresponding change to the Webpack 3 config —
82cfbe5e03ad314d28fb2a1351ea92a4e4de441d. This configuration schema has changed significantly from Webpack 3 to Webpack 4.
What has remained the same from the previous tutorials, is that we’re still saying that we want Webpack to “load” files with the file extension
.jsx through Babel. Regular
.js files will not be going through Babel. This is the Airbnb nomenclature approach, and contrary to Facebook which likes to reuse the
.js extension for files containing JSX logic.
Alright, so we just told Webpack to load
.jsx files through Babel, but we don’t have any
.jsx files yet! So let’s create
webpack_in/entry.jsx for our future ES6/JSX logic! I just did this in the example repo commit
Whereas I had
entry.jsx emit “JSX entry logic”. When we finally test this in a web browser that’s how we’ll know that our configuration is working.
Now we have told Webpack to use Babel for
.jsx files, we have created our
webpack_in/entry.jsx, but we still need to tell Webpack to actually pack that new file. Just like with
webpack_in/entry.js, we must add
webpack_in/entry.jsx to the
entry array in
webpack.config.js. I did this in the example repo commit
It’s time to run
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js to pack our JS and JSX files together, deploy the test page, and test it with a web browser! This will verify that everything we did up to now is working. I just deployed it here.
We’re almost ready to work with React, but still not quite there yet. Just as with Webpack 1 and 3, to tell Babel that we actually want to translate / transpile ES6+ and JSX, we must specify certain “presets“. These
presets are referred to by the tokens
react. The former is for ES6+, and the later is for JSX. (These are the same presets as for Webpack 3 in the previous tutorial.) Before we can even specify these presets in the configuration, we have to install their NPM modules, with the commands:
npm install babel-preset-env and
npm install babel-preset-react (These tokens can be combined into a single NPM command, but I’m listing 2 commands for better clarity.) I documented this in the example repo commit
node_modules folder grew from 140 MB to 153 MB and 154 MB after completing this step.
And now we need to add the Babel / Webpack configuration to use the presets. I just did this in the example repo commit
18073cc8b570a7b8f06952ea93bb871d8c0ffe87. As you can see this is another relatively minor addition of just 3 lines. In fact our whole Webpack configuration file is only 27 lines long, and that’s including the comment header and the blank separator lines. Our configuration is at the minimum it can be, plus now we’ve got our project infrastructure up to the point where we’re ready to work with React!
Note that this change for Webpack 4 configuration is different to the corresponding change to the Webpack 3 configuration in the previous tutorial, which is
ab77c4dba397e64b696d9d6307f8ec011f7a871e. For Webpack 4 we’re using the configuration sub-field
options rather than
Hooray! We’re now ready to work with React. This procedure has not changed at all in our migration from Webpack 1 to Webpack 3 to now Webpack 4, but the git commits across the Webpack-specific branches in the example repo are different.
The first thing we need to do to work with React is install the NPM packages
react-dom with the commands:
npm install react and
npm install react-dom I have documented this in the example repo commit
After installing these packages, my
node_modules/ folder grew from 154 MB to 172 MB.
Next, we need to prepare our future React app root container element inside our test page. The container element is nothing but an empty opening and closing tag that we will specify to React DOM to render our app in. The way React DOM works is that it dumps the previously discussed virtual DOM into this container element. Any previous contents of this container element will get deleted when React DOM does this, so that’s why we’re going to just have an opening and a closing tag.
I’m going to specify a tag
id for this container to be able to use the ancient
document.getElementById(...) browser API call to obtain a reference to the element to feed to React DOM. This is how it is normally done when deploying a React app inside a web page. Using the raw DOM API has waned in popularity over the past decade in favor of using the much more flexible and comprehensive jQuery API; however, while still possible, using jQuery is frowned upon when developing with React. And this is actually the only direct DOM API call that we will be making.
Using jQuery inside React apps is frowned upon because while jQuery is all about the DOM, React has the completely opposite philosophy of never touching the real DOM except when obtaining the root container element as we’re doing right now, or rendering to it from its virtual DOM. Had we included jQuery into our project, at least for the purposes of this demo, it would end up as mostly dead code, needlessly eating up space and bandwidth. There’s nothing remarkable our test app will be doing to need jQuery.
The container element itself can be any tag, and for this tutorial I’m just going to use a plain
<div> with an
react-app. I cherry-picked this into the example repo commit
The most crucial detail in this step is that the root container element needs to be above the
document.getElementById(...) call to obtain a reference to it. Otherwise there would be no element to obtain a reference to, and therefore nowhere to render our app into. This has been discussed earlier in step 10 of the build infrastructure preparation procedure in which we explicitly placed that
<script> tag just before closing the
<body> of our test page.
And at this point we’re ready to start writing our React app inside
webpack_in/entry.jsx! I just did that in the commit
Staying true to the name of this tutorial, I made the app absolutely as minimalistic as it can be — all it does is render a
<div> containing the text “Hello! I’m a React app!!”, and this is all accomplished using just 3 lines of code.
ReactDOM.render(...)to render the JSX markup for our app into the root container element.
You may be wondering why I did not make it 2 lines rather than 3 by omitting the import of
react, since I don’t actually call its API directly in this initial implementation. It turns out that this import is a necessary dependency for the method
ReactDOM.render(...) to work. In any case, by convention we use that method to render the JSX markup for our root widget, but have that root widget be defined separately using the
react package API, so if we were to continue developing this app we would be working directly with that package as well.
Now that we have the
webpack_in/entry.jsx ES6/JSX logic for our app ready, we need to transpile it to
webpack_out/minimalistic_react.js ES5 logic, deploy it, and test it in a web browser. As before, we build / transpile with the command
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js, and upon deployment our app looks like this.
Upon looking at the app test page we will see the string “Hello! I’m a React app!!” rendered into the DOM into that root container element
You may also see a console message “Download the React DevTools for a better development experience” followed by a URL or similar. Facebook has created an additional in-browser debugging tool for React applications, offers it as browser extensions, and also inserted special logic into the React DOM toolkit to promote it via the console log on non-production builds when the browser extension has not been detected. If you install this extension into your browser this informational message will no longer be emitted, and you will instead get a new “React” tab in the browser’s web inspector when you view pages that use React. This React tab will let you inspect React components of a React app in a similar fashion to the DOM Inspector. You can try it out on the current test page (but you’ll see only its one and only component) and on other web sites currently known to use React DOM directly, such as Instagram and Airbnb.
Another interesting thing worth mentioning is that our
So 715 KB raw / 164 KB gzipped is a little too much for production, and we need to slim it down. Since we’re building our releases with Webpack, we’ll once again be working with the Webpack configuration file
webpack.config.js to add settings to do a production build.
The required modifications to the Webpack config are relatively minor, and even less than with Webpack 3 in the previous tutorial. Unlike Webpack 3, Webpack 4 does not need any special plugins for minification. We only need to tell Webpack 4 that we’re not in the
development mode any more. There are 2 ways to do it — (1) just remove the configuration line
mode: 'development' since Webpack 4 does production builds by default, or (2) for better clarity, change the configuration to
mode: 'production'. No additional NPM installs will be required for what we’re doing. And I just committed the
webpack.config.js production configuration change as
Now that we have changed our Webpack configuration for production, it is time to run it again to do our production build. Whereas we had to specify an additional command line parameter
-p with Webpack 3, we don’t need to do it now with Webpack 4 since we already told it to be in the
production< mode in the previous step. So our build command remains
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js. (Had we not changed mode in
webpack.config.js, we could still use the
-p parameter to explicitly force production builds.)
However, the Webpack 'watch' command used earlier to have it auto-re-bundle our code whenever we change it in the course of development is no longer applicable in the production branch, so I removed it from the production branch build instructions in the commit
webpack_out/minimalistic_react.js is now 101 KB raw size. (Same as with Webpack 3.) That's still not too small, but over 7 times smaller than what it used to be. Let's test the new production build here.
So everything still works, the React DevTools promotion is no longer appearing even if the browser plugin is not installed, and, best of all, our downloaded web server auto-gzipped size is now 32 KB. That's pretty efficient!
Preact is just one of many React API clones / drop-in replacements. There's also another such toolkit called Inferno, but in this tutorial we're going to be working with Preact to see how much smaller we can get our
The first thing we need to do to get started with Preact is to install its NPM modules
preact-compat. The later is what duplicates the React API to use the former. We're going to install these modules with the command:
npm install preact preact-compat I've documented this in the example repo commit
947d4f8ab20df411429f1d1fdf59d30e1f004173. Notice that I did not remove the instructions for installing React and React DOM. Just like in the previous tutorials, these are useful to keep in case we run into some weird future problem in the course of our app development, and decide to temporarily go back to the regular React to check if the problem could be caused by the drop-in replacement. (If we wanted to get really fancy, we could set up automated integration tests to verify the performance of our build under multiple environments.)
The size of the
node_modules/ on my development machine was 172 MB before I installed Preact, and it was 175 MB after. (This is the same 3 MB increase as in the Webpack 3 tutorial. Even though we're using a newer version of Webpack, we're still using the same versions of the Preact packages.)
Now that we have installed Preact, we need to tell Webpack to actually use it. So we modify our good old
webpack.config.js to substitute Preact for React. I just did that in the example repo commit
9809a5e371a168d2e43c233597348dc9274f2ace. As you can see, I "alias"-ed Preact in, exactly as with Webpack 1 and Webpack 3. Now when Webpack will be trying to get React it will actually be getting Preact. The key take-away here is that I did not have to modify anything in
entry.jsx where the actual React web app lives, and if we ever want to go back, we can just comment-out these 6 lines, and we'll be back with the plain React. It's incredibly easy, and the Webpack configuration schema for this option has not changed at all between versions!
OK, time to run
./node_modules/webpack/bin/webpack.js --config ./webpack.config.js to test our app with Preact. We're doing a regular development build for now. And the output
webpack_out/minimalistic_react.js on my machine is now 98 KB raw size (8 KB more than with Webpack 3), while the raw size with React was 715 KB. We can look at the Preact development build test page here to see that everything still works, except that the React DevTools no longer finds our app. Preact eliminated the DevTools linkage for maximum efficiency, but if we need to debug our app again we can just temporarily comment-out that alias in Webpack config.
Our gzipped development build turned out to be 24.7 KB, while with React it was 164 KB. Who needs production builds! Well, we still do, it's just that our current minimalistic app only renders a single
Our evaluation of Preact would not be complete without a production build. This is exactly the same as configuring a React production build. Just as before, we need to modify the Webpack config to take it out of the
development mode as outlined in step 1 of the React production section. I just cherry-picked that commit as
Same as for a React production build, we have to modify our
BUILD_INSTRUCTIONS to remove the Webpack watch command. I just cherry-picked that build instructions commit as
And the Preact production output of
webpack_out/minimalistic_react.js on my system is currently 22 KB (Same as with Webpack 3). If we test this build here we can see that our downloaded gzipped size is 7.8 KB !!! As a disclaimer I should mention that I'm not affiliated with either Preact or React and I was not paid to write this blog.
So far I went through everything step by step for the sake of clarity and demonstration, to give you the best idea of all the distinct elements required to get up and running with React; however, not all of the manual steps mentioned are absolutely necessary. This is especially true for manually installing the numerous NPM packages we installed as part of this tutorial. In fact we can list-out our packages in a configuration file called
package.json, and install all of them with a single command of:
Another reason why this is useful is because package names, dependencies, and compatibilities change over time. The NPM packages outlined in this blog will mutate, and sooner or later will accumulate changes that will break the procedure outlined in this tutorial, (just as happened with the previous tutorials for Webpack 1 and 3.) The NPM configuration file
package.json allows us to lock-in package versions, giving us an immutable reference to a known working state of our dependencies.
We can use the NPM command
node init to generate our
package.json. This utility was initially built to publish NPM packages onto the NPM public repository, and for that reason it prompts the user and includes in its output some additional information that I'm going to cut out for now. And so I've added
package.json as git commit
d74d9790f46a9fef4944d4bac7273a84613b9c52 into the Preact development branch, and modified the
BUILD_INSTRUCTIONS in git commit
node init does not differentiate between runtime dependencies and development dependencies. Runtime dependencies are those that are included from within the project application logic, and the logic within these dependencies is invoked during runtime (such as to render the app as React does in this case). Development dependencies are those that are not included from within the application logic, but are needed to bundle, deploy, or debug the application, and so on. In this project, the runtime dependencies are:
While the development dependencies are:
As can be seen in the commit
d74d9790f46a9fef4944d4bac7273a84613b9c52, I manually separated the development dependencies under the separate field
devDependencies. This provides a better separation as to what is needed for what, and makes it easier to reuse this project in other projects, as the development dependencies of a dependency do not need to be pulled in.
The example repo is at https://github.com/maratbn/example_step_by_step_minimalistic_react_app and can be cloned and used as a boilerplate for a new React app. The repo contains commits for this tutorial as well as the previous tutorials on using Webpack 1 and 3, but the commits are separated across different branches. The branches that apply to this tutorial all begin with
webpack4. These additional Webpack 4 specific branches are:
webpack4-- Common configuration logic and commits.
webpack4--package.json-- Just like
webpack4, but with
webpack4--production-- Just like
webpack4, but with additional changes to
BUILD_INSTRUCTIONSto produce minimized bundles intended for deployment to production.
webpack4--production--package.json-- Just like
webpack4--production, but with
webpack4--preact-- Just like
webpack4, but with Preact swapped-in for React.
webpack4--preact--package.json-- Just like
webpack4--package.json, but with Preact.
webpack4--preact--production-- Just like
webpack4--production, but with Preact.
webpack4--preact--production--package.json-- Just like
webpack4--production--package.json, but with Preact.
If you want to use the example repo for your boilerplate, I recommend you base your project off the branch
webpack4--preact--package.json. When you want to deploy to production, fork-off a new release branch, and cherry-pick the production-optimizing commits from
In this tutorial I went over setting up a minimalistic React app with Webpack 4. The differences in procedure between this and the procedure in the Webpack 3 tutorial are:
@^4.12.1if not using
webpack-clihad to be explicitly installed in addition to
developmentmode, as it assumes
productionmode by default.
productionmode, there is no need to explicitly pass the
-pparameter when bundling for production.
There were no changes to the actual React app implementation, even though in this tutorial we're using newer React packages. Also, the production bundle sizes for the pure React and Preact apps remained the same from the previous tutorial, likely because all the non-Webpack packages used in this tutorial as the same as in the previous tutorial, and the app is too small to take advantage of Webpack 4 optimizations. Nevertheless, Webpack 4 is supposed to be much faster and more efficient than Webpack 3, and this should become noticeable as the app size increases.