Hot-reloading in 2018: updated and unabridged
using React + RequireJS (and Node.js, of course)
> a how-to guide and why AMD and RequireJS are currently best for the job.
Special update: You can basically consider SystemJS and AMD/RequireJS to have equivalent hot-reloading capabilities. The general opinion expressed in this article is that these two front-end module loaders are superior to Webpack and Browserify from the standpoint of implementing reliable and robust hot-reloading functionality. Throughout the article, you can basically substitute “RequireJS” with “SystemJS” and the meaning will remain the same.
page-refresh = live-reload
hot-reloading = hot-module-replacement = hot-swapping
The current ‘state-of-the-art’ for developer productivity on the front-end is something called ‘live-reload’. What this means is that the page/app will refresh when certain files are modified in the codebase. Live-reload, as I will argue, is a very suboptimal workflow, when compared with a true hot-reloading system. In fact, I would argue strongly, that hot-reloading is the #1 missing feature in the vast majority of web developers workflows, even in 2018. I will explain why that is, throughout this article.
Some questions for the reader —
- Why aren’t Angular5 apps, created with AngularCLI, pre-configured for hot-module-replacement?
- Why are Create-React-App apps not configured for hot-reloading?
- This is 2018, why do the front-end app frameworks that I am using fail to have out-of-the-box support for hot-reloading?
This is something to think about. You will find out why as you read on.
In the very beginning of my journey to survey hot-reloading in the world of web development, I was inspired by Dan Abramov’s excellent talks about hot reloading front-end static assets with Webpack and React — it inspired me to investigate whether it could be done with RequireJS. In short I succeeded in this, and below there is some code showing you how to create hot-reloading using a Node.js Express server application with RequireJS as the front-end module loader. There is no module or library to install — this article simply gives you a methodology/recipe to follow on your own. What it allows you to do is to take full advantage of the standard RequireJS feature of asynchronous loading of modules, to enable hot-reloading of front-end static assets, including React components, of course. Most developers do not really know what hot-reloading is and many developers will literally die without having used hot-reloading on the front-end — thinking with 100% confidence that a page-refresh is about as good as it gets. This is sad, please don’t let it happen to you.
Beyond the code in this article, I created an example project that showcases how to do hot-reloading with the same setup as this article (at some point I’d like to make this into a Yeoman generator):
Here are the big 4 advantages of hot-reloading:
- No page refresh means console logging is intact. A page refresh will destroy the logging statements in your web browser console, effectively starting an entirely new JS runtime; hot-reloading does not require a browser refresh at all — which of course means your logging statements are naturally preserved which makes general development and debugging a lot easier! This is also one reason why SPAs are better than server-rendered apps — we don’t experience as many page-refreshes which means it’s easier to debug the front-end.* This makes hot-reloading much better than “live-reload.”
- State in the runtime is maintained exactly as before the hot-swap started. You will not have to re-navigate to re-capture specific state in your app. SPAs are more stateful, which means that the user may have entered data into 12 forms before getting to a particular spot. When debugging, many developers have to click through all those forms to get to where they were, so they can test or debug the latest feature. This is a pain in the ass and a waste of time. Hot-reloading solves this, bigtime. Hot-reloading will not refresh the page and will not affect your app’s state (when done correctly) so you will be right where you were before the hot-reload, all 12 forms will still be filled out, which saves time and keeps you sane!
- Avoid those pesky interruptions — with live-reload, sometimes the browser will refresh out of nowhere as you were typing/interacting with your app. Maybe your IDE is configured to auto-save on an interval, and suddenly your app has refreshed despite the fact that you didn’t want it to. Reloading the page is extremely inconvenient at times, because it can interrupt your work unexpectedly.
- Hot-reloading is simply faster than an incremental-build + browser refresh. For example, using Create-React-App, an incremental build + page refresh takes about 2–5 seconds for a small app, and even with auto-refresh. That adds up over the course of a day. Hot-reloading a file takes about a half-second, max! It’s so fast that in my setup I added a setTimeout to slow it down so that the developer could see the results happen after they save changes to a file :)
*If you do have to refresh the page, and you cannot currently implement hot-reloading, I recommend sending your console logging from the browser to a log file on the developer’s filesystem and tailing that log file, which means you can reference logs from previous runtime sessions. I have never tried this but sounds like fun to implement lol.
RequireJS has an ace-in-the-hole when it comes to hot-reloading on the front-end. You will be surprised as to how little code is needed to get this to work with RequireJS, compared to how complicated it is to both accomplish and understand with Browserify or Webpack. Here’s the thing people: RequireJS does hot-loading out-of-the-box by design, in production, and has been doing so for a very long time — this is because it has asynchronous loading or ‘lazy-loading’ as a primary feature. You may have noticed there’s a small trend of creating multiple bundles for single-page-applications so that not all your code loads on page-load. RequireJS got it right the first time by building a model for front-end web applications which load assets asynchronously and on-demand.
So there are two primary reasons that RequireJS is the best module system for implementing hot-reloading:
- RequireJS is an asynchronous module system and is designed from the ground up for lazy-loading/hot-loading of modules.
- In development mode, RequireJS/AMD works in the browser without a build step, unlike Browserify and Webpack. We will see this makes hot reloading much, much simpler.
So RequireJS does hot-loading, but how do we do hot RE-loading? Well, we use RequireJS’s native ability to delete the cache for a module, and then we re-require the module from the filesystem. You will learn much more by reading on, and may be surprised by how straightforward this is.
hot-reloading — the same as hot-module-replacement or hot-swapping; in this case it’s for web front-end static assets; I’d like to draw one distinction between ‘hot-reloading’ and ‘hot-code-push’ — HCP is when you push live updates to your users’ devices. So basically, HCP and live updates are for production releases and hot-reloading is intended primarily for development, even though they each share similar results. Some people also draw a distinction between code that is hot-swapped and code that is eval’d. In this case, the former is the case, but I am not sure if that distinction is a useful one.
Webpack — a module bundler that is agnostic about module format — works with AMD, CommonJS and ES6; exceptionally good at deploying different optimized files for “multiple entry points” to a single-page-application. If you want to get started with Webpack, Pete Hunt’s how-to is probably as good a place to start as any. I personally find the “multiple entry points” description a bit misleading. By “multiple entry points” we are still talking about a SPA (single-page-application), it’s just that we aren’t anywhere near finished loading static assets upon the first page load — so IMO there’s only one “entry-point”, which is page load, but there are other points in the application where new static assets are incorporated into the runtime. *Edit 12/12/2016, ok this original part is not 100% correct. With Webpack we can build SPAs or server-rendered apps, Webpack is agnostic about that. So we can have true multiple entry points with server-rendered/multi-page apps, or pseudo-mutltiple entry points with SPAs + code-splitting.*
hr4R — an example project I created to demonstrate hot-reloading with RequireJS
All due respect to Browserify — I don’t see any reason to use Browserify now that Webpack is on the scene, because Browserify focuses on CommonJS and is also focused on creating one deployment file. After about 800KBs or so, most developers find the file to be too big to parse and execute, especially on mobile devices. If you wish to work with Browserify, there is this module which you can try out to hot-reload React components-https://github.com/milankinen/livereactload, but this is not the same as hot-reloading.
In his talks, Dan Abramov emphasizes the importance of improving your workflow before anything else. DX (Developer Experience) is a thing now. Hot reloading front-end static assets is a big part of improving DX and is intended to save you serious amounts of time that you would otherwise spend in frustration, endlessly iterating through front-end design permutations — waiting for builds to complete, and clicking refresh or waiting for refresh and re-navigating to re-capture the same state you had before you made the change — (perhaps only to find a runtime error with your JSX!).
Hot reloading exists so that you can hot swap the minimum amount of code to see the change, so that changes can be made to your program without affecting your program’s state — it is supposed to allow you the developer to completely avoid the need to refresh the page and re-navigate to where you were in your app to recapture state to see how the changes that you just made took hold —merely saying it with words takes too much time.
It might take 2–10 seconds to wait for a rebuild, click refresh (or wait for auto-refresh) and worst of all, re-navigate to where you were. Or in that time you could have started reading Quora, Medium, Twitter or Facebook and completely lost focus. You must stay in the flow state! Do not interrupt the flow!
Hot-reloading has nothing to do with Nodemon, it is not Forever, it is not PM2 or Node-Supervisor, it is more powerful than that for front-end development, simply because hot-reloading “eschews the refresh” — all you need to do is save a file and it is transported to the browser so that you can see changes in near real-time.
As Dan says in his videos, hot-reload functionality is supposed to make life better for devs and take some of the frustration out of front-end development — to save us from long front-end build times and endlessly clicking through our apps in development cycles.
My belief is that Browserify and Webpack (the two latest and greatest front-end modules loaders) have put us further away from the dream of universally understood hot reloading for front-end development, not closer! —
as I will describe, the RequireJS / AMD spec was practically made for the job. I will give you enough information in this article to figure out how to hot reload JS, HTML and CSS on your own, even if you don’t use the exact tools and modules I use here, the concept will resonate. Also, there is the example project, aforementioned, which you can check out at your leisure.
This article and the code laid forth herein is for anyone who:
- uses RequireJS (with React, Backbone, Angular, or whatever) and wants to improve their workflow
- hasn’t decided on a module system and wants to know the pros/cons
- enjoys DIY instead of blindly configuring someone else’s work
- wants to use React and needs a good accompanying module system, React + RequireJS is a perfect combination, just like Backbone + RequireJS!
- wants to have hot-reloading already! ya basta!
I hereby present the solution to a big chunk of your front-end problems: HR4R — hot reloading for RequireJS. As implied, the benefits of hot reloading with HR4R are:
- you can see JSX transpilation errors as you write the code — the module is being loaded and run in the browser in real time which allows you to see errors long before you otherwise would
- you can cycle through different CSS styles and settings and immediately see what the results are in the browser without having to leave your IDE, a designer’s dream.
- you can change your HTML templates and see the results immediately, no clicking refresh! The browser is in the same exact state except for the template file being swapped out, and render being called. (Assuming you’re still using some HTML templates with React — I am.)
- You can see what your React render functions return in your browser by just saving the JS file in your IDE after making some changes.
- Any view file (.js or .jsx) can be hot loaded —( but any files representing state cannot be hot loaded — Dan is working on this problem.)
As for Webpack’s own React Hot Loader, Dan Abramov seems to have given up his project (and I honestly don’t really know why):
It seems that Dan has been working against, instead of working with, the tools at hand. As far as I can tell, Dan is working against Webpack and he is working against Babel. Why not use a more straightforward module loader and not bother with transpiling ES6 with Babel? Hot reloading with Webpack seems difficult and is a “hack” according to Dan — (although I take his words with a grain of salt — it doesn’t seem like a total hack to me — especially considering that Webpack was designed for hot reloading before Dan arrived). Nevertheless, from my perspective, trying to get hot reloading to work with Webpack (or Browserify — good luck with that) seems like an uphill battle, even if Webpack was designed to handle it — there is so much more code involved in getting it work than the code I will show you to get it working with RequireJS. If I had to pick between hot reloading and ES6, I would choose the former.
So I’d like to introduce a system I created to hot reload JS, CSS and HTML templates into the browser using RequireJS, Node.js, Gulp.js file-system watchers and socket.io. I have tested it with React and Backbone. It is not a module to install, it is merely a methodology to follow. I am not sure if it would work with Angular, Ember, Durandal/Aurelia, but chances are that it could with some tweaking (although many frameworks have their own module system — so you’d more likely want to use Angular and Ember to reload the modules instead of RequireJS — even though I have heard it is best practice to use an external module loader / build system). I will go over the higher level procedures regarding how hr4R works, and then we can dig into some code. I created one module (requirejs-metagen on NPM) that you can use to require entire directories of files, but otherwise it’s up to you to implement this. This is a good thing. You can see conceptually how this can be done, and copy some of the code, but you get to code it yourself so that you actually understand it.
- a filesystem watcher process (gulp.js in this case)
- websockets (socket.io in this case)
- a browser, woo-hoo!
- a module system like RequireJS helps, but probably not strictly necessary
- a way to update all references to the new module in your front-end program (it’s easy if you design your program well, as I will show you how to do); we can accomplish this by re-evaluating require statements in render functions — most of the time the value is cached, but when a hot a reload occurs, we force the new value to be referenced, instead of the old value.
Here are the high-level steps we take (details to follow):
(1) in your gulpfile.js at the root of your project, we add a socket.io server listening on port 3002 - your gulpfile is now your dev server! congrats
(2) in your gulpfile.js, we use gulp.watch to watch for filesystem changes in particular directories.
(3) upon a filesystem change in a particular directory, the socket.io server running in your gulpfile.js will send a message to the browser with a string, representing the path of the file that changed; your front-end code needs to be able to handle that message, and (re)require the file that has changed.
(4) somewhere in your front-end code, preferably in it’s own module, add a socket.io connection to localhost:3002 (you can do a check for what environment you are in — and only make the socket connection when you are in development — assuming you don’t want hot reloading in production.) And you shouldn’t have to worry about CORS when it comes to making socket connections to multiple servers, if you already have a socket.io connection to some server running somewhere.
(5) Configure all your views to refresh references to any templates, child views, or CSS that it would need to render. No need to mark files with metadata — a view is “@hotifiable” if it is able to refresh the references. To configure your views to update references involves using this syntax in RequireJS:
also described here:
anyway, synchronous require calls in RequireJS / AMD look like this:
So, once your application is loaded in the browser, nothing happens. There is no polling, nothing of the sort! Hot reloading starts with you hitting ctrl+s or ctrl+z on your keyboard when editing a file that is being watched by Gulp (I use Windows at home and a Macbook Pro at work, and I remapped my keys on my Mac to match Windows — I still don’t understand how most people hit the command key on a Mac — it’s in the wrong place). The change event is fired (if there is actually a change to the file) and now you have the filepath of the file that changed on the front-end, because socket.io pushed it there, and it’s up to you to do something with it.
Here’s the code for parts (1), (2) and (3) with comments:
Here is the code for part (4)
Here is where the magic happens — require.undef() is the call that deletes the cache and forces RequireJS to re-require modules from the filesystem:
Here is an example of nested React components that get updated. If you have a view showing a top level React component and you update the file for the nested component in your IDE, it will all get hot-reloaded. The key is (1) to ensure that the child views are loaded before rendering the parent view and (2) re-evaluating the reference to the child-views in the render function, with the synchronous require call. This is all made very easy by RequireJS:
So here are the subtle tricks to getting hot reloading to work with RequireJS:
- require.undef() as per: http://requirejs.org/docs/api.html#undef
- using synchronous loading of templates, child views, etc, in render functions so that references are updated / re-evaluated every-time render is called. Key point: render() needs to be synchronous in React so that you can return a valid React component, and in Backbone it’s a convention to return ‘this’ in the render function, so in both cases we want a synchronous render function, which means we can’t make asynchronous require calls in render functions. You can read about synchronous require statements in RequireJS here: https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#sync-require
- to ensure synchronous loading of modules in render functions, you need to load all necessary modules for that view before calling render, but also re-reference them in the render function of React components, otherwise the old reference will be used.
One thing left to say — if we want hot reloading to be easier in the future, we may need an asynchronous/on-demand module loading system for ES6, a la RequireJS. I don’t believe Browserify or Webpack make hot-reloading as easy as it should be, although I am going to have to give Webpack a fair shot.
If you have any questions don’t hesitate to leave comments or email me at email@example.com. Thanks for reading this. Spread the word. Hot reloading should be available to all front-end developers, especially those who do design work.
If you enjoyed this tutorial - it would be helpful if you could click the recommend button and share it with other developers.
“Eschew the refresh”, my friends :)
:: More info if you want it ::
If you wish for more information or background, I edited everything below this out of the article because it was getting to be too much, but here’s more info:
Are there pre-existing solutions?
Despite the perceived benefits, hot reloading front-end code is not universally available and support for it appears spotty, as if it were always a secondary goal for front-end frameworks. It was actually fairly hard for me to find good information on this whole thing. Frameworks like Angular, Ember were probably not designed for hot reloading out of the box (please correct me if I am wrong), and hot reloading for libraries such as Backbone and React was nowhere near in scope for those projects. The only semi-official support for hot reloading for any major web framework that I know of is Meteor — http://info.meteor.com/blog/hot-code-pushes — Meteor added hot reloading support as early as 2012, but as far as I can tell it is deprecated, or at least depreciated, as supporting hot reloading was too difficult due to Meteor’s isomorphism (think: with an isomorphic system where code between server and client is highly coupled — how would the system easily know if the code you changed only affected the client? If it possibly affected the server you’d have to restart the server — see the problem? The problem is that Meteor server restarts can taken up to 30 seconds — they are working on it!). Here’s a direct quote:
“So, hot-pushes aren’t getting enough love in meteor imo. Meteor guys are trying to make things fast in their build process, but it isn’t fast enough yet.”
Furthermore, hot reloading is also not universal because it pretty much requires universal browser support of websockets, something we didn’t really have even (3?) years ago, thanks HTML5 and socket.io!
Some problems with other systems and hot reloading:
- using Angular “source maps stop working when using Webpack’s hot code reloading, but it’s a price I’m willing to pay.”
- feel free to let me know if you know of any issues with your system’s hot reloading mechanism.
- AngularJS doesn’t seem to have asynchronous loading capabilities, which is why many developers have paired Angular with RequireJS, which produces some pretty hairy looking code, frankly, but hopefully works for them. (Without async loading, hot-reloading is much harder).
Those sound like real issues with vanilla Angular1.x and Webpack. So what if we used RequireJS instead of Webpack?
The socket.io mechanism to do hot reloading described in this article should apply for most module loaders, but it will be easier to execute if you have a module system that was designed from the ground up to load new modules on the fly.
The only two complaints I ever hear regarding RequireJS:
(1) too much ceremony with the define statements
(2) AMD makes it difficult to share code with the server.
my answer: As I said, isomorphic JS is overrated. Now I do realize we are firmly in server-side-rendering-React-land territory. I haven’t bought into that yet. (Why are we doing tons of CPU intensive work on a Node.js server if we could delegate that to hundreds or thousands of clients, again?) If you want some isomorphism, it’s extremely easy to convert CommonJS to AMD. (The CommonJS → AMD conversion has been automated for you already.) However it’s more work to convert AMD to CommonJS, probably two minutes of manual time per module on average. But as many have pointed out, how often do we really need to share whole modules between the server and client — not often — having JS on both the client and server is useful enough, we don’t need it to be exactly isomorphic.
Even though we might need to run a build with RequireJS to transpile JSX, Sass/Less, or whatnot, if we need to load just one new file, we can do that without the need to rebuild and redeploy the entire project, which can take at least a couple seconds with Browserify or Webpack. Even with incremental builds and watchify, it can still take several seconds to build Browserify and Webpack — with RequireJS, your code will be hot loaded in much less than a second — I actually include a small delay (setTimeout) so you can swivel your head and see the confirmation of a successful reload.
In development, RequireJS generally advocates for a 1:1 relationship between module and file, and does not want or need a build system. Whereas Browserify and Webpack will need (incremental) builds to get new modules into the air. This is the web goddamnit: I thought we were dynamic and left the slow compilation and building to C++ and Java? In development, this may mean more HTTP requests, but in production you will bundle your RequireJS app. If you design your router in your front-end application to only load views on demand, I promise you that there will only be about 5–10 HTTP requests per view change which is nothing really. In production, if you designed your RequireJS application optimally, each view change will require a maximum of 1 HTTP request to retrieve new HTML/CSS/JS, because it has been bundled for production.
Plus no source maps — in development mode the AMD code in your browser is the same as it is on your filesystem. It couldn’t be simpler. I can just see in my mind’s eye all those devs out there waiting for their Webpack or Browserify build to complete so they can hit the refresh button :) This is a chance at an upgrade, my friends.
We are seeing two ‘new’ trends in web development, with React, Webpack, etc:
- server-side rendering (again)
- Webpack style SPAs whereby multiple bundles are used to deliver a large web application, where a single optimized .js file might just be too damn huge, especially for mobile.
RequireJS has no clear advantage in being used for server-side rendering (TMK), however if you think carefully about the second trend, you might realize that we are coming full-circle on this one, just like we are with the first. It used to be (not too long ago) anathema to modern web development to require JS, HTML or CSS on the fly once your app was loaded. However, Webpack is basically saying — for big web applications or for mobile or both — we need that — we can’t bundle everything in one big .js file for the first page load. So as you can see, RequireJS’s original intentions of asynchronous loading of files throughout the lifecycle of an web application was not inherently flawed, at all. A full-page-reload is bad, that’s agreed on, but asynchronously loading new files on demand seems like a good thing.
*It turns out that in production, RequireJS modules are primarily loaded synchronously, they only use the asynchronous syntax, because that’s what you used to run the code in development. Furthermore, in development, as long as a given module is known to already be loaded, you can use synchronous syntax to load a module synchronously, and as you may have noticed, this is one of the secret sauces that allows us to do hot reloading with RequireJS and React (or Backbone) together. RequireJS is super cool and major props to James Burke for making something that was very well designed. I intend to continue using RequireJS as I don’t really see any downsides to the format. When Angular 2.0 rolls around I might start messing with ES6 and TypeScript, but again, I am in no rush.