Skip to content

Commit

Permalink
fix: update original source in HMR update (#12547)
Browse files Browse the repository at this point in the history
* fix: update original source in HMR update

* tidy up

* comments

* oops
  • Loading branch information
Rich-Harris authored Jul 22, 2024
1 parent 53d32d4 commit 6fdfc53
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-planets-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: update original source in HMR update
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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))))
]);

Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down
25 changes: 22 additions & 3 deletions packages/svelte/src/internal/client/dev/hmr.js
Original file line number Diff line number Diff line change
@@ -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<Component>} source
* @param {Component} original
* @param {() => Source<Component>} 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} */
Expand All @@ -22,6 +25,7 @@ export function hmr(source) {
let ran = false;

block(() => {
const source = get_source();
const component = get(source);

if (effect) {
Expand Down Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}

Expand Down

0 comments on commit 6fdfc53

Please sign in to comment.