Hot Reloading Everywhere
A presentation by Christophe Porteneuve at Confoo Montréal 2018
https://joind.in/talk/1cd00
A presentation by Christophe Porteneuve at Confoo Montréal 2018
https://joind.in/talk/1cd00
const christophe = {
family: { wife: 'Élodie', son: 'Maxence', [Symbol.guess]: '*Shh*' },
city: 'Paris, FR',
company: 'Delicious Insights',
trainings: ['Web Apps Modernes', 'Node.js', 'Git Total',
'ES Total', 'Webpack'],
firstTimeInMontreal: true,
lastTimeInMontreal: Symbol.noWay,
webSince: 1995,
claimsToFame: [
'Prototype.js',
'Ruby On Rails',
'Prototype and Script.aculo.us',
'Paris Web',
'NodeSchool Paris',
],
}
We want to avoid having to reload the full runtime
(web page, Node server, Electron host…) unless absolutely necessary.
We want to see the impact of our code changes instantly, the moment we save.
We want a fast workflow with instant feedback.
In a perfect world, we’d have this.
A well-known dev principle that means you can hot-replace running code in a runtime.
Possible in a number of runtimes, sometimes inherent to a language (Lisp, Erlang, Smalltalk, Elm, Elixir… VB6 *cough*).
VS has Edit and Continue for C# and VB.NET, too.
For CSS and images, browsers have been doing it forever, it’s a simple DOM update.
For JavaScript, this is currently exclusive to v8 (Chrome·ium, Safari, Node, Electron…)
For CSS and images, same thing as hot swapping.
For JS, entirely different: we re-run the script in the current host context (running web page, Electron host…). Not very useful in Node.
This comes with a number of gotchas we’ll highlight later.
Usually makes more sense than HMR.
Unfortunately not supported by all IDEs / editors:
Chrome DevTools is great
WebStorm 2017+ is doing alright (well, sort of)
VS Code / VS don’t hot-swap Node 😢
--inspect
(or kill -USR1
its process).chrome://inspect
instead)
Major caveat: currently breaks on hard file save 🤔
Code that runs in a browser
There are a variety of tools we can use, not all of which handle JS well
CSS / Images: all browsers let us play
JS: because v8 only so far, only Chrome·ium / Electron (not Safari, Firefox, Edge)
Workspaces let us persist, again.
Oh, friend.
LiveReload is ancient
It requires a dedicated server
It has very different models depending on OS
It doesn’t hot-swap anyway
It’s pretty cool
Its core goal is not hot reloading
It doesn’t hot-swap anyway
Still, it’s a neat tool. You could blend it in your dev infra to do its thing: sync user interaction across browsers.
Whoa, you’ve been around the block, haven’t you? 😎
Small Chrome ext + (embeddable) server. Was the first option for hot-swapping build-step JS code. We could edit our files anywhere, restoring Editor Freedom™.
I demonstrated it 3+ years ago, and it was cool.
DevTools filesystem mapping now detects file changes, hot-swaps automatically: we don’t need fb-flo anymore. Not maintained anyway.
What most people refer to as hot-reloading these days.
This basically means Webpack*
Other tools either rely on Webpack for this (Grunt, Gulp)
or have a kludgy implementation (Browserify)**.
Relies on a client/server system: not bound to any editor.
Per-module, hence super fine-grained.
Serve with either webpack-dev-server in hot mode,
or a custom server that embeds webpack-dev-middleware
and webpack-hot-middleware
.
Detect on the client side and enable:
if (module.hot) {
module.hot.accept() // This is called a “self accept”
}
Keep an eye on the console for WDS/HMR notices.
A word about the parts of the dependency graph being hot-reloaded…
HMR re-runs your modules, so… we lose it?
Like, in-memory state from active instances? (React/Vue/Angular/etc. components, etc.)
Well, by default, this would be scratched. Also, lifecycle mayhem may ensue.
So you would need a framework-specific layer on top.
Luckily, some such layers exist!
It was a bit involved:
"plugins": ["react-hot-loader/babel"]
entry: {
main: [
'react-hot-loader/patch',
// …
]
}
import { AppContainer } from 'react-hot-loader'
function renderApp(Component) {
ReactDOM.render(
<AppContainer><Component/></AppContainer>,
document.getElementById('root')
)
}
renderApp()
if (module.hot) {
module.hot.accept('./App', () => {
renderApp(require('./App').default)
})
}
Much simpler for 99% use cases:
"plugins": ["react-hot-loader/babel"]
import { hot } from 'react-hot-loader'
// (App component here, be it SFC or class-based)
export default hot(module)(App)
Anything based on vue-loader has auto Vue-specific HMR 🎉
However, due to Vue’s current handling of <script>
tags and component lifecycle hooks, this still loses state on script change (template / style changes are fine).
This seems like something that could be improved with good heuristics. Not slated yet though.
Angular (and yes, we mean latest Angular, not ol’1.x) doesn’t lend itself well to per-module HMR.
Everybody either doesn’t do HMR or respawns the entire app tree in the page, which seems only marginally better than full-page reload IMHO.
See this article. It’s the most-readable version of this dominant solution that I could find.
If someone has a kick-ass solution available, come talk to me later! 🙏🏻
I looked really hard. I asked the team. No dice.
Here’s a Canadian (Vancouver) Tomster as consolation:
Christophe Porteneuve
Slides are up at bit.ly/confoo-hotreload
Code samples are on the GitHub repo