From 6fdfc537e4cd7fb912bd0b76805e82c09a2bd78b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 22 Jul 2024 16:44:11 -0400 Subject: [PATCH] fix: update original source in HMR update (#12547) * fix: update original source in HMR update * tidy up * comments * oops --- .changeset/small-planets-destroy.md | 5 +++ .../3-transform/client/transform-client.js | 33 ++++++++----------- packages/svelte/src/constants.js | 2 +- .../svelte/src/internal/client/dev/hmr.js | 25 ++++++++++++-- packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/server/index.js | 2 +- .../hmr/_expected/client/index.svelte.js | 11 ++----- 7 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 .changeset/small-planets-destroy.md diff --git a/.changeset/small-planets-destroy.md b/.changeset/small-planets-destroy.md new file mode 100644 index 0000000000..03a0b1bcea --- /dev/null +++ b/.changeset/small-planets-destroy.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: update original source in HMR update 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 4422982d8d..7733871ad4 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 @@ -417,8 +417,19 @@ export function client_component(source, 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.call('$.set', b.id('s'), b.member(b.id('module.default'), b.id('$.ORIGINAL'), true))) + b.stmt( + b.assignment('=', b.member(incoming, b.id('source')), b.member(existing, b.id('source'))) + ), + b.stmt( + b.call('$.set', b.member(existing, b.id('source')), b.member(incoming, b.id('original'))) + ) ]; if (analysis.css.hash) { @@ -438,26 +449,10 @@ export function client_component(source, analysis, options) { } const hmr = b.block([ - b.const(b.id('s'), b.call('$.source', b.id(analysis.name))), - b.const(b.id('$$filename'), b.member(b.id(analysis.name), b.id('$.FILENAME'), true)), - 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'), true), - 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.assignment('=', id, b.call('$.hmr', id, b.thunk(b.member(existing, b.id('source'))))) ), + b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body)))) ]); diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index f6adc94f50..061e6e2eb8 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -32,7 +32,7 @@ export const UNINITIALIZED = Symbol(); // Dev-time component properties export const FILENAME = Symbol('filename'); -export const ORIGINAL = Symbol('original'); +export const HMR = Symbol('hmr'); /** List of elements that require raw contents and should not have SSR comments put in them */ export const RawTextElements = ['textarea', 'script', 'style', 'title']; diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 050901cd7c..0b511ee841 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -1,19 +1,22 @@ /** @import { Source, Effect } from '#client' */ +import { FILENAME, HMR } from '../../../constants.js'; import { EFFECT_TRANSPARENT } from '../constants.js'; import { block, branch, destroy_effect } from '../reactivity/effects.js'; +import { source } from '../reactivity/sources.js'; import { set_should_intro } from '../render.js'; import { get } from '../runtime.js'; /** * @template {(anchor: Comment, props: any) => any} Component - * @param {Source} source + * @param {Component} original + * @param {() => Source} get_source */ -export function hmr(source) { +export function hmr(original, get_source) { /** * @param {Comment} anchor * @param {any} props */ - return function (anchor, props) { + function wrapper(anchor, props) { let instance = {}; /** @type {Effect} */ @@ -22,6 +25,7 @@ export function hmr(source) { let ran = false; block(() => { + const source = get_source(); const component = get(source); if (effect) { @@ -50,5 +54,20 @@ export function hmr(source) { ran = true; return instance; + } + + // @ts-expect-error + wrapper[FILENAME] = original[FILENAME]; + + // @ts-expect-error + 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) }; + + return wrapper; } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 859e85f22c..4349285c34 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,4 +1,4 @@ -export { FILENAME, ORIGINAL } from '../../constants.js'; +export { FILENAME, HMR } from '../../constants.js'; export { add_locations } from './dev/elements.js'; export { hmr } from './dev/hmr.js'; export { diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 6300924ab2..593ddac548 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -1,6 +1,6 @@ /** @import { Component, Payload, RenderOutput } from '#server' */ /** @import { Store } from '#shared' */ -export { FILENAME, ORIGINAL } from '../../constants.js'; +export { FILENAME, HMR } from '../../constants.js'; import { is_promise, noop } from '../shared/utils.js'; import { subscribe_to_store } from '../../store/utils.js'; import { 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 696ad9b1fc..01daada7ac 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 @@ -10,16 +10,11 @@ function Hmr($$anchor) { } if (import.meta.hot) { - const s = $.source(Hmr); - const $$filename = Hmr[$.FILENAME]; - const $$original = Hmr; - - Hmr = $.hmr(s); - Hmr[$.FILENAME] = $$filename; - Hmr[$.ORIGINAL] = $$original; + Hmr = $.hmr(Hmr, () => Hmr[$.HMR].source); import.meta.hot.accept((module) => { - $.set(s, module.default[$.ORIGINAL]); + module.default[$.HMR].source = Hmr[$.HMR].source; + $.set(Hmr[$.HMR].source, module.default[$.HMR].original); }); }