From d5776c3ec38b98659081d21f3cd93960ebfddb91 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 11 Apr 2024 11:20:36 -0400 Subject: [PATCH] feat: simplify HMR implementation (#11132) * chore: simplify HMR implementation * changeset * unused * prettier --- .changeset/fair-bags-smoke.md | 5 ++ .../3-transform/client/transform-client.js | 39 +++++----- .../svelte/src/compiler/utils/builders.js | 14 ---- .../svelte/src/internal/client/dev/hmr.js | 73 +++++++------------ .../tests/snapshot/samples/hmr/_config.js | 7 ++ .../hmr/_expected/client/index.svelte.js | 28 +++++++ .../hmr/_expected/server/index.svelte.js | 9 +++ .../tests/snapshot/samples/hmr/index.svelte | 1 + 8 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 .changeset/fair-bags-smoke.md create mode 100644 packages/svelte/tests/snapshot/samples/hmr/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/hmr/index.svelte diff --git a/.changeset/fair-bags-smoke.md b/.changeset/fair-bags-smoke.md new file mode 100644 index 0000000000..683b4e2417 --- /dev/null +++ b/.changeset/fair-bags-smoke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: simplify HMR implementation 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 cf952d1520..ec802fbdf3 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 @@ -422,32 +422,31 @@ export function client_component(source, analysis, options) { ) ]; - // In order for hmr to work correctly, we need associate each component with a unique key. - // This is because bundlers might put many components into a the same module (usuaully as a chunk). - // `import.meta.hot` will then be the same object for all components in that modules. - if (options.hmr && options.filename) { + if (options.hmr) { body.push( - b.export_default( - b.conditional( - b.import_meta_hot(), - b.call( - '$.hmr', - b.member(b.import_meta_hot(), b.id('data')), - b.id(analysis.name), - b.literal(options.filename) - ), - b.id(analysis.name) - ) - ), b.if( - b.import_meta_hot(), - b.stmt(b.call('import.meta.hot.acceptExports', b.literal('default'))) + b.id('import.meta.hot'), + b.block([ + b.const(b.id('s'), b.call('$.source', b.id(analysis.name))), + b.stmt(b.assignment('=', b.id(analysis.name), b.call('$.hmr', b.id('s')))), + b.stmt( + b.call( + 'import.meta.hot.accept', + b.arrow( + [b.id('module')], + b.block([ + b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module'), b.id('default')))) + ]) + ) + ) + ) + ]) ) ); - } else { - body.push(b.export_default(b.id(analysis.name))); } + body.push(b.export_default(b.id(analysis.name))); + if (options.dev) { if (options.filename) { let filename = options.filename; diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 1b7da19d24..a5776a46de 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -600,20 +600,6 @@ export function throw_error(str) { }; } -/** - * @return {import('estree').MemberExpression} - */ -export function import_meta_hot() { - return member( - { - type: 'MetaProperty', - meta: id('import'), - property: id('meta') - }, - id('hot') - ); -} - export { await_builder as await, let_builder as let, diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 6df951003d..3082e47c68 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -1,55 +1,38 @@ import { block, branch, destroy_effect } from '../reactivity/effects.js'; -import { set, 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 {{ components: Map; wrapper: null | Component; }> }} hot_data - * @param {string} key - * @param {Component} component + * @param {import("#client").Source} source */ -export function hmr(hot_data, component, key) { - var components = (hot_data.components ??= new Map()); - var data = components.get(key); - - if (data === undefined) { - components.set( - key, - (data = { - source: source(component), - wrapper: null - }) - ); - } else { - set(data.source, component); - } - const component_source = data.source; - - return (data.wrapper ??= /** @type {Component} */ ( - (anchor, props) => { - let instance = {}; - - /** @type {import("#client").Effect} */ - let effect; - - block(() => { - const component = get(component_source); - - if (effect) { - // @ts-ignore - for (var k in instance) delete instance[k]; - destroy_effect(effect); - } - - effect = branch(() => { - set_should_intro(false); - Object.assign(instance, component(anchor, props)); - set_should_intro(true); - }); +export function hmr(source) { + /** + * @param {Comment} anchor + * @param {any} props + */ + return (anchor, props) => { + let instance = {}; + + /** @type {import("#client").Effect} */ + let effect; + + block(() => { + const component = get(source); + + if (effect) { + // @ts-ignore + for (var k in instance) delete instance[k]; + destroy_effect(effect); + } + + effect = branch(() => { + set_should_intro(false); + Object.assign(instance, component(anchor, props)); + set_should_intro(true); }); + }); - return instance; - } - )); + return instance; + }; } diff --git a/packages/svelte/tests/snapshot/samples/hmr/_config.js b/packages/svelte/tests/snapshot/samples/hmr/_config.js new file mode 100644 index 0000000000..2cd696d584 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hmr/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + hmr: true + } +}); 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 new file mode 100644 index 0000000000..61c5f6e83f --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js @@ -0,0 +1,28 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal/client"; + +var root = $.template(`

hello world

`); + +function Hmr($$anchor, $$props) { + $.push($$props, false); + $.init(); + + var h1 = root(); + + $.append($$anchor, h1); + $.pop(); +} + +if (import.meta.hot) { + const s = $.source(Hmr); + + Hmr = $.hmr(s); + + import.meta.hot.accept((module) => { + $.set(s, module.default); + }); +} + +export default Hmr; diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js new file mode 100644 index 0000000000..d671f7884a --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js @@ -0,0 +1,9 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; + +export default function Hmr($$payload, $$props) { + $.push(false); + $$payload.out += `

hello world

`; + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/hmr/index.svelte b/packages/svelte/tests/snapshot/samples/hmr/index.svelte new file mode 100644 index 0000000000..b44880118b --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/hmr/index.svelte @@ -0,0 +1 @@ +

hello world