Hot Reloading and Live Updates
Enabling Hot Reloading and Updates
Setting NODE_ENV=development
will tell Breko-hub to enable both hot-reloading and live updates. The unit tests ran in a browser are always hot-updated.
What are hot reloads and live updates?
Hot reloading - updating the application in a browser with new code changes as and when you make them. You'll see these changes without the need for a refresh. A socket server pushes code updates to your app running in the browser. A re-render of the application shows the code changes whilst persisting application state.
Live updates - code changes are available without the need for a restart or re-bundle. E.g. The server is aware of code changes and uses them on the next request.
What gets hot reloaded
Not everything can be hot reloaded in a single page application. Some parts of an application don't get read any other time than on the page load or cannot force a re-render.
Components are perfect for hot-reloading. When you change the render function of a component, it can be re-ran with the same props. The new markup is then rendered onto the page with minimal impact.
Routes are a little more tricky to be hot-reloaded but they are still components.
Reducers - Redux provides a which allows you to apply a new set of reducers to an existing store. This enables reducers to be hot-reloaded. Yet this utility needs to configured in the same place that the store gets created.
SCSS modules export objects of class names - components then use these class names. When SCSS module files change, the hot reload injects their new styles into the page. Any new injected styles overwrite the previous styles and display on the page.
You may have noticed a flash of unloaded styles [FOUS] when running the app in development. The server render does not include CSS module styles. Instead, Webpack loads the CSS module styles asynchronously. Async loading allows the CSS-modules to be hot reloaded. Production mode disables hot reloading so that the CSS-modules styles no longer FOUS.
Unit tests in the browser when ran in the browser through npm run test:server
are hot-reloaded. The hot-reload socket push of the updated code confuses mocha. The test runner doesn't execute any other files than the one you changed. You can run all tests again by refreshing the page.
What gets live-updated
API Routes - Code changes to API endpoints in the apiRouter
will force node to re-read the changed files. The next time the endpoints get called, they will use the updated code.
Utility Functions - Code changes get read straight away. But, their must be a new request or hot-update before the changes are visible.
Routes are actually hot-reloaded as they are still components. But as you will have your page loaded at one route, you won't see the changes until you navigate to the updated route.
Css imports - When not imported as CSS modules, the Extract Text Plugin extracts CSS into one bundle. The bundle file is not hot-reloaded as it's a static html link. The updated CSS code is available after a page refresh.
src/server - If you tinker with the middleware consumed by src/server/router
it will get live updated. The same for the socket connection within the server folder.
Tests in node - Unit and functional tests in node can live updated. If you supply the --watch
argument to either of the test commands, the tests will be re-ran on changes.
npm run test:unit -- --watch
or
npm run test:func -- --watch
What isn't live-updated
The server app instance must be a fixed reference for the listener that handles requests. If we were to live update this instance - it could introduce memory leaks when developing.
Files inside subfolders of the src
folder are either hot reloaded or live updated. Files not in subfolders are not hot reloaded or live updated.
Any files in the server that aren't for routing or socket handling should not be live updated. That means, any other middleware, such as compression, should not be live updated.
Cache deletes
There're a choice of different techniques for applying live updates to a node server. Most techniques involve spawning child processes and restarting instances on every code change. Another technique is to restart the whole server every time server code changes.
The way Breko-hub achieves live-updates to the server involves cache manipulation. Breko-hub deletes cache contained in node's require module on changes. The server is watching the file system for changes. When changes happen, a callback handler is deleting the node cache for that file. After a file's cache gets deleted, node will read the new content from the file system when requested.
Breko-hubs method is much faster than other live update techniques. The same server can stay open without needing to restart.
You can read about the live-update technique used by Breko-hub at the following link. https://tomatao-blog.herokuapp.com/post/server-side-hmr#hot-server.
Middleware
For client side hot-reloading, Breko-hub makes use of a combination of two middleware. One middleware compiles the client side Webpack bundle into memory. And the other listens for changes to hot-reload. This works with the HotModuleReplacementPlugin Webpack plugin. Also, the client bundle must include webpack-hot-middleware/client
to receive hot updates.
Isomorphic tools
Breko-hub utilises Webpack's feature for importing assets into component files. In a universal application, the same component files get used in the server render. The components generate markup that the server injects into the HTML response. Webpack isn't used to compile server code, so, it breaks when trying to import an asset such as SCSS or PNG. This introduces inconsistencies between code used on both the client and the server.
To allow importing assets on both client and server, Breko-hub uses webpack-isomorphic-tools
. The are two halves to the isomorphic tools, a server and a Webpack plugin. To integrate with isomorphic-tools, Breko-hub performs the following steps:
- The Webpack config makes use of Hot Module Replacement and Isomorphic tools plugins.
- The Webpack bundle gets configured to use
webpack-hot-middleware/client
. - The server starts but does not import any client-side files that use assets. These files are often component files needed for a server render. The server instead starts the isomorphic tools server.
- The isomorphic tools waits for a JSON file generated by the client-side first bundle.
- The server also starts a Webpack bundle of the client-side code.
- When the bundle completes, the isomorphic tools plugins will generate a JSON file. The JSON file contains a mapping between asset file names and strings of content for the assets. It generates this by reading imports made within the client-side bundle.
- The isomorphic tools server stops waiting when it finds the JSON file.
- The isomorphic tools callback executes, requiring the component files.
- When any component file imports an asset, the isomorphic tools server interferes. The isomorphic tools server prevents node from attempting to import the asset. Instead, the asset get looked up in the JSON mapping and the content from the mapping gets imported. The asset content is a simple string, such as SVG markup or a base64 image.
- The server can handle requests and use the content string in response markup.
Breko-hub also uses the JSON asset map to get the names of the .css
and .js
files generated by the bundle. This is important as the file names contain a unique hash. The unique hash isn't known until the bundle completes. The server won't know the bundle file names until the bundle is ready. The server uses the JSON asset map to build the link and script tags in the server render.
Isomorphic tools refresh
When a hot reload takes place, the Webpack middleware will also have updated the bundle in memory. The isomorphic assets map (JSON file) will update with any appropriate changes. For the server to be aware of theses changes, a watcher calls isomorphicTools.refresh()
. The refresh function re-reads the updated map and flushes cache for required assets. The next time the server handles a request, it reads the uncached assets.
CSS modules
CSS-modules are different to other assets because they generate objects containing className strings. Breko-hub makes use of css-modules-require-hook
which ensures css-modules work for the server render. Breko-hub has also added SCSS processing for CSS-modules generating class names. This functionality lives in src/helpers/cssModulesHook.js
.
Server Routing
To live-update the server routes, Breko-hub wraps it's server router in a function. The function gets invoked on each request when in development. The router function clears out previous middleware to avoid memory leaks. Once cleared, it re-adds the updated middleware to the server.
In production, we wouldn't want the server to clear it's middleware on every request. The router wrapping function gets invoked on the server start-up and never again. Each request doesn't need any live updates so we can utilise node cache.