-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(blog): added first draft of hsr post
We don't have a way to display this yet, but there will soon be one.
- Loading branch information
1 parent
cbf2629
commit 78fef13
Showing
1 changed file
with
26 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# HSR vs HMR -- How Web Development is Getting an Upgrade | ||
|
||
In v0.3.4, Perseus includes a new, revolutionary feature: *hot state reloading* (HSR), a Wasm improvement to JavaScript's *hot module reloading* (HMR). But what exactly is this new tech, and what are the differences between it and HMR? | ||
|
||
Well, to understand this, we first need to understand HMR. For those who aren't familiar with the JS ecosystem, it's a feature that's part of Webpack and a number of other JS bundlers, and it basically enables a single part of your codebase to be swapped out in the browser, meaning the rest of your code stays the same, meaning your app's state stays the same, meaning you can continue to work in development without having to be thrown back to the beginning of your app. To illustrate this, let's take the following workflow: | ||
|
||
1. Code a login page that works in two stages, the first with a username/password entry and the second with a two-factor authentication code input. | ||
2. Start testing this and get to the 2FA screen. | ||
3. Realize that you misspelled the input placeholder (or some other error) and go back into the code to fix it. | ||
4. The build tool detects the code change, rebuilds, and updates the browser. | ||
|
||
The big question for developer experience here is this: is the developer thrown back to the username/password input, or do they retain their place at the 2FA code input? While this may seem insignificant, this problem very easily scales to become a huge problem, especially in large apps with a number of moving parts. Most web developers that have used a non-HMR framework in the past will have some horror story of having to put in the same inputs dozens of times while they debug and try to fix some strange error three screens into a loging flow or the like. | ||
|
||
HMR was invented to solve this exact problem (as well as a few others), and the way it does it is interesting. With JavaScript, your code can be split into *chunks*, which is a complex and nuanced process that I won't go into here, but it essentially means that the minimum amount of code is served to users' browsers to get some part of your app to work, and then more code is requested as necessary (potentially even predictively). This means that we can swap out just one of those files when some code in it (or some code in another file that ends up in that chunk by bundler magic) is changed, while leaving the rest of the code untouched. This avoids having to perform a full reload of the page, and it crucially *keeps your app's state*. | ||
|
||
But, over in the Wasm world, this isn't feasible on a large scale yet. Wasm code splitting (chunking) is still in the early stages of throwing ideas around, and, while it can be done manually, it's inefficient and introduces unnecessary code bloat. Wasm also has the added benefit of comign to the browser already compiled, unlike JS, meaning there's less overhead in using Wasm, which is why it's a fallacy to compare JS file sizes to Wasm file sizes, when in reality they're more comparable to image file sizes (a 300kb JS bundle would make developers shiver, but a 300kb image is perfectly normal, and much faster to load because there's next to zero compilation overhead). | ||
|
||
So, the upshot of all this is that HMR in any kind of automated and efficient way is insanely hard in Wasm, to the point that (to my knowledge) no-one has actually created a system that can do it reliably and without extensive manual configuration. This means that we're stuck with just performing a full reload of the webpage whenever the code changes, meaning the app's state is lost forever, and those horror stories come right back. Or does it? | ||
|
||
Around the end of 2021, I had an idea about making the concept of page state in Perseus much more powerful, by adding the ability to make it reactive. After a good month of work, v0.3.4 was ready with all these features, and, along the way, I had another idea: **if developers are going to have their entire app's state inside a reactive object, what if we serialized that to a string?** It turns out, that's only a few dozen lines of code, and that's Perseus' game-changing principle of *state freezing* and *state thawing*: the idea that you can take your entire app's state, turn it into a string, store it anywhere you want, and use it to restore the app to a previous state at any time in the future. | ||
|
||
So what if we combine this with live reloading? What if Perseus, when it received a live reload instruction from the CLI, froze the state, stored it in IndexedDB (built-in browser storage), reloaded the page, and then thawed that state? Well, we get what I've termed *hot state reloading*, a full restoration of the app's state. So, how does this compare with HMR? Well, for starters, it's more resilient, because we don't have to worry about what's in a chunk with what, your app's state can be restored in every single case, except when you change the data model for a page (e.g. adding a new property or the like, which would make deserialization impossible). That's the main advantage, but there's also the obvious positive that this is *far* easier to implement than HMR. For frameworks that have app state represented in a single place, this should be a no-brainer in my opinion. No bunders needed, no magic needed, just a bit of stringifying. | ||
|
||
But what about development speed? One of the big advantages of JS frameworks over Wasm frameworks right now is the faster iteration times, and that probably won't change anytime soon. With `perseus export -sw` today, the development pace is extremely speedy compared to other frameworks (and much faster than Perseus once was), but it still doesn't come close to the performance of a JS framework that doesn't need to 'compile' at all. But what about the latency between HMR and HSR? Well, they're basically exactly the same. While HMR may not look like it's triggering a full reload of the page, HSR's full reload with perks is as fast if not faster, since the Wasm bundle can be instantiated more quickly than even a smaller volume of JS code, and since Rust is just objectively faster than JS, especially on the server. Additionally, literally zero server-side HSR-specific computation is needed. In fact, if you were to inspect the WebSocket communications between the Perseus CLI and your app in development, they would have no payloads! That is how simple HSR actually is, and yet it can save a huge amount of developer time and effort. | ||
|
||
To summarize, HSR is an improvement over HMR in a myriad of ways, not least because it's far easier to implement, it's build-tool agnostic in terms of implementation, and it's more resilient than even the best HMR implementations. It's also more versatile, since it can be implemented in any language, regardless of code splitting ability, and it provides a feature that's been sorely missing from the Wasm ecosystem since its inception. With the release of this post, HSR is available for use in Perseus today, and I sincerely hope that other frameworks will adopt and improve upon this technology, so that, together, we can improve developer experience in web development to deliver meaningfully positive software to users across the world more efficiently than ever before. |