Notice: This tutorial was written for Webpack 1, which is presently obsolete. A follow-up tutorial has been written for Webpack 3.
Back when I was becoming familiar with the React JSX framework, I came across a number of introductory tutorials which provided examples for development but not production, and others auto-generated the initial application boiler-plate. But there wasn’t that much material that clarified just the bare essentials, especially useful for understanding the nitty-gritty of working with React, starting new apps from scratch, doing both development and production builds, and migrating existing apps. So I cobbled together that information from a variety of sources to use in my work, and presenting it here to be useful to others.
Special Thank You to Jeffrey Carl Faden for teaching an excellent front-end development seminar which included React and Redux, Dana Woodman for conducting an awesome Introduction to React / Redux workshop, to Brian Holt for giving a great talk on Preact, and to Team Treehouse for these wonderful courses.
React is often talked about as a JavaScript toolkit, but strictly speaking, or at least in my opinion, it is not a JavaScript toolkit, but a JSX toolkit. What is JSX? JSX is a language extension on top of JavaScript, that combines JavaScript and HTML together to allow us to declare our dynamic UI components using an HTML/XML-like markup embedded inside JavaScript logic, in the same sense that PHP allows embedding HTML content inside the PHP scripting language, JSP allows embedding HTML inside Java, and so on and so forth. But unlike PHP and JSP, JSX also allows rendering widgets client-side, permitting us to build single page client-side web apps, while the former are strictly server-side technologies.
The X in JSX stands for JavaScript eXtended, according Facebook, the inventor of React and the acronym JSX. However, at the present time, the Facebook React documentation refers to JSX as JavaScript, even though the JSX syntax is currently not a part of any official JavaScript spec and is rejected by vanilla JavaScript interpretors. Perhaps this is just Facebook’s way of saying that everybody should now be writing JSX rather than vanilla JavaScript, which may indeed be the case, but this terminology is indeed confusing. So just remember, when Facebook React documentation says “JavaScript”, it actually means JSX.
As I stated above, JSX is an extension to JavaScript; however, in this day and age, there is no longer just one JavaScript anymore, which is the confusing part.
The traditional JavaScript that everybody is familiar with is more strictly known as ES5, or EcmaScript 5. This is the JavaScript that is widely supported by the current browsers, but it is not the JavaScript that JSX is an extension of. JSX is an extension of EcmaScript 6, known as ES6 and also as ES2015. JSX syntax can be and often is combined with that of ES2016 — an even later revision of JavaScript / EcmaScript standard, but ES2016 is not absolutely necessary.
These latest JavaScript revisions allow for terser, more concise, and more powerful syntax and APIs to be used, which reduces code bloat, makes the code cleaner, easier to understand, develop, and debug.
So our React JSX code is going to look very different from the traditional ES5 JavaScript not only because of the JSX declarative markup, but also because of the new ES6 syntax.
Since the conventional browsers on which we want to run our web app do not support either JSX or ES6, we will be using a transpiler to translate our code into the supported ES5 as part of deployment for both development and production builds. It is of course possible to completely avoid transpilation by manually writing ES5 from start to finish, but that would entail lots of ugly, repetitive, and unwieldy code that would obscure the intent of the application logic, and in general be very contrary to how the React toolkit was intended to be used, thereby almost defeating the whole purpose of using React at all. React was meant to be used entirely with ES6/JSX.
So I’ve already spent several paragraphs talking about how React is so different from all the traditional JavaScript toolkits, but I haven’t even begun to talk about what makes it fundamentally different, or why it’s really so cool. It’s not just because of ES6/JSX (which has analogies elsewhere), but because of React’s revolutionary widget rendering philosophy, that is unlike that of any traditional JavaScript toolkit that came before it.
So what is this revolutionary widget rendering philosophy you may ask, and what’s so great about it? Well, whenever there is an application state change that effects how some widget / app component should appear, React will logically re-render that whole component and all its children, even the parts that did not change, while apps based on the traditional JavaScript toolkits will attempt to leave most of the component hierarchy untouched, and change only what should actually change for the new state. So doesn’t re-rendering everything sound wasteful and inefficient, and what is there to gain from it?
So the gain with this complete re-render philosophy is that it is no longer necessary to manually write and maintain special cases for partial widget re-renderings, which can require quite a bit of logic, and result in a lot of code-bloat and all sorts of needle-in-a-haystack bugs. So the overall code is much cleaner, quicker to write, less buggy, easier to maintain and understand.
OK, but what about efficiency? And we don’t want our UI to flicker like an analog TV every time it re-renders! And this is where the key React innovation comes in, and it has a name — Virtual DOM.
Virtual DOM is an off-screen internal React in-memory logical representation of what the actual browser DOM should look like after the render, and by being “virtual” it is much faster than the real thing. The fast virtual DOM allows copious renderings and re-renderings without sacrificing efficiency or causing screen flicker. Whenever the virtual DOM changes, React analyses the changes, compares them to the previous state of the virtual DOM, and then updates only what is necessary to the actual browser DOM.
What this means, is that through the wise use of this virtual DOM, React has freed us developers from having to manually implement the nitty-gritty details of partial widget re-renderings. Now all we have to do is declare how our widgets / app components and their data readouts should look like with the JSX markup, and React takes care of the rest, saving us lots of time, and saving our project budgets even more money!
React has essentially eliminated the most mundane, time consuming, and expensive aspect of traditional front-end development.
The most confusing thing about this least confusing thing is that we are actually going to be using not 1 but 2 React toolkits for our front-end development. We’re going to be using (1) the core React toolkit, which implements this wonderful virtual DOM, and we’re also going to be using (2) the distinctly separate React DOM toolkit, which is the comparison and translation logic between the virtual DOM and the browser actual DOM. We’re going to be using React DOM because we’re developing an application that will run in web browsers.
There are also several React Native toolkits for installable UI applications intended to run natively on various operating systems, but that’s beyond the scope of this tutorial. However, the key take-away here is that the core React is not specifically a front-end toolkit, but a platform-agnostic UI toolkit, applications built with which can also be deployed onto the browser front-end.
React was born out of the need to simplify, accelerate, and standardize UI development. The plethora of traditional toolkits before React failed to crack the chronic problem of UI logic exponentially growing in complexity with the size of the application, becoming unwieldy, as developing difficult dilemmas such as circular dependencies and race conditions.
React and JSX simplify front-end development with its declarative (markup) rather than empirical (traditional procedural) approach to building UI components, automating partial re-renderings, the capability to reuse UI logic client-side, server-side, and on numerous operating systems, and maintaining a common set of philosophies and programming conventions that allows large groups of developers to quickly understand each other’s code. React can be used reliably to build very large applications that remain easy to manage. All of this significantly reduces development time and therefore development costs.
Earlier I mentioned that developing with React makes little sense without also developing with JSX and ES6, and that that requires an additional transpilation step for deployment to the traditional ES5-compatible web browsers. So when starting to build a React application, not only must we prepare the basic React boilerplate, but we must also prepare the infrastructure for our transpilation. And this infrastructure actually requires a lot more work than just the React-specific logic, and we must implement it first before we write any React-specific code.
Facilitation of the transpilation of our React logic will require the direct use of 4 additional software tools, which are:
So the take-away here is that not only are we going to be working with React, but we’re also going to be working with these 4 additional systems and all their dependencies, which is just another one of the many things discussed above that can make starting React so disorienting for a beginner. Perhaps no other front-end toolkit out there requires this much additional tools, but on the bright side, all these tools are based on a common language and operating environment.
The Node.js is the runtime environment inside which all of these 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 installation instructions for Node.js are here on their website.
I personally am currently running Node.js version 0.10.25 on an Ubuntu 14.04.5 LTS virtual machine which I’m using as my development environment to write this tutorial.
So install Node.js as the first step of this tutorial. This has been documented in the example repo commit 08ecf493b24fdb5db033f34ce097016f6be6a38b
.
The NPM package manager will be used to install Webpack and Babel, and if you installed Node.js via your OS package manager, then NPM should have also been installed as its dependency. To verify that you have NPM installed, run npm -v
inside your console. I’m personally currently running NPM version 1.3.10. Refer to the installing NPM page if it is somehow missing in your OS environment.
This has been documented in the example repo commit e7d1591e11af6bf581f3cbb066438788189388d7
.
The Webpack module bundler will manage the whole build process for our app, including running the Babel transpiler, so we need to install Webpack 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. Run the command: npm install webpack
This is documented in the example repo commit 5ca0a2cfd30ebd95f7fc3093c58cfb69653ec394
.
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 29 MB on the 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 .gitignore
with node_modules/
in it. This is documented in the example repo commit c23d5ada34a5cc362a9ba2653d21421c10979de4
Now that we have Webpack installed, we could immediately continue to installing Babel, but not just yet. First, we’re going to introduce some very basic Webpack 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 webpack_out
. Our primary input files will import secondary input files, but since Webpack will start the packing with the primary files, and that’s also where the JavaScript logic interpretor will start when our code is finally executed, we will call our primary files Webpack entry files.
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 c636c0efbec34a743aa10017e886705973718c49
.
OK, so now we have our basic JavaScript entry file. Next, we must tell Webpack to grab it and spit-out 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 webpack.config.js
. As can be deduced by its file extension, the configuration file itself is a JavaScript file executed by the Node.js runtime environment. I have prepared this file in the example repo commit 400ee13e7a5fc28b6ce48409a5daabaf3f35f9c3
.
As you can see, the configuration logic is only 8 lines. First, 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
. And that’s all there’s to it for now!
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 06613509b6f1d4c23a281c3aa35ea83973d09f72
.
From this point on it will be necessary to re-bundle our JavaScript again and again every time we change it. This is one drawback to building React-based applications. However, we can save ourselves the trouble of typing the Webpack console command each time by having Webpack “watch” our input files for changes, and re-bundle automatically when changes are detected. This is done by adding the --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
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.
Since we just generated our output JavaScript, we’re going to have a new file webpack_out/minimalistic_react.js
. This file is currently 1765 bytes on my system, while the input file webpack_in/entry.js
is only 257 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 documented in the example repo commit bcf8a4a58742658d022074f4bf6228e6095bcc05
.
OK, so since now we have our generated JavaScript file, it’s time to test it with an HTML test page! I just added one with the example repo commit 3afefeaaccd784f3ebee96966cfc3e8a776f1be9
, and we can try it here.
This page will not look like much, as it should because it does not have much, but if we open the browser web inspector and look inside the console tab, we will see that our test page just emitted the string “JavaScript entry logic.” to the console. That’s exactly what we want to see, as that indicates that the logic inside that entry JavaScript file from step 5 has been packed by Webpack into its generated output, picked-up by our test page, and interpreted by the browser. Our basic Webpack configuration and build infrastructure up to this point is working!
The one crucial detail about this page is that it loads the generated JavaScript 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 <body>
tag. There is an important reason for this — when we ultimately implement our React app, it will need to render itself into its root container element, We need to load our React JavaScript after the browser renders this root container element to be able to find it from our React logic. Without it, we will not be able to render our React app. Had we had this <script>
inside the <head>
our test page would still work for now, but it would not be suitable for when we will need to work with React. So remember, always load your React JavaScript at the bottom of <body>
!
At this point our infrastructure is capable of processing only the regular browser-safe ES5 JavaScript, not the ES6/JSX we need to start developing with React. Webpack is just a packer, and it does not know how to translate from ES6/JSX to ES5 just by itself. To do that, it needs the Babel transpiler I talked about 10 steps ago. It’s finally time to install Babel!
Once again we’ll be using NPM, this time to install the package babel-loader
. The command for that is: npm install babel-loader
This is documented in the example repo commit bba196dd44ac9537fe65e8d2628a4b027f564d36
. At this time the size of my node_modules
subdirectory is 88 MB, and previously it was 29.
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 6 — webpack.config.js
. I did this in the example repo commit 9488a5cc787605c7debd7149e6540e3938315851
.
If you look at this configuration diff, you’ll see that all we’re saying is 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 682de42a68ac56041b9e0be7884559a1288f7404
.
Whereas I had entry.js
emit the text “JavaScript entry logic” to the console log, I made 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 just did this in the example repo commit 1ef8508d20f06d59acb79387bee143bebb54194e
.
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.
If you look in the browser console log, you’ll see that there are now 2 entries, the 1st for JavaScript, and the 2nd for JSX. This verifies that the logic in both of our entry files is reaching the browser, which is exactly what we want.
We’re almost ready to work with React, but still not quite there yet. As it turns out, to tell Babel that we actually want to translate ES6 and JSX, we must specify certain “presets“. These presets
are referred to by the tokens es2015
and react
. The former is for ES6 (as ES6 and ES2015 are synonymous terms), and the later is for JSX. Before we can even specify these presets in the configuration, we have to install their NPM modules, with the commands: npm install babel-preset-es2015
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 e3bde611c6be1efbc361073cf73e55d545a63f6a
.
My local node_modules
folder grew from 88 MB to 985 MB and 1.1 GB 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 1d3b2b61184b590bdcd0f9dc2cfb4e98c026a287
. As you can see this is another relatively minor addition of just 3 lines. In fact our whole Webpack configuration file is only 24 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!
The first thing we need to do is install the NPM modules react
and react-dom
with the commands: npm install react
and npm install react-dom
I have documented this in the example repo commit d4c86c7a00e2e2222c6d1cb18034a6b6b01a862f
.
After installing these modules, my node_modules/
folder grew from 1.1 GB to 1.2, so it appears that it is the Babel presets that are taking up most of the space.
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 in the React community. 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 id
of react-app
. I implemented this in the example repo commit 7c61c2d294842b464b7d078e2fc8deda65e6fde3
.
The most crucial detail in this step is that the root container element needs to be above the <script>
tag loading the React app JavaScript logic. This is so that this element is rendered before the JavaScript logic does that 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 9 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 b0f0210c96a0810a97267e42714a08de2886c7e3
.
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.
react
.react-dom
.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 <div>
we prepared in step 1. If we look into the web inspector console, we will see the line “JavaScript entry logic.” followed by “JSX entry logic.” This confirms that our entry logic has been transpiled properly.
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 webpack_out/minimalistic_react.js
is currently 749.8 KB (at least for me), and all we’re doing is rendering a single line! Server-side gzip compression, if enabled on the web server, can compress this down to 158.6 KB of actual download size, but it is still quite a hefty chunk. Obviously this bloat is not from our 3 lines of code, but from all the React logic underneath. The silver lining here is that this download size will not grow by much as we add more of our own logic to the app. Nevertheless, this is still unacceptable for a production build, which is OK, because this was a development build. Doing a development build included a lot of extra debugging logic into our JavaScript, and also left it unminified. We can significantly cut down on this bloat and minify things if we do a production build, which is what we’re going to do next.
So 749.8 KB raw / 158.6 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. We will tell Webpack to (1) define our build environment as “production”, (2) deduplicate any duplicate code, and (3) to minify the output ES5. There are some additional measures that can be taken to slim it down some more, but since this is a minimalistic tutorial we’ll stick to these 3 for now. No additional NPM installs will be required for what we’re doing. And I just committed the webpack.config.js
production configuration changes to ff37dff57479e3de5644abf82266ef640e8d53dd
. As you can see I added 9 lines, and it could have been even less.
Now that we have changed our Webpack configuration for production, it is time to run it again to do our production build. One caveat here is that the console command to do the build has changed. We must now include the -p
parameter. So our new build command looks like this: ./node_modules/webpack/bin/webpack.js -p --config ./webpack.config.js
I have documented this in the example repo commit 3e78c2ce168cc8845209fe6289370169ad380b32
.
Our new webpack_out/minimalistic_react.js
is now 140.3 KB raw size. That’s still not too small, but over 5 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 42.1 KB. That’s pretty efficient!
It can be argued that React has made the traditional JavaScript toolkits obsolete, and it can now also be argued that React DOM has in turn been obsoleted by some of its drop-in replacements that appeared over the last couple of years. What is a React DOM drop-in replacement? It’s a toolkit duplicating the React API and its philosophy of virtual DOM in the sense that it behaves just like React, but with a different implementation. Why would there be a need for such a toolkit? Well, as discussed in the previous section, using React in a web app can result in relatively large ES5 output file size. Preact is a React API clone that can be substituted into a React web app in place of React without having to rewrite any app logic, and provide the benefit of having much smaller ES5 output file size.
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 webpack_out/minimalistic_react.js
.
The first thing we need to do to get started with Preact is to install its NPM modules preact
and 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 a568dfc3424f562c8b1e516c154645183919e5ae
. Notice that I did not remove the instructions for installing React and React DOM. 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 1.2 GB before I installed Preact, and it was the same after.
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 8d0982d784bef591aaa112221d9217f3e5e1a989
. As you can see, I “alias”-ed Preact in. 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!
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 61.9 KB raw size. That’s less than 20 KB more than the gzipped plain React production build, and less than half of the plain React raw production build! 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 14.6 KB. Who needs production builds! Well, we still do, it’s just that our current minimalistic app only renders a single <div>
.
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 do the 3 additional steps outlined in step 1 of the React production section, and I just cherry-picked that configuration commit as 20f4fad7984c5afbbbe2446e8e1c48baa56ff367
.
Same as for a React production build, we have to modify our BUILD_INSTRUCTIONS
to pass the -p
flag to Webpack. I just cherry-picked that build instructions commit as 21a3028979d0973b9a696bd79d918f50610bd88a
. For formality here’s the full command: ./node_modules/webpack/bin/webpack.js -p --config ./webpack.config.js
.
And the Preact production output of webpack_out/minimalistic_react.js
on my system is currently 24.2 KB. If we test this build here we can see that our downloaded gzipped size is 8.7 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: npm install
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. 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 16e283e5c8ee38467f2561fcfa7ece025eeb4256
into the Preact development branch, and modified the BUILD_INSTRUCTIONS
in git commit 656d8ce29214328b4df47371cb35cb7b70578f60
.
Unfortunately, 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 16e283e5c8ee38467f2561fcfa7ece025eeb4256
, 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 the following branches:
master
— Common configuration logic and commits.master--package.json
— Just like master
, but with package.json
.master--production
— Just like master
, but with additional changes to webpack.config.js
and BUILD_INSTRUCTIONS
to produce minimized bundles intended for deployment to production.master--production--package.json
— Just like master--production
, but with package.json
.master--preact
— Just like master
, but with Preact swapped-in for React.master--preact--package.json
— Just like master--package.json
, but with Preact.master--preact--production
— Just like master--production
, but with Preact.master--preact--production--package.json
— Just like master--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 master--package.json
or master--preact--package.json
. When you want to deploy to production, fork-off a new release branch, and cherry-pick the production-optimizing commits from master--production--package.json
or master--preact--production--package.json
.
In this tutorial blog I started with the fundamental concepts React is based on, the intent behind it, and why it has emerged as the dominant UI framework. Then I went over the vital preliminary steps for setting up the needed infrastructure for developing React apps. I demonstrated implementing a very simple React app on top of that infrastructure, and I covered how to produce both development and production builds. I also talked about how the React API clone drop-in replacement Preact can make production builds even smaller.
I made a pretty detailed tutorial, breaking-down each task as a series of discrete steps, and explaining the purpose of each one. I minimized the number of these steps to just enough to build a minimalistic React app; however, one that can be reliably used as a foundation for building very large apps. I encourage the readers to use the example repo for this tutorial as a starting point for their own React front-end apps. Make sure to also explore the production, Preact, and package.json
branches of the repo.
So enjoy developing your very own React apps! And if you do, make sure to also know how Redux can be used to manage your application state!
Copyright (c) 2010-2018 Marat Nepomnyashy
Except where otherwise noted, this webpage is licensed under a Creative Commons Attribution 3.0 Unported License.
Background wallpaper by Patrick Hoesly, used under Creative Commons Attribution 2.0 Generic License.