diff --git a/.changeset/giant-gifts-mate.md b/.changeset/giant-gifts-mate.md new file mode 100644 index 000000000000..dc2486b69816 --- /dev/null +++ b/.changeset/giant-gifts-mate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent infinite loop when HMRing a component with an `await` diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index f51042eb7c62..d16b910f714d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -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)))) ]); diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 709a1b272220..2303336ef664 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -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} 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; diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js index 1fac1338c5f9..5878c51aaed5 100644 --- a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js @@ -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); }); } diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index 35bffb67a22d..f79243d4e7de 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -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,