diff --git a/.changeset/const-tags.md b/.changeset/const-tags.md new file mode 100644 index 0000000000..82765c5038 --- /dev/null +++ b/.changeset/const-tags.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: invalidate `@const` tags based on visible references in legacy mode diff --git a/.changeset/evil-chicken-attend.md b/.changeset/evil-chicken-attend.md new file mode 100644 index 0000000000..f89e7e17f3 --- /dev/null +++ b/.changeset/evil-chicken-attend.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: disallow `--` in `idPrefix` diff --git a/.changeset/orange-geckos-rest.md b/.changeset/orange-geckos-rest.md new file mode 100644 index 0000000000..5717c249b6 --- /dev/null +++ b/.changeset/orange-geckos-rest.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't override `$destroy/set/on` instance methods in dev mode diff --git a/.editorconfig b/.editorconfig index 2f52d9993f..900cdf7cdc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,6 @@ root = true end_of_line = lf insert_final_newline = true indent_style = tab -indent_size = 2 charset = utf-8 trim_trailing_whitespace = true diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 706a62e3d6..b6c26e0792 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -50,7 +50,7 @@ jobs: if: github.event_name == 'workflow_dispatch' || steps.pr.outcome == 'success' with: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || steps.pr.outputs.ref }} - - uses: pnpm/action-setup@v4.3.0 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 - uses: actions/setup-node@v6 with: node-version: 24 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23d814d527..78bab024ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} @@ -49,7 +49,7 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: 22 @@ -66,7 +66,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: 24 @@ -83,7 +83,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: 24 @@ -104,7 +104,7 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: 18 diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index cd3df32064..0fcda5a778 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -35,7 +35,7 @@ jobs: # For push, fall back to the push SHA. ref: ${{ github.event.pull_request.head.sha || inputs.sha || github.sha }} - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - uses: actions/setup-node@v6 with: node-version: 22.x diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa32e3c5cd..359fcb7eea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -42,7 +42,7 @@ jobs: - name: Create Release Pull Request or Publish to npm id: changesets - uses: changesets/action@v1 + uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1 with: version: pnpm changeset:version publish: pnpm changeset:publish diff --git a/documentation/docs/07-misc/03-typescript.md b/documentation/docs/07-misc/03-typescript.md index 899849473c..b7f49f9c6f 100644 --- a/documentation/docs/07-misc/03-typescript.md +++ b/documentation/docs/07-misc/03-typescript.md @@ -40,22 +40,9 @@ If you want to use one of these features, you need to setup up a `script` prepro To use non-type-only TypeScript features within Svelte components, you need to add a preprocessor that will turn TypeScript into JavaScript. -```ts -/// file: svelte.config.js -// @noErrors -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +### Using Vite -const config = { - // Note the additional `{ script: true }` - preprocess: vitePreprocess({ script: true }) -}; - -export default config; -``` - -### Using SvelteKit or Vite - -The easiest way to get started is scaffolding a new SvelteKit project by typing `npx sv create`, following the prompts and choosing the TypeScript option. +If you're using SvelteKit, or Vite _without_ SvelteKit, you can use `vitePreprocess` from `@sveltejs/vite-plugin-svelte` in your config file: ```ts /// file: svelte.config.js @@ -63,19 +50,16 @@ The easiest way to get started is scaffolding a new SvelteKit project by typing import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; const config = { - preprocess: vitePreprocess() + // Note the additional `{ script: true }` + preprocess: vitePreprocess({ script: true }) }; export default config; ``` -If you don't need or want all the features SvelteKit has to offer, you can scaffold a Svelte-flavoured Vite project instead by typing `npm create vite@latest` and selecting the `svelte-ts` option. - -In both cases, a `svelte.config.js` with `vitePreprocess` will be added. Vite/SvelteKit will read from this config file. - -### Other build tools +### Using other build tools -If you're using tools like Rollup or Webpack instead, install their respective Svelte plugins. For Rollup that's [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) and for Webpack that's [svelte-loader](https://github.com/sveltejs/svelte-loader). For both, you need to install `typescript` and `svelte-preprocess` and add the preprocessor to the plugin config (see the respective READMEs for more info). +If you're using tools like Rollup (via [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte)) or Webpack (via [svelte-loader](https://github.com/sveltejs/svelte-loader)) instead, install `typescript` and `svelte-preprocess` and add the preprocessor to the plugin config. See the respective plugin READMEs for more info. > [!NOTE] If you're starting a new project, we recommend using SvelteKit or Vite instead @@ -85,7 +69,7 @@ When using TypeScript, make sure your `tsconfig.json` is setup correctly. - Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions - Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is -- Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do. +- Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do. ## Typing `$props` diff --git a/documentation/docs/98-reference/.generated/server-errors.md b/documentation/docs/98-reference/.generated/server-errors.md index 0ee6fd0614..5e11c1c8c4 100644 --- a/documentation/docs/98-reference/.generated/server-errors.md +++ b/documentation/docs/98-reference/.generated/server-errors.md @@ -69,6 +69,12 @@ Cause: `csp.nonce` was set while `csp.hash` was `true`. These options cannot be used simultaneously. ``` +### invalid_id_prefix + +``` +The `idPrefix` option cannot include `--`. +``` + ### lifecycle_function_unavailable ``` diff --git a/packages/svelte/messages/server-errors/errors.md b/packages/svelte/messages/server-errors/errors.md index 533e5405ed..fe7f811787 100644 --- a/packages/svelte/messages/server-errors/errors.md +++ b/packages/svelte/messages/server-errors/errors.md @@ -53,6 +53,10 @@ This error occurs when using `hydratable` multiple times with the same key. To a > `csp.nonce` was set while `csp.hash` was `true`. These options cannot be used simultaneously. +## invalid_id_prefix + +> The `idPrefix` option cannot include `--`. + ## lifecycle_function_unavailable > `%name%(...)` is not available on the server diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js index 4d93cd44e0..7bdb2243f2 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/function.js @@ -10,7 +10,7 @@ export function visit_function(node, context) { for (const [name] of context.state.scope.references) { const binding = context.state.scope.get(name); - if (binding && binding.scope.function_depth < context.state.scope.function_depth) { + if (binding && binding.scope !== context.state.scope) { context.state.expression.references.add(binding); } } 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 115910b751..a013db9ee3 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 @@ -366,7 +366,7 @@ export function client_component(analysis, options) { ) ); } else if (dev) { - component_returned_object.push(b.spread(b.call(b.id('$.legacy_api')))); + component_returned_object.unshift(b.spread(b.call(b.id('$.legacy_api')))); } const push_args = [b.id('$$props'), b.literal(analysis.runes)]; diff --git a/packages/svelte/src/internal/server/errors.js b/packages/svelte/src/internal/server/errors.js index d1e594acfd..079bec3c91 100644 --- a/packages/svelte/src/internal/server/errors.js +++ b/packages/svelte/src/internal/server/errors.js @@ -105,6 +105,18 @@ export function invalid_csp() { throw error; } +/** + * The `idPrefix` option cannot include `--`. + * @returns {never} + */ +export function invalid_id_prefix() { + const error = new Error(`invalid_id_prefix\nThe \`idPrefix\` option cannot include \`--\`.\nhttps://svelte.dev/e/invalid_id_prefix`); + + error.name = 'Svelte error'; + + throw error; +} + /** * `%name%(...)` is not available on the server * @param {string} name diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 4bcb45512e..d2ab35a1f2 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -759,6 +759,10 @@ export class Renderer { * @returns {Renderer} */ static #open_render(mode, component, options) { + if (options.idPrefix?.includes('--')) { + e.invalid_id_prefix(); + } + var previous_context = ssr_context; try { diff --git a/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/_config.js b/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/_config.js new file mode 100644 index 0000000000..6282d36f75 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/_config.js @@ -0,0 +1,34 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + +

hello

+

hello

+ `, + + ssrHtml: ` + +

hello

+

hello

+ `, + + async test({ assert, target }) { + const [input] = target.querySelectorAll('input'); + + flushSync(() => { + input.value = 'goodbye'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + }); + + assert.htmlEqual( + target.innerHTML, + ` + +

goodbye

+

goodbye

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/main.svelte b/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/main.svelte new file mode 100644 index 0000000000..a32010717c --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/const-tag-depends-on-const-tag/main.svelte @@ -0,0 +1,15 @@ + + + + + + +{#if true} + {@const m1 = message} + {@const m2 = (() => m1)()} + +

{m1}

+

{m2}

+{/if}