diff --git a/.changeset/pre.json b/.changeset/pre.json index 5826fb7f66..746638d45f 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -385,10 +385,12 @@ "nasty-mayflies-smoke", "nasty-yaks-peel", "neat-boats-shake", + "neat-boxes-chew", "neat-dingos-clap", "neat-files-rescue", "neat-jokes-beam", "nervous-berries-boil", + "nervous-dolphins-allow", "nervous-ducks-repeat", "nervous-spoons-relax", "nervous-turkeys-end", @@ -645,6 +647,7 @@ "ten-worms-reflect", "tender-lemons-judge", "tender-rocks-walk", + "tender-suns-love", "thick-cycles-rule", "thick-pans-tell", "thick-shirts-deliver", @@ -695,6 +698,7 @@ "two-dogs-accept", "two-dragons-yell", "two-falcons-buy", + "two-keys-watch", "unlucky-boxes-obey", "unlucky-steaks-warn", "unlucky-trees-lick", 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/.changeset/tender-suns-love.md b/.changeset/tender-suns-love.md new file mode 100644 index 0000000000..d4f4174da8 --- /dev/null +++ b/.changeset/tender-suns-love.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly set filename on HMR wrappers diff --git a/.changeset/two-keys-watch.md b/.changeset/two-keys-watch.md new file mode 100644 index 0000000000..92a455e07b --- /dev/null +++ b/.changeset/two-keys-watch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: only emit binding_property_non_reactive warning in runes mode diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b67d217c81..2e3f050a43 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.0.0-next.194 + +### Patch Changes + +- fix: bail-out of hydrating head if no anchor is found ([#12541](https://github.com/sveltejs/svelte/pull/12541)) + +- chore: add warning for invalid render function of createRawSnippet ([#12535](https://github.com/sveltejs/svelte/pull/12535)) + +- fix: correctly set filename on HMR wrappers ([#12543](https://github.com/sveltejs/svelte/pull/12543)) + +- fix: only emit binding_property_non_reactive warning in runes mode ([#12544](https://github.com/sveltejs/svelte/pull/12544)) + ## 5.0.0-next.193 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 5067c4ad51..5efa601433 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.0.0-next.193", + "version": "5.0.0-next.194", "type": "module", "types": "./types/index.d.ts", "engines": { 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 0ef05eeac0..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,20 +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'))), - 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')), 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/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 4683a945bd..84c0ad0ed5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -780,7 +780,11 @@ function serialize_inline_component(node, component_name, context, anchor = cont } else if (attribute.type === 'BindDirective') { const expression = /** @type {Expression} */ (context.visit(attribute.expression)); - if (expression.type === 'MemberExpression' && context.state.options.dev) { + if ( + expression.type === 'MemberExpression' && + context.state.options.dev && + context.state.analysis.runes + ) { context.state.init.push(serialize_validate_binding(context.state, attribute, expression)); } @@ -2826,7 +2830,11 @@ export const template_visitors = { const { state, path, visit } = context; const expression = node.expression; - if (expression.type === 'MemberExpression' && context.state.options.dev) { + if ( + expression.type === 'MemberExpression' && + context.state.options.dev && + context.state.analysis.runes + ) { context.state.init.push( serialize_validate_binding( context.state, 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 1489d30df0..4150620999 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/src/version.js b/packages/svelte/src/version.js index b947c0ce3f..fbd6debffa 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.193'; +export const VERSION = '5.0.0-next.194'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/_config.js new file mode 100644 index 0000000000..e99efacba8 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + test({ assert, target, window }) { + assert.htmlEqual(target.innerHTML, `

hello

`); + + const input = target.querySelector('input'); + ok(input); + + input.value = 'goodbye'; + input.dispatchEvent(new window.Event('input')); + + flushSync(); + assert.htmlEqual(target.innerHTML, `

goodbye

`); + }, + + warnings: [] +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/main.svelte new file mode 100644 index 0000000000..5277309402 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-member-expression-no-warning/main.svelte @@ -0,0 +1,6 @@ + + + +

{object.value}

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 44600907c1..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); }); }