Rich Harris 3 days ago committed by GitHub
commit f1f3950f86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent infinite loop when HMRing a component with an `await`

@ -519,14 +519,9 @@ export function client_component(analysis, options) {
if (options.hmr) {
const id = b.id(analysis.name);
const HMR = b.id('$.HMR');
const existing = b.member(id, HMR, true);
const incoming = b.member(b.id('module.default'), HMR, true);
const accept_fn_body = [
b.stmt(b.assignment('=', b.member(incoming, 'source'), b.member(existing, 'source'))),
b.stmt(b.call('$.set', b.member(existing, 'source'), b.member(incoming, 'original')))
b.stmt(b.call(b.member(id, b.id('$.HMR'), true), b.id('module.default')))
];
if (analysis.css.hash) {
@ -535,8 +530,7 @@ export function client_component(analysis, options) {
}
const hmr = b.block([
b.stmt(b.assignment('=', id, b.call('$.hmr', id, b.thunk(b.member(existing, 'source'))))),
b.stmt(b.assignment('=', id, b.call('$.hmr', id))),
b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body))))
]);

@ -1,23 +1,25 @@
/** @import { Source, Effect, TemplateNode } from '#client' */
/** @import { Effect, TemplateNode } from '#client' */
import { FILENAME, HMR } from '../../../constants.js';
import { EFFECT_TRANSPARENT } from '#client/constants';
import { hydrate_node, hydrating } from '../dom/hydration.js';
import { block, branch, destroy_effect } from '../reactivity/effects.js';
import { source } from '../reactivity/sources.js';
import { source, update } from '../reactivity/sources.js';
import { set_should_intro } from '../render.js';
import { get } from '../runtime.js';
/**
* @template {(anchor: Comment, props: any) => any} Component
* @param {Component} original
* @param {() => Source<Component>} get_source
* @param {Component} component
*/
export function hmr(original, get_source) {
export function hmr(component) {
let s = source(0);
/**
* @param {TemplateNode} anchor
* @param {any} props
*/
function wrapper(anchor, props) {
let v = -1;
let instance = {};
/** @type {Effect} */
@ -26,8 +28,9 @@ export function hmr(original, get_source) {
let ran = false;
block(() => {
const source = get_source();
const component = get(source);
if (v === (v = get(s))) {
return;
}
if (effect) {
// @ts-ignore
@ -62,16 +65,12 @@ export function hmr(original, get_source) {
}
// @ts-expect-error
wrapper[FILENAME] = original[FILENAME];
wrapper[FILENAME] = component[FILENAME];
// @ts-ignore
wrapper[HMR] = {
// When we accept an update, we set the original source to the new component
original,
// The `get_source` parameter reads `wrapper[HMR].source`, but in the `accept`
// function we always replace it with `previous[HMR].source`, which in practice
// means we only ever update the original
source: source(original)
wrapper[HMR] = (c) => {
component = c;
update(s);
};
return wrapper;

@ -11,11 +11,10 @@ function Hmr($$anchor) {
}
if (import.meta.hot) {
Hmr = $.hmr(Hmr, () => Hmr[$.HMR].source);
Hmr = $.hmr(Hmr);
import.meta.hot.accept((module) => {
module.default[$.HMR].source = Hmr[$.HMR].source;
$.set(Hmr[$.HMR].source, module.default[$.HMR].original);
Hmr[$.HMR](module.default);
});
}

@ -95,6 +95,7 @@ for (const generate of /** @type {const} */ (['client', 'server'])) {
if (generate === 'server' || FROM_HTML) {
from_html = compile(source, {
dev: DEV,
hmr: DEV,
filename: input,
generate,
runes: argv.values.runes,

Loading…
Cancel
Save