Skip to content

Commit 69fe435

Browse files
committed
refactor bindings preservation
1 parent cd6f06c commit 69fe435

File tree

2 files changed

+57
-28
lines changed

2 files changed

+57
-28
lines changed

packages/svelte-hmr-spec/test/bindings.spec.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,25 @@ describe('bindings', () => {
317317
<script>
318318
::0 export let bet = () => 1
319319
::1 export let get = () => 2
320+
::2 export let bet = () => 3
321+
::3 export let get = () => 4
320322
</script>
321323
322324
* * * * *
323325
324326
::0::
325327
undefined
326328
::1::
329+
undefined
327330
${clickButton()}
328331
2
332+
::2:: doesn't reuse a wrong variable in the right place
333+
2
334+
${clickButton()}
335+
undefined
336+
::3:: remembers older future prop
337+
undefined
338+
${clickButton()}
339+
4
329340
`
330341
})

packages/svelte-hmr/runtime/svelte-hooks.js

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const captureState = cmp => {
2121
}
2222

2323
const {
24-
$$: { callbacks, bound, ctx, props, hmr_future_props },
24+
$$: { callbacks, bound, ctx, props },
2525
} = cmp
2626

2727
const state = cmp.$capture_state()
@@ -37,14 +37,51 @@ const captureState = cmp => {
3737
return {
3838
ctx,
3939
props,
40-
hmr_future_props,
4140
callbacks,
4241
bound,
4342
state,
4443
hmr_props_values,
4544
}
4645
}
4746

47+
// remapping all existing bindings (including hmr_future_foo ones) to the
48+
// new version's props indexes, and refresh them with the new value from
49+
// context
50+
const restoreBound = (cmp, restore) => {
51+
// reverse prop:ctxIndex in $$.props to ctxIndex:prop
52+
//
53+
// ctxIndex can be either a regular index in $$.ctx or a hmr_future_ prop
54+
//
55+
const propsByIndex = {}
56+
for (const [name, i] of Object.entries(restore.props)) {
57+
propsByIndex[i] = name
58+
}
59+
60+
// NOTE $$.bound cannot change in the HMR lifetime of a component, because
61+
// if bindings changes, that means the parent component has changed,
62+
// which means the child (current) component will be wholly recreated
63+
for (const [oldIndex, updateBinding] of Object.entries(restore.bound)) {
64+
// can be either regular prop, or future_hmr_ prop
65+
const propName = propsByIndex[oldIndex]
66+
67+
// this should never happen if remembering of future props is enabled...
68+
// in any case, there's nothing we can do about it if we have lost prop
69+
// name knowledge at this point
70+
if (propName == null) continue
71+
72+
// NOTE $$.props[propName] also propagates knowledge of a possible
73+
// future prop to the new $$.props (via $$.props being a Proxy)
74+
const newIndex = cmp.$$.props[propName]
75+
cmp.$$.bound[newIndex] = updateBinding
76+
77+
// NOTE if the prop doesn't exist or doesn't exist anymore in the new
78+
// version of the component, clearing the binding is the expected
79+
// behaviour (since that's what would happen in non HMR code)
80+
const newValue = cmp.$$.ctx[newIndex]
81+
updateBinding(newValue)
82+
}
83+
}
84+
4885
// restoreState
4986
//
5087
// It is too late to restore context at this point because component instance
@@ -54,32 +91,16 @@ const captureState = cmp => {
5491
// also generally more respectful of normal operation.
5592
//
5693
const restoreState = (cmp, restore) => {
57-
if (!restore) {
58-
return
59-
}
60-
const { callbacks, bound, hmr_future_props } = restore
94+
if (!restore) return
6195

62-
if (callbacks) {
63-
cmp.$$.callbacks = callbacks
96+
if (restore.callbacks) {
97+
cmp.$$.callbacks = restore.callbacks
6498
}
6599

66-
if (bound) {
67-
const propsByIndex = {}
68-
for (const [name, i] of Object.entries(restore.props)) {
69-
propsByIndex[i] = name
70-
}
71-
for (const oldIndex of Object.keys(bound)) {
72-
const callback = bound[oldIndex]
73-
const propName = hmr_future_props[-oldIndex - 1] || propsByIndex[oldIndex]
74-
if (propName == null) continue
75-
const newIndex = cmp.$$.props[propName]
76-
cmp.$$.bound[newIndex] = callback
77-
// NOTE if the prop doesn't exist or doesn't exist anymore in the new
78-
// version of the component, clearing the binding is the expected
79-
// behaviour (since that's what would happen in non HMR code)
80-
callback(cmp.$$.ctx[newIndex])
81-
}
100+
if (restore.bound) {
101+
restoreBound(cmp, restore)
82102
}
103+
83104
// props, props.$$slots are restored at component creation (works
84105
// better -- well, at all actually)
85106
}
@@ -164,15 +185,12 @@ export const createProxiedComponent = (
164185
// https://github.com/sveltejs/svelte/blob/1632bca34e4803d6b0e0b0abd652ab5968181860/src/runtime/internal/Component.ts#L46
165186
//
166187
const rememberFutureProps = cmp => {
167-
cmp.$$.hmr_future_props = []
168-
169188
if (typeof Proxy === 'undefined') return
170189

171190
cmp.$$.props = new Proxy(cmp.$$.props, {
172191
get(target, name) {
173192
if (target[name] === undefined) {
174-
cmp.$$.hmr_future_props.push(name)
175-
return -cmp.$$.hmr_future_props.length
193+
target[name] = 'hmr_future_' + name
176194
}
177195
return target[name]
178196
},

0 commit comments

Comments
 (0)