Skip to content

Commit

Permalink
docs: edited hsr blog post
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Apr 12, 2022
1 parent fce0db8 commit 57913b4
Showing 1 changed file with 6 additions and 6 deletions.
12 changes: 6 additions & 6 deletions website/blog/hsr-vs-hmr.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ Well, to understand this, we first need to understand HMR. For those who aren't
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.
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 apps with a large 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 login 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).
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 coming 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?
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.

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.
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 bundlers 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.
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 just as fast, if not faster in some cases, 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 (and it'll only get faster on the web as Wasm becomes more advanced), 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.
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.

0 comments on commit 57913b4

Please sign in to comment.