Replies: 1 comment 1 reply
-
This is really interesting @rixo. I'll bring this discussion to our next team meeting. It will take a bit, so I think the best way to move the discussion forward is to create a PR that takes care of edge cases where we can check the performance and complexity implications of this new API. And we can follow there (or here) to get as close as possible to a final proposal. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This discussion is about adding a new capacity, partially accepting a module, to the HMR engine.
With Vite's current HMR API, a module can either accept itself entirely, or accept some of its incoming deps. It has no way of accepting some, but not all of its own exports.
This can be problematic for frameworks that provides automatic HMR handlers in the case of mixed module with both exports that can be (self) accepted automatically, and random user exports, that cannot be accepted automatically.
A new HMR API that would allow a module to partially accept only some of its own exports would give frameworks that have to deal with such mixed modules an elegant way of doing so, enabling more reliable and efficient HMR.
Problem
The main usage of HMR today is to rely on UI frameworks to provide automatic HMR handlers for their component, and let all the rest of the user code uninstrumented. Said otherwise, all or a big portion of UI components will be automatically "accepted", while the rest of the user code won't be "accepted", which means that HMR update bubbles can only be stopped by UI components.
Since HMR's unit of work is JS modules, and since a module currently can only accept itself as a whole or not at all, a problem arises for UI frameworks with modules that contains mixed content: some UI components (that could be accepted automatically) and random user code (that cannot be accepted).
In these case, the current API only leaves a choice between forfeiting HMR for the components in those mixed modules, which can eventually leads to some full reloads that could have been avoided, or accepting too much and let HMR gets out of sync / misrepresent changes.
Examples
React
Foo.js
From my observations, React HMR has chosen to not accept such mixed modules at all, but this cause unneeded HMR updates to any other module that is importing this one, even if they're only using the component. In the worst case, the entry component will be a mixed module too, and so we'll get a full reload that could have been avoided.
For example, such a module is only using the export
Foo
from our previous module, and we know it wouldn't need to be updated following a HMR change inFoo.js
, but in practice it would get updated ifFoo.js
is updated.Svelte
In a Svelte component, the default export is necessarily the component itself, but the user is also allowed to have their own arbitrary exports.
Foo.svelte
Such a module cannot be safely auto accepted, because we would then have multiple versions of the
randomExport
function in the other modules of the app, depending mainly on when the other module has been loaded as compared to successive HMR updates toFoo.svelte
.Doing like React and letting any module that has custom exports unaccepted is not a satisfaying option either, because Svelte can use static exports for configuration purpose, for example in Svelte Kit:
Since those options are generally used in "page" components, that also are the entry points of the app, not accepting mixed modules essentially means completely forfeiting HMR for those "page" components.
This is a limitation of the HMR engine, and not a hard technical limitation, because those "framework" exports are used only by the server code and are never actually imported in client code, so it would be safe to accept a HMR update in such a component.
As far as I can tell, the same dilemma probably also exists in other frameworks like Next.js, that also use server-side only exports in their page component (for example
getStaticProps
orgetServerSideProps
).HMR partial accept
The above problems could be solved elegantly by a framework's HMR provider if it had the ability to self accept only some parts of its own exports.
The syntax could be something like the following:
Foo.js
When a partially accepted module is hit by a change, the HMR bubble would propagate only to importers that imports at least one non accepted exports from the partially accepted module.
Following the previous example, such a module would not be invalidated by a change in
Foo.js
:... while the following module would get invalidated:
Example benefits
This new HMR API would give an easy and elegant way to efficiently solve the problems illustrated previously.
React
An importer would get refreshed only if it makes use of the "unknown"
bar
export. Importers only using theFoo
component could have the update automatically handled for them by React HMR and wouldn't need to be refreshed.Svelte
In Svelte, the component is always the default export and every other export belongs to the user and cannot be automatically accepted, so every Svelte component would get instrumented with:
In our simple example:
Consumer of this module would get refreshed if they use
randomExport
, correctly reflecting the last version of user's code, while modules that only use the component wouldn't get extraneous refreshes.Back with our Svelte Kit example:
Such a "page" component would always stop the HMR bubble, because those exports are only used by the framework itself on the server-side, so the module has no "importers" on the client side, which is exactly what we want.
Feasibility
I think the implementation would be pretty straightforward. I was able to POC this in Vite in a couple of hours.
The main drawback I've identified is that currently Vite doesn't descend deep enough into import analysis. Currently Vite only extract relationships between modules, whereas for this feature we'd need to extract every import bindings (default import, named imports) that are used. This means supporting this would add a little overhead.
However, with this new API, I have been able to fix the worst outstanding issue in Svelte HMR with a one line change. Fixing it with the current API would require enormous amount of hack with virtual modules and extensive modifications of the original code, with all the risks that this implies.
I am still lacking perspective on this idea, so it's very possible I might have missed something. But it does look like a very promising and powerful tool to leverage the HMR engine for improved experience, so I'm very curious to know what you would think about this?
Beta Was this translation helpful? Give feedback.
All reactions