Skip to content

Commit afe1d11

Browse files
trueadmRich-Harris
andauthored
feat: hot module reloading support for Svelte 5 (#11106)
* feat: hot module reloading support for Svelte 5 * fix lockfile * tweaks * types * lint * lint * tweaks * add hmr flag * tweak * tweaks * move HMR logic into its own module * simplify * tidy up types * fix test * lint * need some indirection here or references break * prevent transitions during HMR update --------- Co-authored-by: Rich Harris <[email protected]>
1 parent e1b2d29 commit afe1d11

File tree

23 files changed

+147
-64
lines changed

23 files changed

+147
-64
lines changed

.changeset/olive-moons-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: hot module reloading support for Svelte 5

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -415,15 +415,31 @@ export function client_component(source, analysis, options) {
415415
const body = [
416416
...state.hoisted,
417417
...module.body,
418-
b.export_default(
419-
b.function_declaration(
420-
b.id(analysis.name),
421-
[b.id('$$anchor'), b.id('$$props')],
422-
component_block
423-
)
418+
b.function_declaration(
419+
b.id(analysis.name),
420+
[b.id('$$anchor'), b.id('$$props')],
421+
component_block
424422
)
425423
];
426424

425+
if (options.hmr) {
426+
body.push(
427+
b.export_default(
428+
b.conditional(
429+
b.import_meta_hot(),
430+
b.call('$.hmr', b.member(b.import_meta_hot(), b.id('data')), b.id(analysis.name)),
431+
b.id(analysis.name)
432+
)
433+
),
434+
b.if(
435+
b.import_meta_hot(),
436+
b.stmt(b.call('import.meta.hot.acceptExports', b.literal('default')))
437+
)
438+
);
439+
} else {
440+
body.push(b.export_default(b.id(analysis.name)));
441+
}
442+
427443
if (options.dev) {
428444
if (options.filename) {
429445
let filename = options.filename;

packages/svelte/src/compiler/utils/builders.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,20 @@ export function throw_error(str) {
600600
};
601601
}
602602

603+
/**
604+
* @return {import('estree').MemberExpression}
605+
*/
606+
export function import_meta_hot() {
607+
return member(
608+
{
609+
type: 'MetaProperty',
610+
meta: id('import'),
611+
property: id('meta')
612+
},
613+
id('hot')
614+
);
615+
}
616+
603617
export {
604618
await_builder as await,
605619
let_builder as let,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { block, branch, destroy_effect } from '../reactivity/effects.js';
2+
import { set, source } from '../reactivity/sources.js';
3+
import { set_should_intro } from '../render.js';
4+
import { get } from '../runtime.js';
5+
6+
/**
7+
* @template {(anchor: Comment, props: any) => any} Component
8+
* @param {{ source: import("#client").Source<Component>; wrapper: Component; }} data
9+
* @param {Component} component
10+
*/
11+
export function hmr(data, component) {
12+
if (data.source) {
13+
set(data.source, component);
14+
} else {
15+
data.source = source(component);
16+
}
17+
18+
return (data.wrapper ??= /** @type {Component} */ (
19+
(anchor, props) => {
20+
let instance = {};
21+
22+
/** @type {import("#client").Effect} */
23+
let effect;
24+
25+
block(() => {
26+
const component = get(data.source);
27+
28+
if (effect) {
29+
// @ts-ignore
30+
for (var k in instance) delete instance[k];
31+
destroy_effect(effect);
32+
}
33+
34+
effect = branch(() => {
35+
set_should_intro(false);
36+
Object.assign(instance, component(anchor, props));
37+
set_should_intro(true);
38+
});
39+
});
40+
41+
return instance;
42+
}
43+
));
44+
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { hmr } from './dev/hmr.js';
12
export { add_owner, mark_module_start, mark_module_end } from './dev/ownership.js';
23
export { await_block as await } from './dom/blocks/await.js';
34
export { if_block as if } from './dom/blocks/if.js';

packages/svelte/src/internal/client/render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
init_operations
88
} from './dom/operations.js';
99
import { HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js';
10-
import { flush_sync, push, pop, current_component_context, untrack } from './runtime.js';
10+
import { flush_sync, push, pop, current_component_context } from './runtime.js';
1111
import { effect_root, branch } from './reactivity/effects.js';
1212
import {
1313
hydrate_anchor,

packages/svelte/tests/parser-legacy/samples/css/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@
7878
"content": {
7979
"start": 23,
8080
"end": 48,
81-
"comment": null,
82-
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
81+
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n",
82+
"comment": null
8383
}
8484
}
8585
}

packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@
7878
"content": {
7979
"start": 23,
8080
"end": 48,
81-
"comment": null,
82-
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n"
81+
"styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n",
82+
"comment": null
8383
}
8484
}
8585
}

packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,8 @@
10741074
"content": {
10751075
"start": 7,
10761076
"end": 798,
1077-
"comment": null,
1078-
"styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n"
1077+
"styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n",
1078+
"comment": null
10791079
}
10801080
},
10811081
"js": [],

packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@
393393
"content": {
394394
"start": 7,
395395
"end": 378,
396-
"comment": null,
397-
"styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n\t:is( /*button*/\n\t\tbutton, /*p after h1*/\n\t\th1 + p\n\t\t){\n\t\tcolor: red;\n\t}\n"
396+
"styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n\t:is( /*button*/\n\t\tbutton, /*p after h1*/\n\t\th1 + p\n\t\t){\n\t\tcolor: red;\n\t}\n",
397+
"comment": null
398398
}
399399
},
400400
"js": [],

0 commit comments

Comments
 (0)