fix: store a persistent HMR wrapper per component

The previous HMR logic created a new wrapper and source per HMR update, which meant things would either get wrapped more and more (prior to #12454) or would not get updated after the first update because the reference to the original would get lost (after #12454).

This fixes that by creating a registry within the HMR wrappers by filename are stored, retrieved and its signals updated. That way nothing gets lost, and nothing gets wrapped more than needed.

Fixes #12506
pull/12537/head
Simon Holthausen 2 years ago
parent 0fb9fb9a95
commit 45d94fae01

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: store a persistent HMR wrapper per component

@ -417,9 +417,7 @@ export function client_component(source, analysis, options) {
); );
if (options.hmr) { if (options.hmr) {
const accept_fn_body = [ const accept_fn_body = [b.stmt(b.call('$$hmr.update', b.id('module.default')))];
b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module.default'), b.id('$.ORIGINAL'), true)))
];
if (analysis.css.hash) { if (analysis.css.hash) {
// remove existing `<style>` element, in case CSS changed // remove existing `<style>` element, in case CSS changed
@ -438,20 +436,8 @@ export function client_component(source, analysis, options) {
} }
const hmr = b.block([ const hmr = b.block([
b.const(b.id('s'), b.call('$.source', b.id(analysis.name))), b.const(b.id('$$hmr'), b.call('$.hmr', b.id(analysis.name))),
b.const(b.id('filename'), b.member(b.id(analysis.name), b.id('filename'))), b.stmt(b.assignment('=', b.id(analysis.name), b.id('$$hmr.wrapper'))),
b.const(b.id('$$original'), b.id(analysis.name)),
b.stmt(b.assignment('=', b.id(analysis.name), b.call('$.hmr', b.id('s')))),
b.stmt(b.assignment('=', b.member(b.id(analysis.name), b.id('filename')), b.id('filename'))),
// Assign the original component to the wrapper so we can use it on hot reload patching,
// else we would call the HMR function two times
b.stmt(
b.assignment(
'=',
b.member(b.id(analysis.name), b.id('$.ORIGINAL'), true),
b.id('$$original')
)
),
b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body)))) b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body))))
]); ]);

@ -1,19 +1,37 @@
/** @import { Source, Effect } from '#client' */ /** @import { Effect } from '#client' */
import { FILENAME, ORIGINAL } from '../../../constants.js';
import { EFFECT_TRANSPARENT } from '../constants.js'; import { EFFECT_TRANSPARENT } from '../constants.js';
import { block, branch, destroy_effect } from '../reactivity/effects.js'; import { block, branch, destroy_effect } from '../reactivity/effects.js';
import { set, source } from '../reactivity/sources.js';
import { set_should_intro } from '../render.js'; import { set_should_intro } from '../render.js';
import { get } from '../runtime.js'; import { get } from '../runtime.js';
/**
* For each original component, store a persistent reference to the HMR wrapper
* @type {Map<string, {wrapper: any, update: (update: any) => void}>}
*/
const registry = new Map();
/** /**
* @template {(anchor: Comment, props: any) => any} Component * @template {(anchor: Comment, props: any) => any} Component
* @param {Source<Component>} source * @param {Component} original
*/ */
export function hmr(source) { export function hmr(original) {
let result = registry.get(/** @type {any} */ (original)[FILENAME]);
if (result) {
// update the reference to the original component as it's now updated
result.wrapper[ORIGINAL] = original;
return result;
}
const component_source = source(original);
/** /**
* @param {Comment} anchor * @param {Comment} anchor
* @param {any} props * @param {any} props
*/ */
return function (anchor, props) { const wrapper = function (anchor, props) {
let instance = {}; let instance = {};
/** @type {Effect} */ /** @type {Effect} */
@ -22,7 +40,7 @@ export function hmr(source) {
let ran = false; let ran = false;
block(() => { block(() => {
const component = get(source); const component = get(component_source);
if (effect) { if (effect) {
// @ts-ignore // @ts-ignore
@ -51,4 +69,15 @@ export function hmr(source) {
return instance; return instance;
}; };
// stash a reference to the original component to avoid adding more and more wrappers per HMR update
wrapper[ORIGINAL] = original;
result = {
wrapper,
update: (update) => set(component_source, update[ORIGINAL])
};
registry.set(/** @type {any} */ (original)[FILENAME], result);
return result;
} }

Loading…
Cancel
Save