This is just a draft discussion for the near minor release after #34
The focus here would be to make it so that /* @refresh granular */ is now the default refresh behavior, but even more so the next refresh architecture would change everything.
The Default (Non-Granular) Refresh
The default mode currently for solid-refresh. The behavior is that if the file changed, the components will just remount in-place. That's it. The issue here is that HMR can happen even if the file is unchanged, so if the user even saved the file without doing anything, components in the file would just remount.
The Old Granular Refresh
The old (current) granular refresh takes refreshing to the next level, in such a way that components refresh as if the components are located in separate files. The current granular refresh would keep track of the component's code by producing a hash (using xxHash32) as a "signature" for the component. This signature is then compared between HMR updates and then the registry decides if will remount the component if it changed, code-wise.
Code checks is good, but not accurate. It just so happens that external factors may change but the code doesn't, so the refresh must know it as well, which is why the current granular refresh also scans for external bindings: variables that aren't locally declared. The collected bindings is then emitted as an object. HMR then compares the object by key-value (think of dependency lists but out-of-order comparison) at which the HMR decides if the component should remount.
With both code check and dependency check, it as if granular refresh is emulating the components as some sort of independent modules, to which I call it Hot Component Replacement too.
The old Granular Refresh is quite nice, but it could be better. Old granular refresh is capable of cross-HMR state preservation (in a way) because it doesn't remount the component aggressively, but what if refresh doesn't have to remount the component at all?
The new Granular Refresh takes it to another step by not refreshing the component, but refreshing it's content. The compiler will try to separate the component's implementation into two parts: setup and template, in which then the same analysis (code signature and dependency tracking) is done to the each part.
For example, here's input code and the theoretically compiled code
function Counter() {
const [count, setCount] = createSignal(0);
function increment() {
setCount((c) => c + 1);
}
function decrement() {
setCount((c) => c - 1);
}
return (
<div>
<h1>Count: {count()}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
In new Refresh, Counter doesn't have to re-mount, it's setup and template can re-evaluate independently (depending on change) but can still communicate as one by making it so that setup returns a "lexical scope" through a reactive proxy. setup can also update independently that template doesn't have to re-render, which means components down the tree won't have to remount when necessary.
Known challenges:
How to work through static branched code
How to manage event listeners for elements since those are only registered once.
This is just a draft discussion for the near minor release after #34
The focus here would be to make it so that
/* @refresh granular */
is now the default refresh behavior, but even more so the next refresh architecture would change everything.The Default (Non-Granular) Refresh
The default mode currently for solid-refresh. The behavior is that if the file changed, the components will just remount in-place. That's it. The issue here is that HMR can happen even if the file is unchanged, so if the user even saved the file without doing anything, components in the file would just remount.
The Old Granular Refresh
The old (current) granular refresh takes refreshing to the next level, in such a way that components refresh as if the components are located in separate files. The current granular refresh would keep track of the component's code by producing a hash (using xxHash32) as a "signature" for the component. This signature is then compared between HMR updates and then the registry decides if will remount the component if it changed, code-wise.
Code checks is good, but not accurate. It just so happens that external factors may change but the code doesn't, so the refresh must know it as well, which is why the current granular refresh also scans for external bindings: variables that aren't locally declared. The collected bindings is then emitted as an object. HMR then compares the object by key-value (think of dependency lists but out-of-order comparison) at which the HMR decides if the component should remount.
With both code check and dependency check, it as if granular refresh is emulating the components as some sort of independent modules, to which I call it
Hot Component Replacement
too.Current granular refresh looks like this:
The New Granular Refresh
The old Granular Refresh is quite nice, but it could be better. Old granular refresh is capable of cross-HMR state preservation (in a way) because it doesn't remount the component aggressively, but what if refresh doesn't have to remount the component at all?
The new Granular Refresh takes it to another step by not refreshing the component, but refreshing it's content. The compiler will try to separate the component's implementation into two parts: setup and template, in which then the same analysis (code signature and dependency tracking) is done to the each part.
For example, here's input code and the theoretically compiled code
which produces
In new Refresh,
Counter
doesn't have to re-mount, it's setup and template can re-evaluate independently (depending on change) but can still communicate as one by making it so thatsetup
returns a "lexical scope" through a reactive proxy.setup
can also update independently thattemplate
doesn't have to re-render, which means components down the tree won't have to remount when necessary.Known challenges: