diff --git a/.changeset/large-humans-report.md b/.changeset/large-humans-report.md deleted file mode 100644 index 6ba09ef148..0000000000 --- a/.changeset/large-humans-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reinstate missing prefersReducedMotion export diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbdd1e420c..cf73a1f6cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ env: jobs: Tests: + permissions: {} runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: @@ -41,6 +42,7 @@ jobs: env: CI: true Lint: + permissions: {} runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -59,8 +61,9 @@ jobs: run: pnpm lint - name: build and check generated types if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail - run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); } Benchmarks: + permissions: {} runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/.github/workflows/docs-preview-create-request.yml b/.github/workflows/docs-preview-create-request.yml deleted file mode 100644 index f57766dc36..0000000000 --- a/.github/workflows/docs-preview-create-request.yml +++ /dev/null @@ -1,26 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview create request - -on: - pull_request_target: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-create - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/docs-preview-delete-request.yml b/.github/workflows/docs-preview-delete-request.yml deleted file mode 100644 index 4eb0e996a6..0000000000 --- a/.github/workflows/docs-preview-delete-request.yml +++ /dev/null @@ -1,27 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview delete request - -on: - pull_request_target: - branches: - - main - types: [closed] - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-delete - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index ce7bf04136..71df3242e8 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -9,6 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/github-script@v6 with: script: | diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml index 1698a456d3..3f1fca5a0b 100644 --- a/.github/workflows/pkg.pr.new-comment.yml +++ b/.github/workflows/pkg.pr.new-comment.yml @@ -6,11 +6,15 @@ on: types: - completed +permissions: + pull-requests: write + jobs: build: name: 'Update comment' runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Download artifact uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 4292ec900a..b2b521dc6f 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -3,6 +3,8 @@ on: [push, pull_request] jobs: build: + permissions: {} + runs-on: ubuntu-latest steps: @@ -12,7 +14,7 @@ jobs: - run: corepack enable - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: pnpm - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a17f49bbeb..6debe5662a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,7 @@ jobs: name: Release runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Checkout Repo uses: actions/checkout@v4 with: @@ -33,7 +34,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Build - run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); } - name: Create Release Pull Request or Publish to npm id: changesets diff --git a/.github/workflows/sync-request.yml b/.github/workflows/sync-request.yml deleted file mode 100644 index de2ce77692..0000000000 --- a/.github/workflows/sync-request.yml +++ /dev/null @@ -1,22 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Sync request - -on: - push: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: sync-request - client-payload: |- - { - "package": "svelte" - } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd7bbb476e..0e2628f84f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions. -If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml) +If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml). ### Reporting new issues @@ -62,8 +62,6 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose) ## Pull requests -> HEADS UP: Svelte 5 will likely change a lot on the compiler. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but big feature PRs will likely not be merged. - ### Proposing a change If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml). diff --git a/LICENSE.md b/LICENSE.md index e2a8b89fa4..f872adf738 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2016-24 [these people](https://github.com/sveltejs/svelte/graphs/contributors) +Copyright (c) 2016-2025 [Svelte Contributors](https://github.com/sveltejs/svelte/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index be94cba63c..7ea7164752 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -[![Cybernetically enhanced web apps: Svelte](https://sveltejs.github.io/assets/banner.png)](https://svelte.dev) + + + + Svelte - web development for the rest of us + + -[![license](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat) +[![License](https://img.shields.io/npm/l/svelte.svg)](LICENSE.md) [![Chat](https://img.shields.io/discord/457912077277855764?label=chat&logo=discord)](https://svelte.dev/chat) ## What is Svelte? @@ -24,10 +29,6 @@ You may view [our roadmap](https://svelte.dev/roadmap) if you'd like to see what Please see the [Contributing Guide](CONTRIBUTING.md) and the [`svelte`](packages/svelte) package for information on contributing to Svelte. -### svelte.dev - -The source code for https://svelte.dev lives in the [sites](https://github.com/sveltejs/svelte/tree/master/sites/svelte.dev) folder, with all the documentation right [here](https://github.com/sveltejs/svelte/tree/master/documentation). The site is built with [SvelteKit](https://svelte.dev/docs/kit). - ## Is svelte.dev down? Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402). diff --git a/assets/banner.png b/assets/banner.png new file mode 100644 index 0000000000..3428b278bf Binary files /dev/null and b/assets/banner.png differ diff --git a/assets/banner_dark.png b/assets/banner_dark.png new file mode 100644 index 0000000000..1adba40d8e Binary files /dev/null and b/assets/banner_dark.png differ diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_avoidable.js b/benchmarking/benchmarks/reactivity/kairo/kairo_avoidable.js index 1237547ebe..9daea6de99 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_avoidable.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_avoidable.js @@ -20,12 +20,12 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); assert($.get(computed5) === 6); for (let i = 0; i < 1000; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(computed5) === 6); @@ -45,7 +45,7 @@ export async function kairo_avoidable_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -74,7 +74,7 @@ export async function kairo_avoidable_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_broad.js b/benchmarking/benchmarks/reactivity/kairo/kairo_broad.js index 8148a743ea..8dc5710c87 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_broad.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_broad.js @@ -25,12 +25,12 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); counter = 0; for (let i = 0; i < 50; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(last) === i + 50); @@ -51,7 +51,7 @@ export async function kairo_broad_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -80,7 +80,7 @@ export async function kairo_broad_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_deep.js b/benchmarking/benchmarks/reactivity/kairo/kairo_deep.js index 806042cc72..8690c85f86 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_deep.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_deep.js @@ -25,12 +25,12 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); counter = 0; for (let i = 0; i < iter; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(current) === len + i); @@ -51,7 +51,7 @@ export async function kairo_deep_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -80,7 +80,7 @@ export async function kairo_deep_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_diamond.js b/benchmarking/benchmarks/reactivity/kairo/kairo_diamond.js index deb9482de9..bf4e07ee89 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_diamond.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_diamond.js @@ -28,13 +28,13 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); assert($.get(sum) === 2 * width); counter = 0; for (let i = 0; i < 500; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(sum) === (i + 1) * width); @@ -55,7 +55,7 @@ export async function kairo_diamond_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -84,7 +84,7 @@ export async function kairo_diamond_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_mux.js b/benchmarking/benchmarks/reactivity/kairo/kairo_mux.js index 8eafacc9eb..fc252a27b5 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_mux.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_mux.js @@ -22,13 +22,13 @@ function setup() { destroy, run() { for (let i = 0; i < 10; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(heads[i], i); }); assert($.get(splited[i]) === i + 1); } for (let i = 0; i < 10; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(heads[i], i * 2); }); assert($.get(splited[i]) === i * 2 + 1); @@ -48,7 +48,7 @@ export async function kairo_mux_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -77,7 +77,7 @@ export async function kairo_mux_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_repeated.js b/benchmarking/benchmarks/reactivity/kairo/kairo_repeated.js index 2bddf879c9..3bee06ca0e 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_repeated.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_repeated.js @@ -25,13 +25,13 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); assert($.get(current) === size); counter = 0; for (let i = 0; i < 100; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(current) === i * size); @@ -52,7 +52,7 @@ export async function kairo_repeated_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -81,7 +81,7 @@ export async function kairo_repeated_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_triangle.js b/benchmarking/benchmarks/reactivity/kairo/kairo_triangle.js index 9d99b7815b..11a419a52e 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_triangle.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_triangle.js @@ -38,13 +38,13 @@ function setup() { destroy, run() { const constant = count(width); - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); assert($.get(sum) === constant); counter = 0; for (let i = 0; i < 100; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); assert($.get(sum) === constant - width + i * width); @@ -65,7 +65,7 @@ export async function kairo_triangle_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -94,7 +94,7 @@ export async function kairo_triangle_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/kairo/kairo_unstable.js b/benchmarking/benchmarks/reactivity/kairo/kairo_unstable.js index c30c007561..54eb732cb2 100644 --- a/benchmarking/benchmarks/reactivity/kairo/kairo_unstable.js +++ b/benchmarking/benchmarks/reactivity/kairo/kairo_unstable.js @@ -25,13 +25,13 @@ function setup() { return { destroy, run() { - $.flush_sync(() => { + $.flush(() => { $.set(head, 1); }); assert($.get(current) === 40); counter = 0; for (let i = 0; i < 100; i++) { - $.flush_sync(() => { + $.flush(() => { $.set(head, i); }); } @@ -51,7 +51,7 @@ export async function kairo_unstable_unowned() { const { run, destroy } = setup(); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); @@ -80,7 +80,7 @@ export async function kairo_unstable_owned() { }); const { timing } = await fastest_test(10, () => { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 1000; i++) { run(); } }); diff --git a/benchmarking/benchmarks/reactivity/mol_bench.js b/benchmarking/benchmarks/reactivity/mol_bench.js index c9f492f619..536b078d74 100644 --- a/benchmarking/benchmarks/reactivity/mol_bench.js +++ b/benchmarking/benchmarks/reactivity/mol_bench.js @@ -51,11 +51,11 @@ function setup() { */ run(i) { res.length = 0; - $.flush_sync(() => { + $.flush(() => { $.set(B, 1); $.set(A, 1 + i * 2); }); - $.flush_sync(() => { + $.flush(() => { $.set(A, 2 + i * 2); $.set(B, 2); }); diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js index a5fc6d10a9..9d8d279c35 100644 --- a/benchmarking/compare/index.js +++ b/benchmarking/compare/index.js @@ -2,7 +2,6 @@ import fs from 'node:fs'; import path from 'node:path'; import { execSync, fork } from 'node:child_process'; import { fileURLToPath } from 'node:url'; -import { benchmarks } from '../benchmarks.js'; // if (execSync('git status --porcelain').toString().trim()) { // console.error('Working directory is not clean'); diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js index 6fa58e2bac..a2e8646379 100644 --- a/benchmarking/compare/runner.js +++ b/benchmarking/compare/runner.js @@ -1,7 +1,7 @@ -import { benchmarks } from '../benchmarks.js'; +import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js'; const results = []; -for (const benchmark of benchmarks) { +for (const benchmark of reactivity_benchmarks) { const result = await benchmark(); console.error(result.benchmark); results.push(result); diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md index e035e6d6df..c7351729ff 100644 --- a/documentation/docs/01-introduction/02-getting-started.md +++ b/documentation/docs/01-introduction/02-getting-started.md @@ -2,7 +2,7 @@ title: Getting started --- -We recommend using [SvelteKit](../kit), the official application framework from the Svelte team powered by [Vite](https://vite.dev/): +We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with: ```bash npx sv create myapp @@ -15,7 +15,9 @@ Don't worry if you don't know Svelte yet! You can ignore all the nice features S ## Alternatives to SvelteKit -You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well. +You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS, and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well. + +>[!NOTE] Vite is often used in standalone mode to build [single page apps (SPAs)](../kit/glossary#SPA), which you can also [build with SvelteKit](../kit/single-page-apps). There are also plugins for [Rollup](https://github.com/sveltejs/rollup-plugin-svelte), [Webpack](https://github.com/sveltejs/svelte-loader) [and a few others](https://sveltesociety.dev/packages?category=build-plugins), but we recommend Vite. diff --git a/documentation/docs/01-introduction/04-svelte-js-files.md b/documentation/docs/01-introduction/04-svelte-js-files.md index 0e05484299..1d3e3dd61a 100644 --- a/documentation/docs/01-introduction/04-svelte-js-files.md +++ b/documentation/docs/01-introduction/04-svelte-js-files.md @@ -4,7 +4,7 @@ title: .svelte.js and .svelte.ts files Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files. -These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app. +These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app (though note that you [cannot export reassigned state]($state#Passing-state-across-modules)). > [!LEGACY] > This is a concept that didn't exist prior to Svelte 5 diff --git a/documentation/docs/01-introduction/xx-props.md b/documentation/docs/01-introduction/xx-props.md deleted file mode 100644 index cad854d878..0000000000 --- a/documentation/docs/01-introduction/xx-props.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Public API of a component ---- - -### Public API of a component - -Svelte uses the `$props` rune to declare _properties_ or _props_, which means describing the public interface of the component which becomes accessible to consumers of the component. - -> [!NOTE] `$props` is one of several runes, which are special hints for Svelte's compiler to make things reactive. - -```svelte - -``` - -You can specify a fallback value for a prop. It will be used if the component's consumer doesn't specify the prop on the component when instantiating the component, or if the passed value is `undefined` at some point. - -```svelte - -``` - -To get all properties, use rest syntax: - -```svelte - -``` - -You can use reserved words as prop names. - -```svelte - -``` - -If you're using TypeScript, you can declare the prop types: - -```svelte - -``` - -If you're using JavaScript, you can declare the prop types using JSDoc: - -```svelte - -``` - -If you export a `const`, `class` or `function`, it is readonly from outside the component. - -```svelte - -``` - -Readonly props can be accessed as properties on the element, tied to the component using [`bind:this` syntax](bindings#bind:this). - -### Reactive variables - -To change component state and trigger a re-render, just assign to a locally declared variable that was declared using the `$state` rune. - -Update expressions (`count += 1`) and property assignments (`obj.x = y`) have the same effect. - -```svelte - -``` - -Svelte's ` -``` - -If you'd like to react to changes to a prop, use the `$derived` or `$effect` runes instead. - -```svelte - -``` - -For more information on reactivity, read the documentation around runes. diff --git a/documentation/docs/01-introduction/xx-reactivity-fundamentals.md b/documentation/docs/01-introduction/xx-reactivity-fundamentals.md deleted file mode 100644 index d5e67ada71..0000000000 --- a/documentation/docs/01-introduction/xx-reactivity-fundamentals.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Reactivity fundamentals ---- - -Reactivity is at the heart of interactive UIs. When you click a button, you expect some kind of response. It's your job as a developer to make this happen. It's Svelte's job to make your job as intuitive as possible, by providing a good API to express reactive systems. - -## Runes - -Svelte 5 uses _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and inside `.svelte.js` and `.svelte.ts` modules. - -Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language. - -The following sections introduce the most important runes for declare state, derived state and side effects at a high level. For more details refer to the later sections on [state](state) and [side effects](side-effects). - -## `$state` - -Reactive state is declared with the `$state` rune: - -```svelte - - - -``` - -You can also use `$state` in class fields (whether public or private): - -```js -// @errors: 7006 2554 -class Todo { - done = $state(false); - text = $state(); - - constructor(text) { - this.text = text; - } -} -``` - -> [!LEGACY] -> In Svelte 4, state was implicitly reactive if the variable was declared at the top level -> -> ```svelte -> -> -> -> ``` - -## `$derived` - -Derived state is declared with the `$derived` rune: - -```svelte - - - - -

{count} doubled is {doubled}

-``` - -The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions. - -As with `$state`, you can mark class fields as `$derived`. - -> [!LEGACY] -> In Svelte 4, you could use reactive statements for this. -> -> ```svelte -> -> -> -> ->

{count} doubled is {doubled}

-> ``` -> -> This only worked at the top level of a component. - -## `$effect` - -To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)): - -```svelte - - - -``` - -The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. - -> [!LEGACY] -> In Svelte 4, you could use reactive statements for this. -> -> ```svelte -> -> -> -> ``` -> -> This only worked at the top level of a component. diff --git a/documentation/docs/02-runes/01-what-are-runes.md b/documentation/docs/02-runes/01-what-are-runes.md index dc163ebdf1..59c371eb49 100644 --- a/documentation/docs/02-runes/01-what-are-runes.md +++ b/documentation/docs/02-runes/01-what-are-runes.md @@ -2,7 +2,7 @@ title: What are runes? --- -> [!NOTE] **rune** /ro͞on/ _noun_ +> [!NOTE] **rune** /ruːn/ _noun_ > > A letter or mark used as a mystical or magic symbol. diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md index 77140dc690..16630a977b 100644 --- a/documentation/docs/02-runes/02-$state.md +++ b/documentation/docs/02-runes/02-$state.md @@ -44,12 +44,7 @@ todos[0].done = !todos[0].done; If you push a new object to the array, it will also be proxified: ```js -// @filename: ambient.d.ts -declare global { - const todos: Array<{ done: boolean, text: string }> -} - -// @filename: index.js +let todos = [{ done: false, text: 'add more todos' }]; // ---cut--- todos.push({ done: false, @@ -255,3 +250,83 @@ console.log(total.value); // 7 ``` ...though if you find yourself writing code like that, consider using [classes](#Classes) instead. + +## Passing state across modules + +You can declare state in `.svelte.js` and `.svelte.ts` files, but you can only _export_ that state if it's not directly reassigned. In other words you can't do this: + +```js +/// file: state.svelte.js +export let count = $state(0); + +export function increment() { + count += 1; +} +``` + +That's because every reference to `count` is transformed by the Svelte compiler — the code above is roughly equivalent to this: + +```js +/// file: state.svelte.js (compiler output) +// @filename: index.ts +interface Signal { + value: T; +} + +interface Svelte { + state(value?: T): Signal; + get(source: Signal): T; + set(source: Signal, value: T): void; +} +declare const $: Svelte; +// ---cut--- +export let count = $.state(0); + +export function increment() { + $.set(count, $.get(count) + 1); +} +``` + +> [!NOTE] You can see the code Svelte generates by clicking the 'JS Output' tab in the [playground](/playground). + +Since the compiler only operates on one file at a time, if another file imports `count` Svelte doesn't know that it needs to wrap each reference in `$.get` and `$.set`: + +```js +// @filename: state.svelte.js +export let count = 0; + +// @filename: index.js +// ---cut--- +import { count } from './state.svelte.js'; + +console.log(typeof count); // 'object', not 'number' +``` + +This leaves you with two options for sharing state between modules — either don't reassign it... + +```js +// This is allowed — since we're updating +// `counter.count` rather than `counter`, +// Svelte doesn't wrap it in `$.state` +export const counter = $state({ + count: 0 +}); + +export function increment() { + counter.count += 1; +} +``` + +...or don't directly export it: + +```js +let count = $state(0); + +export function getCount() { + return count; +} + +export function increment() { + count += 1; +} +``` diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index 6b38f99746..2464aa9295 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -51,3 +51,62 @@ In essence, `$derived(expression)` is equivalent to `$derived.by(() => expressio Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read. To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack). + +## Overriding derived values + +Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them (unless they are declared with `const`). This can be useful for things like _optimistic UI_, where a value is derived from the 'source of truth' (such as data from your server) but you'd like to show immediate feedback to the user: + +```svelte + + + +``` + +> [!NOTE] Prior to Svelte 5.25, deriveds were read-only. + +## Deriveds and reactivity + +Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)... + +```svelte +let items = $state([...]); + +let index = $state(0); +let selected = $derived(items[index]); +``` + +...you can change (or `bind:` to) properties of `selected` and it will affect the underlying `items` array. If `items` was _not_ deeply reactive, mutating `selected` would have no effect. + +## Update propagation + +Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull'). + +If the new value of a derived is referentially identical to its previous value, downstream updates will be skipped. In other words, Svelte will only update the text inside the button when `large` changes, not when `count` changes, even though `large` depends on `count`: + +```svelte + + + +``` diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index b338795220..46ea9b81e9 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -2,15 +2,11 @@ title: $effect --- -Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes. +Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on `` elements, or making network requests. They only run in the browser, not during server-side rendering. -Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `

hello {name}!

` when `name` changes, for example. +Generally speaking, you should _not_ update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, see [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches. -But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `` element, or something across a network) with state inside your Svelte app. - -> [!NOTE] Avoid overusing `$effect`! When you do too much work in effects, code often becomes difficult to understand and maintain. See [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches. - -Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)): +You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)): ```svelte - + ``` -Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. +When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes. + +> [!NOTE] If you're having difficulty understanding why your `$effect` is rerunning or is not running see [understanding dependencies](#Understanding-dependencies). Effects are triggered differently than the `$:` blocks you may be used to if coming from Svelte 4. + +### Understanding lifecycle -You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed). +Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes. Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. -You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/playground/untitled#H4sIAAAAAAAAE42RQY-bMBCF_8rI2kPopiXpMQtIPfbeW6m0xjyKtWaM7CFphPjvFVB2k2oPe7LmzXzyezOjaqxDVKefo5JrD3VaBLVXrLu5-tb3X-IZTmat0hHv6cazgCWqk8qiCbaXouRSHISMH1gop4coWrA7JE9bp7PO2QjjuY5vA8fDYZ3hUh7QNDCy2yWUFzTOUilpSj9aG-linaMKFGACtKCmSwvGGYGeLQvCWbtnMq3m34grajxHoa1JOUXI93_V_Sfz7Oz7Mafj0ypN-zvHm8dSAmQITP_xaUq2IU1GO1dp80I2Uh_82dao92Rl9R8GvgF0QrbrUFstcFeq0PgAkha0LoICPoeB4w1SJUvsZcj4rvcMlvmvGlGCv6J-DeSgw2vabQnJlm55p7nM0rcTctYei3HZxZSl7XHVqkHEM3k2zpqXfFyj393zU05fpyI6f0HI0hUoPoamC9roKDeo2ivBH1EnCQOmX9NfYw2GHrgCAAA=)). +You can use `$effect` anywhere, not just at the top level of a component, as long as it is called while a parent effect is running. + +> [!NOTE] Svelte uses effects internally to represent logic and expressions in your template — this is how `

hello {name}!

` updates when `name` changes. + +An effect can return a _teardown function_ which will run immediately before the effect re-runs ([demo](/playground/untitled#H4sIAAAAAAAAE42SQVODMBCF_8pOxkPRKq3HCsx49K4n64xpskjGkDDJ0tph-O8uINo6HjxB3u7HvrehE07WKDbiyZEhi1osRWksRrF57gQdm6E2CKx_dd43zU3co6VB28mIf-nKO0JH_BmRRRVMQ8XWbXkAgfKtI8jhIpIkXKySu7lSG2tNRGZ1_GlYr1ZTD3ddYFmiosUigbyAbpC2lKbwWJkIB8ZhhxBQBWRSw6FCh3sM8GrYTthL-wqqku4N44TyqEgwF3lmRHr4Op0PGXoH31c5rO8mqV-eOZ49bikgtcHBL55tmhIkEMqg_cFB2TpFxjtg703we6NRL8HQFCS07oSUCZi6Rm04lz1yytIHBKoQpo1w6Gsm4gmyS8b8Y5PydeMdX8gwS2Ok4I-ov5NZtvQde95GMsccn_1wzNKfu3RZtS66cSl9lvL7qO1aIk7knbJGvefdtIOzi73M4bYvovUHDFk6AcX_0HRESxnpBOW_jfCDxIZCi_1L_wm4xGQ60wIAAA==)). ```svelte +// later... +destroy(); ``` ## When not to use `$effect` @@ -288,6 +267,8 @@ In general, `$effect` is best considered something of an escape hatch — useful > [!NOTE] For things that are more complicated than a simple expression like `count * 2`, you can also use `$derived.by`. +If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25. + You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)): ```svelte @@ -316,7 +297,7 @@ You might be tempted to do something convoluted with effects to link one value t ``` -Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)): +Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)): ```svelte ``` -If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)): - -```svelte - - - - - -``` - If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack). diff --git a/documentation/docs/02-runes/05-$props.md b/documentation/docs/02-runes/05-$props.md index 58e9b36f8e..f300fb239d 100644 --- a/documentation/docs/02-runes/05-$props.md +++ b/documentation/docs/02-runes/05-$props.md @@ -196,4 +196,27 @@ You can, of course, separate the type declaration from the annotation: ``` +> [!NOTE] Interfaces for native DOM elements are provided in the `svelte/elements` module (see [Typing wrapper components](typescript#Typing-wrapper-components)) + Adding types is recommended, as it ensures that people using your component can easily discover which props they should provide. + + +## `$props.id()` + +This rune, added in version 5.20.0, generates an ID that is unique to the current component instance. When hydrating a server-rendered component, the value will be consistent between server and client. + +This is useful for linking elements via attributes like `for` and `aria-labelledby`. + +```svelte + + +
+ + + + + +
+``` \ No newline at end of file diff --git a/documentation/docs/02-runes/06-$bindable.md b/documentation/docs/02-runes/06-$bindable.md index 14bc8ddbec..c12c2bf490 100644 --- a/documentation/docs/02-runes/06-$bindable.md +++ b/documentation/docs/02-runes/06-$bindable.md @@ -33,7 +33,7 @@ Now, a component that uses `` can add the [`bind:`](bind) directive ```svelte -/// App.svelte +/// file: App.svelte +``` + +`$inspect.trace` takes an optional first argument which will be used as the label. diff --git a/documentation/docs/02-runes/08-$host.md b/documentation/docs/02-runes/08-$host.md index 7b5e041e5e..ba6f0a5b5b 100644 --- a/documentation/docs/02-runes/08-$host.md +++ b/documentation/docs/02-runes/08-$host.md @@ -2,7 +2,7 @@ title: $host --- -When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): +When compiling a component as a [custom element](custom-elements), the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): ```svelte diff --git a/documentation/docs/03-template-syntax/01-basic-markup.md b/documentation/docs/03-template-syntax/01-basic-markup.md index b41dc187c3..5e8b4342d3 100644 --- a/documentation/docs/03-template-syntax/01-basic-markup.md +++ b/documentation/docs/03-template-syntax/01-basic-markup.md @@ -185,7 +185,7 @@ You can use HTML comments inside components. Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason. ```svelte - + ``` diff --git a/documentation/docs/03-template-syntax/03-each.md b/documentation/docs/03-template-syntax/03-each.md index df0ba4d8f5..70666f6a57 100644 --- a/documentation/docs/03-template-syntax/03-each.md +++ b/documentation/docs/03-template-syntax/03-each.md @@ -23,8 +23,6 @@ Iterating over values can be done with an each block. The values in question can ``` -You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property. - An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback: ```svelte diff --git a/documentation/docs/03-template-syntax/06-snippet.md b/documentation/docs/03-template-syntax/06-snippet.md index f8148f3dc3..c9951d3f34 100644 --- a/documentation/docs/03-template-syntax/06-snippet.md +++ b/documentation/docs/03-template-syntax/06-snippet.md @@ -112,7 +112,7 @@ Snippets can reference themselves and each other ([demo](/playground/untitled#H4 ## Passing snippets to components -Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): +Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)): ```svelte - + ``` Components also support `bind:this`, allowing you to interact with component instances programmatically. diff --git a/documentation/docs/03-template-syntax/12-use.md b/documentation/docs/03-template-syntax/12-use.md index f3db72a772..45de023578 100644 --- a/documentation/docs/03-template-syntax/12-use.md +++ b/documentation/docs/03-template-syntax/12-use.md @@ -50,17 +50,16 @@ The `Action` interface receives three optional type arguments — a node type (w ```svelte + + +
...
+``` + +If the value is an array, the truthy values are combined: + +```svelte + +
...
+``` + +Note that whether we're using the array or object form, we can set multiple classes simultaneously with a single condition, which is particularly useful if you're using things like Tailwind. + +Arrays can contain arrays and objects, and clsx will flatten them. This is useful for combining local classes with props, for example: + +```svelte + + + + +``` + +The user of this component has the same flexibility to use a mixture of objects, arrays and strings: + +```svelte + + + + +``` + +Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props: + +```svelte + + +
...
+``` + +## The `class:` directive + +Prior to Svelte 5.16, the `class:` directive was the most convenient way to set classes on elements conditionally. + +```svelte + +
...
+
...
+``` + +As with other directives, we can use a shorthand when the name of the class coincides with the value: + +```svelte +
...
+``` + +> [!NOTE] Unless you're using an older version of Svelte, consider avoiding `class:`, since the attribute is more powerful and composable. diff --git a/documentation/docs/03-template-syntax/xx-control-flow.md b/documentation/docs/03-template-syntax/xx-control-flow.md deleted file mode 100644 index b73917997b..0000000000 --- a/documentation/docs/03-template-syntax/xx-control-flow.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Control flow ---- - -- if -- each -- await (or move that into some kind of data loading section?) -- NOT: key (move into transition section, because that's the common use case) - -Svelte augments HTML with control flow blocks to be able to express conditionally rendered content or lists. - -The syntax between these blocks is the same: - -- `{#` denotes the start of a block -- `{:` denotes a different branch part of the block. Depending on the block, there can be multiple of these -- `{/` denotes the end of a block - -## {#if ...} - -## {#each ...} - -```svelte - -{#each expression as name}...{/each} -``` - -```svelte - -{#each expression as name, index}...{/each} -``` - -```svelte - -{#each expression as name (key)}...{/each} -``` - -```svelte - -{#each expression as name, index (key)}...{/each} -``` - -```svelte - -{#each expression as name}...{:else}...{/each} -``` - -Iterating over lists of values can be done with an each block. - -```svelte -

Shopping list

-
    - {#each items as item} -
  • {item.name} x {item.qty}
  • - {/each} -
-``` - -You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property. - -An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback: - -```svelte -{#each items as item, i} -
  • {i + 1}: {item.name} x {item.qty}
  • -{/each} -``` - -If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. - -```svelte -{#each items as item (item.id)} -
  • {item.name} x {item.qty}
  • -{/each} - - -{#each items as item, i (item.id)} -
  • {i + 1}: {item.name} x {item.qty}
  • -{/each} -``` - -You can freely use destructuring and rest patterns in each blocks. - -```svelte -{#each items as { id, name, qty }, i (id)} -
  • {i + 1}: {name} x {qty}
  • -{/each} - -{#each objects as { id, ...rest }} -
  • {id}
  • -{/each} - -{#each items as [id, ...rest]} -
  • {id}
  • -{/each} -``` - -An each block can also have an `{:else}` clause, which is rendered if the list is empty. - -```svelte -{#each todos as todo} -

    {todo.text}

    -{:else} -

    No tasks today!

    -{/each} -``` - -It is possible to iterate over iterables like `Map` or `Set`. Iterables need to be finite and static (they shouldn't change while being iterated over). Under the hood, they are transformed to an array using `Array.from` before being passed off to rendering. If you're writing performance-sensitive code, try to avoid iterables and use regular arrays as they are more performant. - -## Other block types - -Svelte also provides [`#snippet`](snippets), [`#key`](transitions-and-animations) and [`#await`](data-fetching) blocks. You can find out more about them in their respective sections. diff --git a/documentation/docs/03-template-syntax/xx-data-fetching.md b/documentation/docs/03-template-syntax/xx-data-fetching.md deleted file mode 100644 index 4526d51335..0000000000 --- a/documentation/docs/03-template-syntax/xx-data-fetching.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Data fetching ---- - -Fetching data is a fundamental part of apps interacting with the outside world. Svelte is unopinionated with how you fetch your data. The simplest way would be using the built-in `fetch` method: - -```svelte - -``` - -While this works, it makes working with promises somewhat unergonomic. Svelte alleviates this problem using the `#await` block. - -## {#await ...} - -## SvelteKit loaders - -Fetching inside your components is great for simple use cases, but it's prone to data loading waterfalls and makes code harder to work with because of the promise handling. SvelteKit solves this problem by providing a opinionated data loading story that is coupled to its router. Learn more about it [in the docs](../kit). diff --git a/documentation/docs/04-styling/01-scoped-styles.md b/documentation/docs/04-styling/01-scoped-styles.md index f870d0a5b8..eae26d0cb1 100644 --- a/documentation/docs/04-styling/01-scoped-styles.md +++ b/documentation/docs/04-styling/01-scoped-styles.md @@ -33,10 +33,7 @@ If a component defines `@keyframes`, the name is scoped to the component using t /* these keyframes are only accessible inside this component */ @keyframes bounce { - /* ... *. + /* ... */ } ``` - - - diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index 15f249a771..f5439b4b83 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -13,7 +13,7 @@ Boundaries allow you to guard against errors in part of your app from breaking t If an error occurs while rendering or updating the children of a ``, or running any [`$effect`]($effect) functions contained therein, the contents will be removed. -Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries. +Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries. ## Properties diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index 30799215b6..4204bcfe6d 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -2,129 +2,137 @@ title: Context --- - +Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`... -Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow. - -The easiest way to do that is to create global state and just import that. +```svelte + + ``` +...and the child retrieves it with `getContext`: + ```svelte - + + +

    {message}, inside Child.svelte

    ``` -This has a few drawbacks though: +This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)): + +```svelte + + + +``` -- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs -- it may give the false impression that certain state is global when in reality it should only used in a certain part of your app +The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value. -To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems. +In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions. -## Setting and getting context +## Using context with state -To associate an arbitrary object with the current component, use `setContext`. +You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))... ```svelte + + + + + + ``` -The context is then available to children of the component (including slotted content) with `getContext`. +...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this... ```svelte - + ``` -`setContext` and `getContext` solve the above problems: +...you must do this: -- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state -- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app +```svelte + +``` -> [!NOTE] `setContext`/`getContext` must be called during component initialisation. +Svelte will warn you if you get it wrong. -Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive. +## Type-safe context -```svelte - - +```js +/// file: context.js +// @filename: ambient.d.ts +interface User {} - -``` +// @filename: index.js +// ---cut--- +import { getContext, setContext } from 'svelte'; -```svelte - - +/** @param {User} user */ +export function setUserContext(user) { + setContext(key, user); +} -

    Count is {value.count}

    +export function getUserContext() { + return /** @type {User} */ (getContext(key)); +} ``` -To check whether a given `key` has been set in the context of a parent component, use `hasContext`. +## Replacing global state -```svelte - + // ... +}); ``` -You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it. +In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)... ```svelte + ``` -## Encapsulating context interactions - -The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them. - -```ts -// @errors: 2304 -import { getContext, setContext } from 'svelte'; - -let userKey = Symbol('user'); - -export function setUserContext(user: User) { - setContext(userKey, user); -} - -export function getUserContext(): User { - return getContext(userKey) as User; -} -``` +...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests. diff --git a/documentation/docs/06-runtime/03-lifecycle-hooks.md b/documentation/docs/06-runtime/03-lifecycle-hooks.md index a3dbe04b00..f051c46d73 100644 --- a/documentation/docs/06-runtime/03-lifecycle-hooks.md +++ b/documentation/docs/06-runtime/03-lifecycle-hooks.md @@ -45,8 +45,6 @@ If a function is returned from `onMount`, it will be called when the component i ## `onDestroy` -> EXPORT_SNIPPET: svelte#onDestroy - Schedules a callback to run immediately before the component is unmounted. Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component. @@ -149,7 +147,7 @@ With runes, we can use `$effect.pre`, which behaves the same as `$effect` but ru } function toggle() { - toggleValue = !toggleValue; + theme = theme === 'dark' ? 'light' : 'dark'; } diff --git a/documentation/docs/06-runtime/04-imperative-component-api.md b/documentation/docs/06-runtime/04-imperative-component-api.md index ffd03d85a6..16a2c8151c 100644 --- a/documentation/docs/06-runtime/04-imperative-component-api.md +++ b/documentation/docs/06-runtime/04-imperative-component-api.md @@ -33,19 +33,22 @@ Note that unlike calling `new App(...)` in Svelte 4, things like effects (includ ## `unmount` -Unmounts a component created with [`mount`](#mount) or [`hydrate`](#hydrate): +Unmounts a component that was previously created with [`mount`](#mount) or [`hydrate`](#hydrate). + +If `options.outro` is `true`, [transitions](transition) will play before the component is removed from the DOM: ```js -// @errors: 1109 import { mount, unmount } from 'svelte'; import App from './App.svelte'; -const app = mount(App, {...}); +const app = mount(App, { target: document.body }); // later -unmount(app); +unmount(app, { outro: true }); ``` +Returns a `Promise` that resolves after transitions have completed if `options.outro` is true, or immediately otherwise. + ## `render` Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app: diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index c8774e341f..0842019039 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -40,7 +40,7 @@ You can now write unit tests for code inside your `.js/.ts` files: /// file: multiplier.svelte.test.js import { flushSync } from 'svelte'; import { expect, test } from 'vitest'; -import { multiplier } from './multiplier.js'; +import { multiplier } from './multiplier.svelte.js'; test('Multiplier', () => { let double = multiplier(0, 2); @@ -53,9 +53,30 @@ test('Multiplier', () => { }); ``` +```js +/// file: multiplier.svelte.js +/** + * @param {number} initial + * @param {number} k + */ +export function multiplier(initial, k) { + let count = $state(initial); + + return { + get value() { + return count * k; + }, + /** @param {number} c */ + set: (c) => { + count = c; + } + }; +} +``` + ### Using runes inside your test files -It is possible to use runes inside your test files. First ensure your bundler knows to route the file through the Svelte compiler before running the test by adding `.svelte` to the filename (e.g `multiplier.svelte.test.js`). After that, you can use runes inside your tests. +Since Vitest processes your test files the same way as your source files, you can use runes inside your tests as long as the filename includes `.svelte`: ```js /// file: multiplier.svelte.test.js @@ -75,6 +96,21 @@ test('Multiplier', () => { }); ``` +```js +/// file: multiplier.svelte.js +/** + * @param {() => number} getCount + * @param {number} k + */ +export function multiplier(getCount, k) { + return { + get value() { + return getCount() * k; + } + }; +} +``` + If the code being tested uses effects, you need to wrap the test inside `$effect.root`: ```js @@ -105,6 +141,27 @@ test('Effect', () => { }); ``` +```js +/// file: logger.svelte.js +/** + * @param {() => any} getValue + */ +export function logger(getValue) { + /** @type {any[]} */ + let log = $state([]); + + $effect(() => { + log.push(getValue()); + }); + + return { + get value() { + return log; + } + }; +} +``` + ### Component testing It is possible to test your components in isolation using Vitest. diff --git a/documentation/docs/07-misc/04-custom-elements.md b/documentation/docs/07-misc/04-custom-elements.md index 71c66f7edc..a8e0c81763 100644 --- a/documentation/docs/07-misc/04-custom-elements.md +++ b/documentation/docs/07-misc/04-custom-elements.md @@ -125,3 +125,4 @@ Custom elements can be a useful way to package components for consumption in a n - The deprecated `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot - Polyfills are required to support older browsers - You can use Svelte's context feature between regular Svelte components within a custom element, but you can't use them across custom elements. In other words, you can't use `setContext` on a parent custom element and read that with `getContext` in a child custom element. +- Don't declare properties or attributes starting with `on`, as their usage will be interpreted as an event listener. In other words, Svelte treats `` as `customElement.addEventListener('eworld', true)` (and not as `customElement.oneworld = true`) diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md index 09ea84f26c..e502b7921a 100644 --- a/documentation/docs/07-misc/07-v5-migration-guide.md +++ b/documentation/docs/07-misc/07-v5-migration-guide.md @@ -10,13 +10,13 @@ You don't have to migrate to the new syntax right away - Svelte 5 still supports At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign. -### let -> $state +### let → $state In Svelte 4, a `let` declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the `$state` rune. Let's migrate the counter to runes mode by wrapping the counter in `$state`: ```svelte ``` @@ -25,14 +25,14 @@ Nothing else changes. `count` is still the number itself, and you read and write > [!DETAILS] Why we did this > `let` being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a `let` declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn't as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to [the tutorial](/tutorial) to learn more. -### $: -> $derived/$effect +### $: → $derived/$effect In Svelte 4, a `$:` statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the `$derived` rune: ```svelte ``` @@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is ```svelte ``` +Note that [when `$effect` runs is different]($effect#Understanding-dependencies) than when `$:` runs. + > [!DETAILS] Why we did this > `$:` was a great shorthand and easy to get started with: you could slap a `$:` in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn't as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With `$derived` and `$effect`, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want `$derived`), but future-you and other developers on your team will have an easier time. > @@ -71,14 +74,14 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is > - executing dependencies as needed and therefore being immune to ordering problems > - being TypeScript-friendly -### export let -> $props +### export let → $props In Svelte 4, properties of a component were declared using `export let`. Each property was one declaration. In Svelte 5, all properties are declared through the `$props` rune, through destructuring: ```svelte ``` @@ -103,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional ```svelte @@ -190,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call ```svelte @@ -339,7 +342,31 @@ When spreading props, local event handlers must go _after_ the spread, or they r In Svelte 4, content can be passed to components using slots. Svelte 5 replaces them with snippets which are more powerful and flexible, and as such slots are deprecated in Svelte 5. -They continue to work, however, and you can mix and match snippets and slots in your components. +They continue to work, however, and you can pass snippets to a component that uses slots: + +```svelte + + +
    + +``` + +```svelte + + + + + default child content + + {#snippet foo({ message })} + message from child: {message} + {/snippet} + +``` + +(The reverse is not true — you cannot pass slotted content to a component that uses [`{@render ...}`](/docs/svelte/@render) tags.) When using custom elements, you should still use `` like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it. @@ -440,11 +467,11 @@ By now you should have a pretty good understanding of the before/after and how t We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using `npx sv migrate svelte-5`. This will do the following things: - bump core dependencies in your `package.json` -- migrate to runes (`let` -> `$state` etc) -- migrate to event attributes for DOM elements (`on:click` -> `onclick`) -- migrate slot creations to render tags (`` -> `{@render children()}`) -- migrate slot usages to snippets (`
    ...
    ` -> `{#snippet x()}
    ...
    {/snippet}`) -- migrate obvious component creations (`new Component(...)` -> `mount(Component, ...)`) +- migrate to runes (`let` → `$state` etc) +- migrate to event attributes for DOM elements (`on:click` → `onclick`) +- migrate slot creations to render tags (`` → `{@render children()}`) +- migrate slot usages to snippets (`
    ...
    ` → `{#snippet x()}
    ...
    {/snippet}`) +- migrate obvious component creations (`new Component(...)` → `mount(Component, ...)`) You can also migrate a single component in VS Code through the `Migrate Component to Svelte 5 Syntax` command, or in our Playground through the `Migrate` button. @@ -597,28 +624,57 @@ export declare const MyComponent: Component<{ To declare that a component of a certain type is required: +```js +import { ComponentA, ComponentB } from 'component-library'; +---import type { SvelteComponent } from 'svelte';--- ++++import type { Component } from 'svelte';+++ + +---let C: typeof SvelteComponent<{ foo: string }> = $state(--- ++++let C: Component<{ foo: string }> = $state(+++ + Math.random() ? ComponentA : ComponentB +); +``` + +The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (i.e. `ComponentType>` is equivalent to `Component<{ prop: string }>`). + +### bind:this changes + +Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property. + +## `` is no longer necessary + +In Svelte 4, components are _static_ — if you render ``, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you had to use ``. + +This is no longer true in Svelte 5: + ```svelte - - + + + + + ``` +While migrating, keep in mind that your component's name should be capitalized (`Thing`) to distinguish it from elements, unless using dot notation. -The two utility types `ComponentEvents` and `ComponentType` are also deprecated. `ComponentEvents` is obsolete because events are defined as callback props now, and `ComponentType` is obsolete because the new `Component` type is the component type already (e.g. `ComponentType>` == `Component<{ prop: string }>`). +### Dot notation indicates a component -### bind:this changes +In Svelte 4, `` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks: -Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property. +```svelte +{#each items as item} + +{/each} +``` ## Whitespace handling changed @@ -653,16 +709,6 @@ The `legacy` compiler option, which generated bulkier but IE-friendly code, no l Content inside component tags becomes a snippet prop called `children`. You cannot have a separate prop by that name. -## Dot notation indicates a component - -In Svelte 4, `` would create an element with a tag name of `"foo.bar"`. In Svelte 5, `foo.bar` is treated as a component instead. This is particularly useful inside `each` blocks: - -```svelte -{#each items as item} - -{/each} -``` - ## Breaking changes in runes mode Some breaking changes only apply once your component is in runes mode. @@ -679,51 +725,59 @@ If a bindable property has a default value (e.g. `let { foo = $bindable('bar') } ### `accessors` option is ignored -Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them. +Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. -### `immutable` option is ignored - -Setting the `immutable` option has no effect in runes mode. This concept is replaced by how `$state` and its variations work. +```svelte + -### Classes are no longer "auto-reactive" + +``` -In Svelte 4, doing the following triggered reactivity: +In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them. ```svelte +``` - +Alternatively, if the place where they are instantiated is under your control, you can also make use of runes inside `.js/.ts` files by adjusting their ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`, and then use `$state`: + +```js ++++import { mount } from 'svelte';+++ +import App from './App.svelte' + +---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } }); +app.foo = 'baz'--- ++++const props = $state({ foo: 'bar' }); +const app = mount(App, { target: document.getElementById("app"), props }); +props.foo = 'baz';+++ ``` -This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive. +### `immutable` option is ignored -### `` is no longer necessary +Setting the `immutable` option has no effect in runes mode. This concept is replaced by how `$state` and its variations work. -In Svelte 4, components are _static_ — if you render ``, and the value of `Thing` changes, [nothing happens](/playground/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you must use ``. +### Classes are no longer "auto-reactive" -This is no longer true in Svelte 5: +In Svelte 4, doing the following triggered reactivity: ```svelte - - - - - + ``` +This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive. + ### Touch and wheel events are passive When using `onwheel`, `onmousewheel`, `ontouchstart` and `ontouchmove` event attributes, the handlers are [passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) to align with browser defaults. This greatly improves responsiveness by allowing the browser to scroll the document immediately, rather than waiting to see if the event handler calls `event.preventDefault()`. diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index b56c27af86..ed5c6277c0 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -46,7 +46,7 @@ It will show up on hover. - You can use markdown here. - You can also use code blocks here. - Usage: - ```tsx + ```svelte
    ``` --> @@ -96,7 +96,7 @@ However, you can use any router library. A lot of people use [page.js](https://g If you prefer a declarative HTML approach, there's the isomorphic [svelte-routing](https://github.com/EmilTholin/svelte-routing) library and a fork of it called [svelte-navigator](https://github.com/mefechoel/svelte-navigator) containing some additional functionality. -If you need hash-based routing on the client side, check out [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router) or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/). +If you need hash-based routing on the client side, check out the [hash option](https://svelte.dev/docs/kit/configuration#router) in SvelteKit, [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/). [Routify](https://routify.dev) is another filesystem-based router, similar to SvelteKit's router. Version 3 supports Svelte's native SSR. diff --git a/documentation/docs/07-misc/xx-reactivity-indepth.md b/documentation/docs/07-misc/xx-reactivity-indepth.md deleted file mode 100644 index b40072552f..0000000000 --- a/documentation/docs/07-misc/xx-reactivity-indepth.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Reactivity in depth ---- - -- how to think about Runes ("just JavaScript" with added reactivity, what this means for keeping reactivity alive across boundaries) -- signals diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 90c9c1f9d1..32348bb781 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -21,15 +21,19 @@ A component is attempting to bind to a non-bindable property `%key%` belonging t ### component_api_changed ``` -%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information +Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5 ``` +See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information. + ### component_api_invalid_new ``` -Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes for more information +Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. ``` +See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information. + ### derived_references_self ``` @@ -118,14 +122,39 @@ Property descriptors defined on `$state` objects must contain `value` and always Cannot set prototype of `$state` object ``` -### state_unsafe_local_read +### state_unsafe_mutation ``` -Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state +Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` ``` -### state_unsafe_mutation +This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go: + +```svelte + + + + +

    {count} is even: {even}

    +

    {count} is odd: {odd}

    ``` -Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` + +This is forbidden because it introduces instability: if `

    {count} is even: {even}

    ` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived: + +```js +let count = 0; +// ---cut--- +let even = $derived(count % 2 === 0); +let odd = $derived(!even); ``` + +If side-effects are unavoidable, use [`$effect`]($effect) instead. diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index ef19a28994..77d1df4cdd 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -52,7 +52,7 @@ Your `console.%method%` contained `$state` proxies. Consider using `$inspect(... When logging a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), browser devtools will log the proxy itself rather than the value it represents. In the case of Svelte, the 'target' of a `$state` proxy might not resemble its current value, which can be confusing. -The easiest way to log a value as it changes over time is to use the [`$inspect`](https://svelte.dev/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](https://svelte.dev/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value. +The easiest way to log a value as it changes over time is to use the [`$inspect`](/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value. ### event_handler_invalid @@ -66,6 +66,31 @@ The easiest way to log a value as it changes over time is to use the [`$inspect` The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value ``` +Certain attributes like `src` on an `` element will not be repaired during hydration, i.e. the server value will be kept. That's because updating these attributes can cause the image to be refetched (or in the case of an ` +
    +
      +
      + + + +
      + + + + + + + + +
      +
        +
        + + + +
        + + + + + + + + +
        +
          +
          + +` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte new file mode 100644 index 0000000000..e9e5a16168 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte @@ -0,0 +1,22 @@ + + +{#each attributeValues as val} + +
          + + + + + + + + +
          +
            +
            + +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js new file mode 100644 index 0000000000..ab94125503 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/_config.js @@ -0,0 +1,33 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ target, assert }) { + // Test for https://github.com/sveltejs/svelte/issues/15237 + const [setValues, clearValue] = target.querySelectorAll('button'); + const [text1, text2, check1, check2] = target.querySelectorAll('input'); + + assert.equal(text1.value, ''); + assert.equal(text2.value, ''); + assert.equal(check1.checked, false); + assert.equal(check2.checked, false); + + flushSync(() => { + setValues.click(); + }); + + assert.equal(text1.value, 'message'); + assert.equal(text2.value, 'message'); + assert.equal(check1.checked, true); + assert.equal(check2.checked, true); + + flushSync(() => { + clearValue.click(); + }); + + assert.equal(text1.value, ''); + assert.equal(text2.value, ''); + assert.equal(check1.checked, false); + assert.equal(check2.checked, false); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte new file mode 100644 index 0000000000..4bb4365ee2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-input/main.svelte @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/_config.js b/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/_config.js new file mode 100644 index 0000000000..0597c2fda8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, errors }) { + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/main.svelte b/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/main.svelte new file mode 100644 index 0000000000..cb3804af34 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/autofocus-with-call/main.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/_config.js b/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/_config.js new file mode 100644 index 0000000000..f81b41d41a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + ssrHtml: '

            42


            loading...

            ', + html: '

            loading...


            42

            ', + + props: { + browser: true + }, + + server_props: { + browser: false + }, + + async test({ assert, target }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, '

            42


            42

            '); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/main.svelte b/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/main.svelte new file mode 100644 index 0000000000..d8d0cd4027 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/await-hydrate-maybe-promise/main.svelte @@ -0,0 +1,25 @@ + + + + +{#await a} + {#if true}

            loading...

            {/if} +{:then a} +

            {a}

            +{/await} + +
            + +{#await b} + {#if true}

            loading...

            {/if} +{:then b} +

            {b}

            +{/await} diff --git a/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/_config.js new file mode 100644 index 0000000000..29dc5e8d7f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/_config.js @@ -0,0 +1,26 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const audio = target.querySelector('audio'); + const btn = target.querySelector('button'); + + ok(audio); + + flushSync(() => { + audio.currentTime = 10; + audio.dispatchEvent(new Event('timeupdate')); + }); + assert.deepEqual(logs, ['event']); + + flushSync(() => { + btn?.click(); + }); + flushSync(() => { + audio.currentTime = 20; + audio.dispatchEvent(new Event('timeupdate')); + }); + assert.deepEqual(logs, ['event']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/main.svelte new file mode 100644 index 0000000000..40c215378e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-current-time-remove-listener/main.svelte @@ -0,0 +1,12 @@ + + + +{#if show} + +{/if} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte new file mode 100644 index 0000000000..0026309d44 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte @@ -0,0 +1,11 @@ + + +
            div, v => div = v}>123
            diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js new file mode 100644 index 0000000000..1d51c8eead --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + assert.htmlEqual(target.innerHTML, `
            123
            `); + + assert.deepEqual(logs, ['123', '123']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte new file mode 100644 index 0000000000..21646e745a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte @@ -0,0 +1,11 @@ + + + child, v => child = v} /> diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte new file mode 100644 index 0000000000..bea5849ec7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte @@ -0,0 +1,12 @@ + + + a, + (v) => { + console.log('b', v); + a = v; + }} +/> diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js new file mode 100644 index 0000000000..dd5c387405 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js @@ -0,0 +1,26 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; +import { assert_ok } from '../../../suite'; + +export default test({ + async test({ assert, target, logs }) { + const [input, checkbox] = target.querySelectorAll('input'); + + input.value = '2'; + input.dispatchEvent(new window.Event('input')); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            ` + ); + + assert.deepEqual(logs, ['b', '2', 'a', '2']); + + flushSync(() => { + checkbox.click(); + }); + assert.deepEqual(logs, ['b', '2', 'a', '2', 'check', false]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte new file mode 100644 index 0000000000..f6d908fba1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte @@ -0,0 +1,25 @@ + + + + + a, + (v) => { + console.log('a', v); + a = v; + }} +/> + +
            + check, + (v)=>{ + console.log('check', v); + check = v; + }} /> +
            \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte new file mode 100644 index 0000000000..17e895fd8c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js new file mode 100644 index 0000000000..45cd58f55c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js @@ -0,0 +1,35 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + compileOptions: { + dev: true + }, + html: ``, + ssrHtml: ``, + + async test({ assert, target, instance }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const input = target.querySelector('input'); + flushSync(() => { + btn1.click(); + }); + assert.equal(btn1.innerHTML, 'false'); + assert.equal(btn2.innerHTML, 'false'); + assert.equal(input?.checked, false); + + flushSync(() => { + btn2.click(); + }); + assert.equal(btn1.innerHTML, 'true'); + assert.equal(btn2.innerHTML, 'true'); + assert.equal(input?.checked, true); + + flushSync(() => { + input?.click(); + }); + assert.equal(btn1.innerHTML, 'false'); + assert.equal(btn2.innerHTML, 'false'); + assert.equal(input?.checked, false); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte new file mode 100644 index 0000000000..3b47eef156 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte new file mode 100644 index 0000000000..c309299748 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte @@ -0,0 +1,12 @@ + + +{@render children({ props: snippetProps })} diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js new file mode 100644 index 0000000000..e52264c793 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte new file mode 100644 index 0000000000..5900ddc846 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte @@ -0,0 +1,19 @@ + + + + + + {#snippet children({ props })} + + {/snippet} + diff --git a/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/_config.js b/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/_config.js new file mode 100644 index 0000000000..076efee994 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/_config.js @@ -0,0 +1,126 @@ +import { flushSync, tick } from 'svelte'; +import { test } from '../../test'; + +// This test counts mutations on hydration +// set_class() should not mutate class on hydration, except if mismatch +export default test({ + mode: ['server', 'hydrate'], + + server_props: { + browser: false + }, + + props: { + browser: true + }, + + html: ` +
            +
            + + + + +
            + + + +
            + `, + + ssrHtml: ` +
            +
            + + + + +
            + + + +
            + `, + + async test({ target, assert, component, instance }) { + flushSync(); + tick(); + assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']); + + component.foo = false; + flushSync(); + tick(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], + 'first mutation' + ); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + +
            + + + +
            + ` + ); + + component.foo = true; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'SPAN', 'B', 'I', 'DIV', 'SPAN', 'B', 'I'], + 'second mutation' + ); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + +
            + + + +
            + ` + ); + + component.classname = 'another'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'B', 'DIV', 'B'], + 'class mutation' + ); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + +
            + + + +
            + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/main.svelte new file mode 100644 index 0000000000..d748988e21 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-directive-mutations/main.svelte @@ -0,0 +1,52 @@ + + +
            +
            + + + + +
            + + + +
            + + diff --git a/packages/svelte/tests/runtime-runes/samples/class-directive/_config.js b/packages/svelte/tests/runtime-runes/samples/class-directive/_config.js new file mode 100644 index 0000000000..2756b40493 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-directive/_config.js @@ -0,0 +1,145 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` +
            + +
            + +
            + +
            + + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + + `, + test({ assert, target, component }) { + component.foo = true; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + ` + ); + + component.bar = false; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + ` + ); + + component.foo = false; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-directive/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-directive/main.svelte new file mode 100644 index 0000000000..966c07a78e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-directive/main.svelte @@ -0,0 +1,40 @@ + + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + +
            + + diff --git a/packages/svelte/tests/snapshot/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js similarity index 100% rename from packages/svelte/tests/snapshot/_config.js rename to packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte new file mode 100644 index 0000000000..aec1e67cc6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/clsx/_config.js b/packages/svelte/tests/runtime-runes/samples/clsx/_config.js new file mode 100644 index 0000000000..202a13b1cb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/clsx/_config.js @@ -0,0 +1,49 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` +
            +
            +
            +
            +
            + +
            child
            +
            child
            +
            child
            +
            child
            +
            child
            + + + + + `, + test({ assert, target }) { + const button = target.querySelector('button'); + + button?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            +
            +
            +
            + +
            child
            +
            child
            +
            child
            +
            child
            +
            child
            + + + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/clsx/child.svelte b/packages/svelte/tests/runtime-runes/samples/clsx/child.svelte new file mode 100644 index 0000000000..1b8be697c0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/clsx/child.svelte @@ -0,0 +1,5 @@ + + +
            child
            diff --git a/packages/svelte/tests/runtime-runes/samples/clsx/main.svelte b/packages/svelte/tests/runtime-runes/samples/clsx/main.svelte new file mode 100644 index 0000000000..ebc0697f97 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/clsx/main.svelte @@ -0,0 +1,35 @@ + + +
            +
            +
            +
            +
            + + + + + + + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte new file mode 100644 index 0000000000..8bbec90de4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/FlakyComponent.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js new file mode 100644 index 0000000000..4338969a48 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: '

            2

            ', + mode: ['client'], + test({ target, assert }) { + const btn = target.querySelector('button'); + const p = target.querySelector('p'); + + flushSync(() => { + btn?.click(); + }); + + assert.equal(p?.innerHTML, '4'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte new file mode 100644 index 0000000000..25ea8a3ffc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-tag-boundary/main.svelte @@ -0,0 +1,14 @@ + + + + + + {@const double = test * 2} + {#snippet failed()} +

            {double}

            + {/snippet} + +
            \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js new file mode 100644 index 0000000000..7f406d8f0d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js @@ -0,0 +1,24 @@ +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target }) { + const my_element = /** @type HTMLElement & { object: { test: true }; } */ ( + target.querySelector('my-element') + ); + assert.equal(my_element.getAttribute('string'), 'test'); + assert.equal(my_element.hasAttribute('object'), false); + assert.deepEqual(my_element.object, { test: true }); + + const my_link = /** @type HTMLAnchorElement & { object: { test: true }; } */ ( + target.querySelector('a') + ); + assert.equal(my_link.getAttribute('string'), 'test'); + assert.equal(my_link.hasAttribute('object'), false); + assert.deepEqual(my_link.object, { test: true }); + + const [value1, value2] = target.querySelectorAll('value-element'); + assert.equal(value1.shadowRoot?.innerHTML, 'test'); + assert.equal(value2.shadowRoot?.innerHTML, 'test'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte new file mode 100644 index 0000000000..4c98245e5b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte @@ -0,0 +1,22 @@ + + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-disconnect/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-disconnect/_config.js new file mode 100644 index 0000000000..76e60b7402 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-disconnect/_config.js @@ -0,0 +1,96 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + let [b1, b2, b3, b4, b5] = target.querySelectorAll('button'); + + b1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 1
            +
            Name: a

            +
            ` + ); + + b2?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 2
            +
            Name: b

            +
            ` + ); + + b3?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 3
            +
            Name: c

            +
            ` + ); + + b4?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 4
            +
            Name: d

            +
            ` + ); + + b5?.click(); + flushSync(); + + b5?.click(); + flushSync(); + + [b1, b2, b3, b4, b5] = target.querySelectorAll('button'); + + b1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 1
            +
            Name: a

            +
            ` + ); + + b2?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 2
            +
            Name: b

            +
            ` + ); + + b3?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 3
            +
            Name: c

            +
            ` + ); + + b4?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Current ID: 4
            +
            Name: d

            +
            ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-disconnect/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-disconnect/main.svelte new file mode 100644 index 0000000000..8cb6ed2afd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-disconnect/main.svelte @@ -0,0 +1,23 @@ + + +
            + {#if visible} +
            Current ID: {currentId}
            +
            Name: {currentItem.name}
            + {#each items as item} +
            + {/each} + {/if} +
            +
            +
            diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte new file mode 100644 index 0000000000..cd215304a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte new file mode 100644 index 0000000000..a1d9f93bec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte @@ -0,0 +1,5 @@ + + +{disabled} diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js new file mode 100644 index 0000000000..9948f91966 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn2?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, `\nfalse`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte new file mode 100644 index 0000000000..0219acdf7f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte @@ -0,0 +1,11 @@ + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js new file mode 100644 index 0000000000..8cd4af0548 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/_config.js @@ -0,0 +1,25 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn2?.click(); + flushSync(); + + btn1?.click(); + flushSync(); + + btn1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `\n3\n\n1` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte new file mode 100644 index 0000000000..48d4f5fd0b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-12/main.svelte @@ -0,0 +1,18 @@ + + + {linked.current} + {count} diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-2/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-2/_config.js index 3604000543..3ca98bb0c6 100644 --- a/packages/svelte/tests/runtime-runes/samples/derived-unowned-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-2/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -7,7 +8,7 @@ export default test({ async test({ assert, target }) { await Promise.resolve(); - await Promise.resolve(); + flushSync(); assert.htmlEqual( target.innerHTML, '
            d2: 3,4,5
            d3: 3,4,5
            d4: 3,4,5
            ' diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-5/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-5/_config.js index 20c3cc112e..5f38394d06 100644 --- a/packages/svelte/tests/runtime-runes/samples/derived-unowned-5/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-5/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -5,7 +6,7 @@ export default test({ // The test has a bunch of queueMicrotasks await Promise.resolve(); await Promise.resolve(); - await Promise.resolve(); + flushSync(); assert.htmlEqual(target.innerHTML, `
            Zeeba Neighba
            `); } diff --git a/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js new file mode 100644 index 0000000000..b364a989f4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

            state,derived state,derived.by derived state

            ` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte new file mode 100644 index 0000000000..bc8efba7e7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/deriveds-in-constructor/main.svelte @@ -0,0 +1,18 @@ + + +

            {foo.initial}

            \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js index f682972179..20092ddadf 100644 --- a/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-style-attr/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; import { flushSync } from 'svelte'; export default test({ - html: `
            Hello world
            diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js b/packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/_config.js similarity index 99% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js rename to packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/_config.js index b4864154c3..dba2e85650 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/_config.js @@ -4,7 +4,6 @@ export default test({ compileOptions: { dev: true }, - async test({ assert, warnings }) { assert.deepEqual(warnings, []); } diff --git a/packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/main.svelte new file mode 100644 index 0000000000..f927bf079a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-bind-store-no-warning/main.svelte @@ -0,0 +1,10 @@ + + + +{#each $array as item} +
            +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js b/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js new file mode 100644 index 0000000000..ee35058c59 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-9/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [btn1] = target.querySelectorAll('button'); + + btn1.click(); + flushSync(); + + await Promise.resolve(); + await Promise.resolve(); + + assert.deepEqual(logs, ['cleanup']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte new file mode 100644 index 0000000000..f5b2c8eb12 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-9/main.svelte @@ -0,0 +1,46 @@ + + + + +{#each myStore.data as _}{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js index 6a3d9eef77..e55733c148 100644 --- a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js @@ -10,6 +10,6 @@ export default test({ flushSync(() => { b1.click(); }); - assert.deepEqual(logs, ['init 0', 'cleanup 2', null, 'init 2', 'cleanup 4', null, 'init 4']); + assert.deepEqual(logs, ['init 0']); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root-5/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-root-5/_config.js new file mode 100644 index 0000000000..260c757e3d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-root-5/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [b1, b2] = target.querySelectorAll('button'); + + flushSync(() => b1.click()); + assert.deepEqual(logs, [0, 1]); + + flushSync(() => b1.click()); + assert.deepEqual(logs, [0, 1, 2]); + + flushSync(() => b2.click()); + assert.deepEqual(logs, [0, 1, 2]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-root-5/main.svelte new file mode 100644 index 0000000000..06655a5362 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-root-5/main.svelte @@ -0,0 +1,23 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js new file mode 100644 index 0000000000..749b9997c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const b1 = target.querySelector('button'); + + b1?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `Store: new

            Text: new message

            ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte new file mode 100644 index 0000000000..3c16e3c036 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte @@ -0,0 +1,12 @@ + + +Store: {$store} +

            Text: {text}

            + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-21/Child.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/Child.svelte new file mode 100644 index 0000000000..ea60542af9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/Child.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js new file mode 100644 index 0000000000..e301f83e60 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: '
            0
            ', + mode: ['client'], + test({ assert, target }) { + let btn = target.querySelector('button'); + let div = target.querySelector('div'); + + flushSync(() => { + btn?.click(); + }); + + assert.equal(div?.innerHTML, `1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte new file mode 100644 index 0000000000..ed3140b1ef --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-21/main.svelte @@ -0,0 +1,14 @@ + + + + + + + {#snippet failed()} +
            {count}
            + {/snippet} +
            diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js new file mode 100644 index 0000000000..d53812d4c3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/_config.js @@ -0,0 +1,48 @@ +import { assertType } from 'vitest'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + + compileOptions: { + dev: true + }, + + test({ assert, target, warnings, logs }) { + /** @type {any} */ + let error = null; + + const handler = (/** @type {any} */ e) => { + error = e.error; + e.stopImmediatePropagation(); + }; + + window.addEventListener('error', handler, true); + + const [b1, b2, b3] = target.querySelectorAll('button'); + + b1.click(); + assert.deepEqual(logs, []); + assert.equal(error, null); + + error = null; + logs.length = 0; + + b2.click(); + assert.deepEqual(logs, ['clicked']); + assert.equal(error, null); + + error = null; + logs.length = 0; + + b3.click(); + assert.deepEqual(logs, []); + assert.deepEqual(warnings, [ + '`click` handler at main.svelte:10:17 should be a function. Did you mean to add a leading `() =>`?' + ]); + assert.isNotNull(error); + assert.match(error.message, /is not a function/); + + window.removeEventListener('error', handler, true); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte new file mode 100644 index 0000000000..f6e344ece8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-values/main.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js b/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js deleted file mode 100644 index d3b9c8e4eb..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js +++ /dev/null @@ -1,10 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true // to ensure we we catch the error - }, - error: - 'bind_invalid_export\n' + - 'Component counter/index.svelte has an export named `increment` that a consumer component is trying to access using `bind:increment`, which is disallowed. Instead, use `bind:this` (e.g. ``) and then access the property on the bound component instance (e.g. `component.increment`)' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte b/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte deleted file mode 100644 index 14e0de961b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/counter/index.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -{count} diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte b/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte deleted file mode 100644 index 4ad1684701..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/_config.js new file mode 100644 index 0000000000..26e90e431b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/_config.js @@ -0,0 +1,263 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target }) { + /** + * @param {NodeListOf} inputs + * @param {string} field + * @param {any | any[]} value + */ + function check_inputs(inputs, field, value) { + for (let i = 0; i < inputs.length; i++) { + assert.equal(inputs[i][field], Array.isArray(value) ? value[i] : value, `field ${i}`); + } + } + + /** + * @param {any} input + * @param {string} field + * @param {any} value + */ + function set_input(input, field, value) { + input[field] = value; + input.dispatchEvent( + new Event(typeof value === 'boolean' ? 'change' : 'input', { bubbles: true }) + ); + } + + /** + * @param {HTMLOptionElement} option + */ + function select_option(option) { + option.selected = true; + option.dispatchEvent(new Event('change', { bubbles: true })); + } + + const after_reset = []; + + const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]')); + const [test1, test2, test3, test4, test5, test6, test7, test14] = + target.querySelectorAll('div'); + const [test8, test9, test10, test11] = target.querySelectorAll('select'); + const [ + test1_span, + test2_span, + test3_span, + test4_span, + test5_span, + test6_span, + test7_span, + test8_span, + test9_span, + test10_span, + test11_span + ] = target.querySelectorAll('span'); + + { + /** @type {NodeListOf} */ + const inputs = test1.querySelectorAll('input, textarea'); + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test1_span.innerHTML, 'x x x x'); + + for (const input of inputs) { + set_input(input, 'value', 'foo'); + } + flushSync(); + check_inputs(inputs, 'value', 'foo'); + assert.htmlEqual(test1_span.innerHTML, 'foo foo foo foo'); + + after_reset.push(() => { + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test1_span.innerHTML, 'x x x x'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test2.querySelectorAll('input, textarea'); + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test2_span.innerHTML, 'x x x x'); + + for (const input of inputs) { + set_input(input, 'value', 'foo'); + } + flushSync(); + check_inputs(inputs, 'value', 'foo'); + assert.htmlEqual(test2_span.innerHTML, 'foo foo foo foo'); + + after_reset.push(() => { + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test2_span.innerHTML, 'x x x x'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test3.querySelectorAll('input, textarea'); + check_inputs(inputs, 'value', 'y'); + assert.htmlEqual(test3_span.innerHTML, 'y y y y'); + + for (const input of inputs) { + set_input(input, 'value', 'foo'); + } + flushSync(); + check_inputs(inputs, 'value', 'foo'); + assert.htmlEqual(test3_span.innerHTML, 'foo foo foo foo'); + + after_reset.push(() => { + check_inputs(inputs, 'value', 'x'); + assert.htmlEqual(test3_span.innerHTML, 'x x x x'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test4.querySelectorAll('input'); + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test4_span.innerHTML, 'true true'); + + for (const input of inputs) { + set_input(input, 'checked', false); + } + flushSync(); + check_inputs(inputs, 'checked', false); + assert.htmlEqual(test4_span.innerHTML, 'false false'); + + after_reset.push(() => { + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test4_span.innerHTML, 'true true'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test5.querySelectorAll('input'); + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test5_span.innerHTML, 'true true'); + + for (const input of inputs) { + set_input(input, 'checked', false); + } + flushSync(); + check_inputs(inputs, 'checked', false); + assert.htmlEqual(test5_span.innerHTML, 'false false'); + + after_reset.push(() => { + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test5_span.innerHTML, 'true true'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test6.querySelectorAll('input'); + check_inputs(inputs, 'checked', false); + assert.htmlEqual(test6_span.innerHTML, 'false false'); + + after_reset.push(() => { + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test6_span.innerHTML, 'true true'); + }); + } + { + /** @type {NodeListOf} */ + const inputs = test7.querySelectorAll('input'); + check_inputs(inputs, 'checked', true); + assert.htmlEqual(test7_span.innerHTML, 'true'); + + after_reset.push(() => { + check_inputs(inputs, 'checked', false); + assert.htmlEqual(test7_span.innerHTML, 'false'); + }); + } + + { + /** @type {NodeListOf} */ + const options = test8.querySelectorAll('option'); + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test8_span.innerHTML, 'b'); + + select_option(options[2]); + flushSync(); + check_inputs(options, 'selected', [false, false, true]); + assert.htmlEqual(test8_span.innerHTML, 'c'); + + after_reset.push(() => { + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test8_span.innerHTML, 'b'); + }); + } + + { + /** @type {NodeListOf} */ + const options = test9.querySelectorAll('option'); + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test9_span.innerHTML, 'b'); + + select_option(options[2]); + flushSync(); + check_inputs(options, 'selected', [false, false, true]); + assert.htmlEqual(test9_span.innerHTML, 'c'); + + after_reset.push(() => { + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test9_span.innerHTML, 'b'); + }); + } + + { + /** @type {NodeListOf} */ + const options = test10.querySelectorAll('option'); + check_inputs(options, 'selected', [false, false, true]); + assert.htmlEqual(test10_span.innerHTML, 'c'); + + select_option(options[0]); + flushSync(); + check_inputs(options, 'selected', [true, false, false]); + assert.htmlEqual(test10_span.innerHTML, 'a'); + + after_reset.push(() => { + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test10_span.innerHTML, 'b'); + }); + } + + { + /** @type {NodeListOf} */ + const options = test11.querySelectorAll('option'); + check_inputs(options, 'selected', [false, false, true]); + assert.htmlEqual(test11_span.innerHTML, 'c'); + + select_option(options[0]); + flushSync(); + check_inputs(options, 'selected', [true, false, false]); + assert.htmlEqual(test11_span.innerHTML, 'a'); + + after_reset.push(() => { + check_inputs(options, 'selected', [false, true, false]); + assert.htmlEqual(test11_span.innerHTML, 'b'); + }); + } + + { + /** @type {NodeListOf} */ + const inputs = test14.querySelectorAll('input, textarea'); + assert.equal(inputs[0].value, 'x'); + assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, true); + // this is still missing...i have no idea how to fix this lol + // assert.equal(inputs[2].value, 'x'); + + after_reset.push(() => { + assert.equal(inputs[0].value, 'y'); + assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, false); + assert.equal(inputs[2].value, 'y'); + }); + } + + reset.click(); + await Promise.resolve(); + flushSync(); + after_reset.forEach((fn) => fn()); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/main.svelte new file mode 100644 index 0000000000..3fc7ef11a5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-spread/main.svelte @@ -0,0 +1,199 @@ + + +
            +

            Input/Textarea value

            + +
            + + + + + + + + +
            + + +
            + + + + + + + + +
            + + +
            + + + + + + + + +
            + +

            Input checked

            + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + + + +
            + + +
            + + +
            + + + +

            Select (single)

            + + + + + + + + + + + + +

            Select (multiple)

            + + + + + + + + +

            Static values

            +
            + + + +
            + + +
            + +

            + Bound values: + {value1} {value3} {value6} {value8} + {value9} {value12} {value14} {value16} + {value17} {value20} {value22} {value24} + {checked2} {checked4} + {checked6} {checked8} + {checked10} {checked12} + {checked14} + {selected1} + {selected2} + {selected3} + {selected4} + {selected5} + {selected6} +

            diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js index 5ef72aaa8e..35ab6e8ece 100644 --- a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js @@ -68,7 +68,6 @@ export default test({ assert.htmlEqual(test1_span.innerHTML, 'foo foo foo foo'); after_reset.push(() => { - console.log('-------------'); check_inputs(inputs, 'value', 'x'); assert.htmlEqual(test1_span.innerHTML, 'x x x x'); }); @@ -88,7 +87,6 @@ export default test({ assert.htmlEqual(test2_span.innerHTML, 'foo foo foo foo'); after_reset.push(() => { - console.log('-------------'); check_inputs(inputs, 'value', 'x'); assert.htmlEqual(test2_span.innerHTML, 'x x x x'); }); diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js new file mode 100644 index 0000000000..4fdf3632d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + html: ` +
            +
            +` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte new file mode 100644 index 0000000000..1e8115ff62 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte @@ -0,0 +1,6 @@ + + +
            +
            diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/_config.js new file mode 100644 index 0000000000..7bb71adafb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/_config.js @@ -0,0 +1,19 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, target, logs }) { + assert.deepEqual(logs, []); + + const [b1, b2] = target.querySelectorAll('button'); + b1.click(); + b2.click(); + await Promise.resolve(); + + assert.ok(logs[0].stack.startsWith('Error:') && logs[0].stack.includes('HTMLButtonElement.')); + assert.deepEqual(logs[1], 1); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/main.svelte new file mode 100644 index 0000000000..d42742c5b5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-console-trace/main.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/_config.js new file mode 100644 index 0000000000..f54f78f5c1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/_config.js @@ -0,0 +1,56 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +/** + * @param {any[]} logs + */ +function normalise_trace_logs(logs) { + let normalised = []; + for (let i = 0; i < logs.length; i++) { + const log = logs[i]; + + if (typeof log === 'string' && log.includes('%c')) { + const split = log.split('%c'); + normalised.push({ + log: (split[0].length !== 0 ? split[0] : split[1]).trim(), + highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold' + }); + i++; + } else if (log instanceof Error) { + continue; + } else { + normalised.push({ log }); + } + } + return normalised; +} + +export default test({ + compileOptions: { + dev: true + }, + + test({ assert, target, logs }) { + // initial log, everything is highlighted + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'iife', highlighted: false }, + { log: '$state', highlighted: true }, + { log: 0 }, + { log: 'effect', highlighted: false } + ]); + + logs.length = 0; + + const button = target.querySelector('button'); + button?.click(); + flushSync(); + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'iife', highlighted: false }, + { log: '$state', highlighted: true }, + { log: 1 }, + { log: 'effect', highlighted: false } + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/main.svelte new file mode 100644 index 0000000000..f60fb0438c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-nested/main.svelte @@ -0,0 +1,13 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/_config.js new file mode 100644 index 0000000000..e779a835c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/main.svelte new file mode 100644 index 0000000000..30eb95f124 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-null/main.svelte @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/_config.js new file mode 100644 index 0000000000..c9a66289a1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/_config.js @@ -0,0 +1,72 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +/** + * @param {any[]} logs + */ +function normalise_trace_logs(logs) { + let normalised = []; + for (let i = 0; i < logs.length; i++) { + const log = logs[i]; + + if (typeof log === 'string' && log.includes('%c')) { + const split = log.split('%c'); + normalised.push({ + log: (split[0].length !== 0 ? split[0] : split[1]).trim(), + highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold' + }); + i++; + } else if (log instanceof Error) { + continue; + } else { + normalised.push({ log }); + } + } + return normalised; +} + +export default test({ + compileOptions: { + dev: true + }, + + test({ assert, target, logs }) { + // initial log, everything is highlighted + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$state', highlighted: true }, + { log: false } + ]); + + logs.length = 0; + + const button = target.querySelector('button'); + button?.click(); + flushSync(); + + const input = target.querySelector('input'); + input?.click(); + flushSync(); + + // checked changed, effect reassign state, values should be correct and be correctly highlighted + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$state', highlighted: true }, + { log: true }, + { log: '$state', highlighted: true }, + { log: 1 }, + { log: 'effect', highlighted: false }, + { log: '$state', highlighted: false }, + { log: true }, + { log: '$state', highlighted: true }, + { log: 2 }, + { log: 'effect', highlighted: false }, + { log: '$state', highlighted: false }, + { log: true }, + { log: '$state', highlighted: true }, + { log: 3 } + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/main.svelte new file mode 100644 index 0000000000..5028c0f251 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-reassignment/main.svelte @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js index 7bb71adafb..efa5985e4e 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js @@ -1,19 +1,97 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; +/** + * @param {any[]} logs + */ +function normalise_trace_logs(logs) { + let normalised = []; + for (let i = 0; i < logs.length; i++) { + const log = logs[i]; + + if (typeof log === 'string' && log.includes('%c')) { + const split = log.split('%c'); + normalised.push({ + log: (split[0].length !== 0 ? split[0] : split[1]).trim(), + highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold' + }); + i++; + } else if (log instanceof Error) { + continue; + } else { + normalised.push({ log }); + } + } + return normalised; +} + export default test({ compileOptions: { dev: true }, - async test({ assert, target, logs }) { - assert.deepEqual(logs, []); + test({ assert, target, logs }) { + // initial log, everything is highlighted + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 0 }, + { log: '$state', highlighted: true }, + { log: 0 }, + { log: '$state', highlighted: true }, + { log: false } + ]); + + logs.length = 0; + + const button = target.querySelector('button'); + button?.click(); + flushSync(); + + // count changed, derived and state are highlighted, last state is not + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 2 }, + { log: '$state', highlighted: true }, + { log: 1 }, + { log: '$state', highlighted: false }, + { log: false } + ]); + + logs.length = 0; + + const input = target.querySelector('input'); + input?.click(); + flushSync(); + + // checked changed, last state is highlighted, first two are not + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: false }, + { log: 2 }, + { log: '$state', highlighted: false }, + { log: 1 }, + { log: '$state', highlighted: true }, + { log: true } + ]); + + logs.length = 0; + + button?.click(); + flushSync(); - const [b1, b2] = target.querySelectorAll('button'); - b1.click(); - b2.click(); - await Promise.resolve(); + // count change and derived it's >=4, checked is not in the dependencies anymore - assert.ok(logs[0].stack.startsWith('Error:') && logs[0].stack.includes('HTMLButtonElement.')); - assert.deepEqual(logs[1], 1); + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 4 }, + { log: '$state', highlighted: true }, + { log: 2 } + ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte index d42742c5b5..99f246aa73 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte @@ -1,11 +1,16 @@ - - + + diff --git a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-2/_config.js b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-2/_config.js index 1066d9a2df..cb8e648645 100644 --- a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-2/_config.js @@ -10,7 +10,6 @@ export default test({ assert.deepEqual(logs, [ 'parent: $effect.pre 0', 'parent: $effect.pre (2) 0', - 'parent: render 0', '1: $effect.pre 0', '1: $effect.pre (2) 0', '1: render 0', @@ -20,6 +19,7 @@ export default test({ '3: $effect.pre 0', '3: $effect.pre (2) 0', '3: render 0', + 'parent: render 0', '1: $effect 0', '2: $effect 0', '3: $effect 0', @@ -33,7 +33,6 @@ export default test({ assert.deepEqual(logs, [ 'parent: $effect.pre 1', 'parent: $effect.pre (2) 1', - 'parent: render 1', '1: $effect.pre 1', '1: $effect.pre (2) 1', '1: render 1', @@ -43,6 +42,7 @@ export default test({ '3: $effect.pre 1', '3: $effect.pre (2) 1', '3: render 1', + 'parent: render 1', '1: $effect 1', '2: $effect 1', '3: $effect 1', diff --git a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-3/_config.js b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-3/_config.js index 55847c35a2..6c063bcb3e 100644 --- a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-3/_config.js @@ -8,13 +8,13 @@ export default test({ async test({ assert, component, logs }) { assert.deepEqual(logs, [ - 'parent: render 0', '1: $effect.pre 0', '1: render 0', '2: $effect.pre 0', '2: render 0', '3: $effect.pre 0', '3: render 0', + 'parent: render 0', '1: $effect 0', '2: $effect 0', '3: $effect 0', @@ -26,13 +26,13 @@ export default test({ flushSync(() => (component.n += 1)); assert.deepEqual(logs, [ - 'parent: render 1', '1: $effect.pre 1', '1: render 1', '2: $effect.pre 1', '2: render 1', '3: $effect.pre 1', '3: render 1', + 'parent: render 1', '1: $effect 1', '2: $effect 1', '3: $effect 1', diff --git a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-4/_config.js b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-4/_config.js index 0cd7e15a37..29b0b67a52 100644 --- a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-4/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children-4/_config.js @@ -10,7 +10,6 @@ export default test({ assert.deepEqual(logs, [ 'parent: $effect.pre 0', 'parent: nested $effect.pre 0', - 'parent: render 0', '1: $effect.pre 0', '1: nested $effect.pre 0', '1: render 0', @@ -20,6 +19,7 @@ export default test({ '3: $effect.pre 0', '3: nested $effect.pre 0', '3: render 0', + 'parent: render 0', '1: $effect 0', '2: $effect 0', '3: $effect 0', @@ -33,7 +33,6 @@ export default test({ assert.deepEqual(logs, [ 'parent: $effect.pre 1', 'parent: nested $effect.pre 1', - 'parent: render 1', '1: $effect.pre 1', '1: nested $effect.pre 1', '1: render 1', @@ -43,6 +42,7 @@ export default test({ '3: $effect.pre 1', '3: nested $effect.pre 1', '3: render 1', + 'parent: render 1', '1: $effect 1', '2: $effect 1', '3: $effect 1', diff --git a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children/_config.js b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children/_config.js index 19b8fb3938..3138ec7231 100644 --- a/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/lifecycle-render-order-for-children/_config.js @@ -9,13 +9,13 @@ export default test({ async test({ assert, component, logs }) { assert.deepEqual(logs, [ 'parent: $effect.pre 0', - 'parent: render 0', '1: $effect.pre 0', '1: render 0', '2: $effect.pre 0', '2: render 0', '3: $effect.pre 0', '3: render 0', + 'parent: render 0', '1: $effect 0', '2: $effect 0', '3: $effect 0', @@ -28,13 +28,13 @@ export default test({ assert.deepEqual(logs, [ 'parent: $effect.pre 1', - 'parent: render 1', '1: $effect.pre 1', '1: render 1', '2: $effect.pre 1', '2: render 1', '3: $effect.pre 1', '3: render 1', + 'parent: render 1', '1: $effect 1', '2: $effect 1', '3: $effect 1', diff --git a/packages/svelte/tests/runtime-runes/samples/media-query/_config.js b/packages/svelte/tests/runtime-runes/samples/media-query/_config.js new file mode 100644 index 0000000000..f7a4ca05f5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/media-query/_config.js @@ -0,0 +1,9 @@ +import { expect } from 'vitest'; +import { test } from '../../test'; + +export default test({ + async test({ window }) { + expect(window.matchMedia).toHaveBeenCalledWith('(max-width: 599px), (min-width: 900px)'); + expect(window.matchMedia).toHaveBeenCalledWith('(min-width: 900px)'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/media-query/main.svelte b/packages/svelte/tests/runtime-runes/samples/media-query/main.svelte new file mode 100644 index 0000000000..446a9213dd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/media-query/main.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/_config.js b/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/_config.js new file mode 100644 index 0000000000..cc4dfb37f0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const btn = target.querySelector('button'); + ok(btn); + flushSync(() => { + btn.click(); + }); + assert.deepEqual(logs, [true]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/main.svelte b/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/main.svelte new file mode 100644 index 0000000000..646334c1ec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/muted-without-bind-works/main.svelte @@ -0,0 +1,13 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js b/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js index a8c16b7008..eb631bc9f4 100644 --- a/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/nested-effect-conflict/_config.js @@ -10,14 +10,6 @@ export default test({ }); await Promise.resolve(); - assert.deepEqual(logs, [ - 'top level', - 'inner', - 0, - 'destroy inner', - undefined, - 'destroy outer', - undefined - ]); + assert.deepEqual(logs, ['top level', 'inner', 0, 'destroy inner', 0, 'destroy outer', 0]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js index 62c6961242..8452661026 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js @@ -10,7 +10,7 @@ export default test({ test({ assert, target, warnings }) { const warning = - 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead'; + 'Mutating unbound props (`object`, at Counter.svelte:5:23) is strongly discouraged. Consider using `bind:object={...}` in main.svelte (or using a callback) instead'; const [btn1, btn2] = target.querySelectorAll('button'); btn1.click(); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte deleted file mode 100644 index 13de753647..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/child.svelte +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte deleted file mode 100644 index 8a6922e9e2..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-global-2/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js similarity index 74% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js index c07b9ce129..96b18d1854 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/_config.js @@ -8,7 +8,7 @@ let warn; let warnings = []; export default test({ - html: ``, + html: ``, compileOptions: { dev: true @@ -34,8 +34,8 @@ export default test({ btn?.click(); }); - assert.htmlEqual(target.innerHTML, ``); + assert.htmlEqual(target.innerHTML, ``); - assert.deepEqual(warnings, []); + assert.deepEqual(warnings, [], 'expected getContext to have widened ownership'); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte new file mode 100644 index 0000000000..2dd7cab141 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/main.svelte @@ -0,0 +1,9 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-1/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js index c07b9ce129..66f1726a2a 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/_config.js @@ -1,41 +1,24 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - export default test({ - html: ``, - compileOptions: { dev: true }, - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, + test({ assert, target, warnings }) { + const [btn1, btn2] = target.querySelectorAll('button'); - after_test: () => { - console.warn = warn; - warnings = []; - }, + flushSync(() => { + btn1.click(); + }); - test({ assert, target }) { - const btn = target.querySelector('button'); + assert.deepEqual(warnings.length, 0); flushSync(() => { - btn?.click(); + btn2.click(); }); - assert.htmlEqual(target.innerHTML, ``); - - assert.deepEqual(warnings, []); + assert.deepEqual(warnings.length, 1); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte index ad450a937e..0be7e434e4 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/main.svelte @@ -1,9 +1,8 @@ - - + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js index 3e7a68cf97..2906b9bce5 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/state.svelte.js @@ -1 +1,14 @@ -export let global = $state({}); +export function create_my_state() { + const my_state = $state({ + a: 0 + }); + + function inc() { + my_state.a++; + } + + return { + my_state, + inc + }; +} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/sub.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-2/sub.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/Child.svelte rename to packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/Child.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js index 96b18d1854..ab7327ab8b 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/_config.js @@ -1,41 +1,24 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - export default test({ - html: ``, - compileOptions: { dev: true }, - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, + async test({ assert, target, warnings }) { + const [btn1, btn2] = target.querySelectorAll('button'); - after_test: () => { - console.warn = warn; - warnings = []; - }, + flushSync(() => { + btn1.click(); + }); - test({ assert, target }) { - const btn = target.querySelector('button'); + assert.deepEqual(warnings.length, 0); flushSync(() => { - btn?.click(); + btn2.click(); }); - assert.htmlEqual(target.innerHTML, ``); - - assert.deepEqual(warnings, [], 'expected getContext to have widened ownership'); + assert.deepEqual(warnings.length, 1); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte index 2dd7cab141..8e8343790b 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-3/main.svelte @@ -1,9 +1,13 @@ - + + diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js deleted file mode 100644 index aeb3740dfe..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/_config.js +++ /dev/null @@ -1,37 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - -export default test({ - compileOptions: { - dev: true - }, - - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, - - after_test: () => { - console.warn = warn; - warnings = []; - }, - - test({ assert, target }) { - const btn = target.querySelector('button'); - - flushSync(() => { - btn?.click(); - }); - - assert.deepEqual(warnings, []); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte deleted file mode 100644 index 2d40c13949..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/main.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js deleted file mode 100644 index 4079059171..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/state.svelte.js +++ /dev/null @@ -1,13 +0,0 @@ -class Global { - state = $state({}); - - add_a(a) { - this.state.a = a; - } - - increment_a_b() { - this.state.a.b++; - } -} - -export const global = new Global(); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte deleted file mode 100644 index 044904aa18..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-4/sub.svelte +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js deleted file mode 100644 index 66f1726a2a..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/_config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - test({ assert, target, warnings }) { - const [btn1, btn2] = target.querySelectorAll('button'); - - flushSync(() => { - btn1.click(); - }); - - assert.deepEqual(warnings.length, 0); - - flushSync(() => { - btn2.click(); - }); - - assert.deepEqual(warnings.length, 1); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte deleted file mode 100644 index 0be7e434e4..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/main.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js deleted file mode 100644 index 2906b9bce5..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-5/state.svelte.js +++ /dev/null @@ -1,14 +0,0 @@ -export function create_my_state() { - const my_state = $state({ - a: 0 - }); - - function inc() { - my_state.a++; - } - - return { - my_state, - inc - }; -} diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte deleted file mode 100644 index aa31fd7606..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/Child.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js deleted file mode 100644 index cc9ea715f0..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/_config.js +++ /dev/null @@ -1,37 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -/** @type {typeof console.warn} */ -let warn; - -/** @type {any[]} */ -let warnings = []; - -export default test({ - compileOptions: { - dev: true - }, - - before_test: () => { - warn = console.warn; - - console.warn = (...args) => { - warnings.push(...args); - }; - }, - - after_test: () => { - console.warn = warn; - warnings = []; - }, - - async test({ assert, target }) { - const btn = target.querySelector('button'); - - flushSync(() => { - btn?.click(); - }); - - assert.deepEqual(warnings.length, 0); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte deleted file mode 100644 index 92d7dbd2db..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-6/main.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js deleted file mode 100644 index ab7327ab8b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/_config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - async test({ assert, target, warnings }) { - const [btn1, btn2] = target.querySelectorAll('button'); - - flushSync(() => { - btn1.click(); - }); - - assert.deepEqual(warnings.length, 0); - - flushSync(() => { - btn2.click(); - }); - - assert.deepEqual(warnings.length, 1); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte deleted file mode 100644 index 8e8343790b..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner-7/main.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte deleted file mode 100644 index ffe6ef75c4..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/Counter.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte deleted file mode 100644 index 5f1c7461f6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/main.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js deleted file mode 100644 index 6881c2faf6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-inherited-owner/state.svelte.js +++ /dev/null @@ -1,3 +0,0 @@ -export let global = $state({ - object: { count: -1 } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js index 87474a05cc..39fa80c55a 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js @@ -8,6 +8,6 @@ export default test({ }, warnings: [ - 'Intermediate.svelte passed a value to Counter.svelte with `bind:`, but the value is owned by main.svelte. Consider creating a binding between main.svelte and Intermediate.svelte' + 'Intermediate.svelte passed property `object` to Counter.svelte with `bind:`, but its parent component main.svelte did not declare `object` as a binding. Consider creating a binding between main.svelte and Intermediate.svelte (e.g. `bind:object={...}` instead of `object={...}`)' ] }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js index 66e5184380..7b8cc676d5 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js @@ -33,7 +33,7 @@ export default test({ assert.htmlEqual(target.innerHTML, ``); assert.deepEqual(warnings, [ - 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' + 'Mutating unbound props (`notshared`, at Counter.svelte:10:23) is strongly discouraged. Consider using `bind:notshared={...}` in main.svelte (or using a callback) instead' ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js index e766a946d0..bd2ecc28b6 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-7/_config.js @@ -1,7 +1,6 @@ import { flushSync } from 'svelte'; import { ok, test } from '../../test'; -// Tests that proxies widen ownership correctly even if not directly connected to each other export default test({ compileOptions: { dev: true diff --git a/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/Component.svelte b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/Component.svelte new file mode 100644 index 0000000000..b5da702fa7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/Component.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js new file mode 100644 index 0000000000..0d24e265d3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, logs }) { + assert.deepEqual(logs, [1]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte new file mode 100644 index 0000000000..92746760a4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/onmount-prop-access/main.svelte @@ -0,0 +1,14 @@ + + +{#key key} + +{/key} diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/Child.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/Child.svelte new file mode 100644 index 0000000000..ef91b0756d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/Child.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js new file mode 100644 index 0000000000..4c77aea206 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/_config.js @@ -0,0 +1,20 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + test({ target, warnings, assert }) { + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.deepEqual(warnings, []); + + flushSync(() => { + btn?.click(); + }); + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte new file mode 100644 index 0000000000..4a3ce82726 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-function-bindings/main.svelte @@ -0,0 +1,10 @@ + + + len % 2 === 0 ? arr : arr2, (v) => {}} /> \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Child.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Child.svelte new file mode 100644 index 0000000000..78b82caed9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Child.svelte @@ -0,0 +1,5 @@ + + +{test} diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Parent.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Parent.svelte new file mode 100644 index 0000000000..7bfb17aa64 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/Parent.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/_config.js b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/_config.js new file mode 100644 index 0000000000..e93067eb9d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + dev: true + }, + async test({ warnings, assert }) { + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/main.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/main.svelte new file mode 100644 index 0000000000..282afb1771 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-binding-bindable-fallback/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/Parent.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/Parent.svelte new file mode 100644 index 0000000000..7d6b248da7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/Parent.svelte @@ -0,0 +1,8 @@ + + + + + +{test} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/_config.js b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/_config.js new file mode 100644 index 0000000000..9b4e3479ea --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + compileOptions: { + dev: true + }, + async test({ warnings, assert, target }) { + const [btn, btn2] = target.querySelectorAll('button'); + flushSync(() => { + btn2.click(); + }); + assert.deepEqual(warnings, []); + flushSync(() => { + btn.click(); + }); + flushSync(() => { + btn2.click(); + }); + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/main.svelte b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/main.svelte new file mode 100644 index 0000000000..282afb1771 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/ownership-invalid-mutation-bindable-fallback/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js b/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js new file mode 100644 index 0000000000..cb9d31a69f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/pre-no-content/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
            `
            +});
            diff --git a/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte b/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte
            new file mode 100644
            index 0000000000..a4357066a5
            --- /dev/null
            +++ b/packages/svelte/tests/runtime-runes/samples/pre-no-content/main.svelte
            @@ -0,0 +1 @@
            +
            
            \ No newline at end of file
            diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte
            new file mode 100644
            index 0000000000..ad8bbd6f01
            --- /dev/null
            +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte
            @@ -0,0 +1,5 @@
            +
            +
            +

            {id}

            diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js new file mode 100644 index 0000000000..6d4306c413 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -0,0 +1,60 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + id_prefix: 'myPrefix', + test({ assert, target, variant }) { + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

            c1

            +

            c2

            +

            c3

            +

            c4

            + ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

            myPrefix-s1

            +

            myPrefix-s2

            +

            myPrefix-s3

            +

            myPrefix-s4

            + ` + ); + } + + let button = target.querySelector('button'); + flushSync(() => button?.click()); + + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

            c1

            +

            c2

            +

            c3

            +

            c4

            +

            c5

            + ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

            myPrefix-s1

            +

            myPrefix-s2

            +

            myPrefix-s3

            +

            myPrefix-s4

            +

            c1

            + ` + ); + } + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte new file mode 100644 index 0000000000..646bb2ebde --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte @@ -0,0 +1,19 @@ + + + + +

            {id}

            + + + + + +{#if show} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte new file mode 100644 index 0000000000..ad8bbd6f01 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/Child.svelte @@ -0,0 +1,5 @@ + + +

            {id}

            diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id/_config.js new file mode 100644 index 0000000000..416ef6cfbe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/_config.js @@ -0,0 +1,59 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, variant }) { + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

            c1

            +

            c2

            +

            c3

            +

            c4

            + ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

            s1

            +

            s2

            +

            s3

            +

            s4

            + ` + ); + } + + let button = target.querySelector('button'); + flushSync(() => button?.click()); + + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

            c1

            +

            c2

            +

            c3

            +

            c4

            +

            c5

            + ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

            s1

            +

            s2

            +

            s3

            +

            s4

            +

            c1

            + ` + ); + } + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte new file mode 100644 index 0000000000..646bb2ebde --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id/main.svelte @@ -0,0 +1,19 @@ + + + + +

            {id}

            + + + + + +{#if show} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte deleted file mode 100644 index f22fd6e976..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/Counter.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -{rest.count} diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js deleted file mode 100644 index fa0994c370..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - html: '0', - - error: - 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte deleted file mode 100644 index 80242b75c6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte deleted file mode 100644 index 4bc2db3968..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/Counter.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -{count} diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js deleted file mode 100644 index fa0994c370..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js +++ /dev/null @@ -1,13 +0,0 @@ -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - html: '0', - - error: - 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' -}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte deleted file mode 100644 index 80242b75c6..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/Test.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/Test.svelte new file mode 100644 index 0000000000..c2b06202ae --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/Test.svelte @@ -0,0 +1,11 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js new file mode 100644 index 0000000000..4462f492fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js @@ -0,0 +1,36 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + html: `
            x
            `, + + test({ assert, target, warnings }) { + const btn = target.querySelector('button'); + ok(btn); + + flushSync(() => btn.click()); + assert.htmlEqual( + target.innerHTML, + `
            x
            ` + ); + + flushSync(() => btn.click()); + assert.htmlEqual( + target.innerHTML, + `
            x
            ` + ); + + const input = target.querySelector('input'); + ok(input); + input.checked = true; + flushSync(() => input.dispatchEvent(new Event('change', { bubbles: true }))); + + assert.deepEqual(warnings, [ + 'Assignment to `items` property (main.svelte:9:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.' + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte new file mode 100644 index 0000000000..a79fe873b7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte @@ -0,0 +1,25 @@ + + + + + +
            x
            + + + + + entries[2], (v) => (entries[2] = v)}> + + +{#snippet funBind(context)} + {}, (e) => (context.element = e)} /> +{/snippet} +{@render funBind({ set element(e) { elementFunBind = e } })} diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment/_config.js similarity index 86% rename from packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment/_config.js rename to packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment/_config.js index 99d957e980..1603241227 100644 --- a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment/_config.js @@ -5,7 +5,7 @@ export default test({ html: ``, test({ assert, target }) { - const [btn1, btn2] = target.querySelectorAll('button'); + const [btn1] = target.querySelectorAll('button'); flushSync(() => btn1.click()); assert.htmlEqual(target.innerHTML, ``); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment/main.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment/main.svelte rename to packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/_config.js deleted file mode 100644 index a6d79c05ed..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/_config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { flushSync } from 'svelte'; -import { test } from '../../test'; - -export default test({ - compileOptions: { - dev: true - }, - - html: ``, - - test({ assert, target, warnings }) { - const btn = target.querySelector('button'); - - flushSync(() => btn?.click()); - assert.htmlEqual(target.innerHTML, ``); - - flushSync(() => btn?.click()); - assert.htmlEqual(target.innerHTML, ``); - - assert.deepEqual(warnings, [ - 'Assignment to `items` property (main.svelte:5:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.' - ]); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/main.svelte deleted file mode 100644 index f151336046..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/proxy-nullish-coalescing-assignment-warning/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/svelte/tests/runtime-runes/samples/random/_config.js b/packages/svelte/tests/runtime-runes/samples/random/_config.js new file mode 100644 index 0000000000..368dd20c6c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/random/_config.js @@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const [p1, p2] = target.querySelectorAll('p'); + assert.notEqual(p1.textContent, p2.textContent); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/random/main.svelte b/packages/svelte/tests/runtime-runes/samples/random/main.svelte new file mode 100644 index 0000000000..e1ec0b5649 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/random/main.svelte @@ -0,0 +1,6 @@ + + +

            {(Math.random() * m).toFixed(10)}

            +

            {(Math.random() * m).toFixed(10)}

            diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte new file mode 100644 index 0000000000..afb62ced2f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/Component.svelte @@ -0,0 +1,7 @@ + + +

            {label}

            diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js new file mode 100644 index 0000000000..650e48e84c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/_config.js @@ -0,0 +1,19 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + html: `

            0

            `, + + async test({ assert, target }) { + const p = target.querySelector('p'); + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '1'); + flushSync(() => { + btn?.click(); + }); + assert.equal(p?.innerHTML, '2'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte new file mode 100644 index 0000000000..773aabeea3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/read-version-previous-reaction/main.svelte @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/runes-from-func/_config.js b/packages/svelte/tests/runtime-runes/samples/runes-from-func/_config.js index 7d3dd9993f..5ed7579c62 100644 --- a/packages/svelte/tests/runtime-runes/samples/runes-from-func/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/runes-from-func/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -5,7 +6,7 @@ export default test({ async test({ assert, target }) { await Promise.resolve(); - await Promise.resolve(); + flushSync(); assert.htmlEqual(target.innerHTML, `1`); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/select-value-with-call/_config.js b/packages/svelte/tests/runtime-runes/samples/select-value-with-call/_config.js new file mode 100644 index 0000000000..0597c2fda8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-value-with-call/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, errors }) { + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/select-value-with-call/main.svelte b/packages/svelte/tests/runtime-runes/samples/select-value-with-call/main.svelte new file mode 100644 index 0000000000..b1d60ecf6d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-value-with-call/main.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/_config.js new file mode 100644 index 0000000000..ae00bd9f56 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: 'a' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/main.svelte new file mode 100644 index 0000000000..d090460a09 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-2/main.svelte @@ -0,0 +1,13 @@ + + +{@render b()} + +{#snippet a()} + {abc} +{/snippet} + +{#snippet b()} + {@render a()} +{/snippet} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/_config.js new file mode 100644 index 0000000000..240263603d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '

            Hello world!

            ' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/main.svelte new file mode 100644 index 0000000000..b23a69d53d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-3/main.svelte @@ -0,0 +1,13 @@ + + + + +

            Hello {name}!

            + +{#snippet foo()} + oo +{/snippet} diff --git a/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js new file mode 100644 index 0000000000..0fb086c8e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js @@ -0,0 +1,41 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, target, warnings }) { + const [btn1, btn2, btn3, btn4, btn5, btn6, clear] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + btn2.click(); + btn3.click(); + btn4.click(); + btn5.click(); + btn6.click(); + }); + + assert.deepEqual(warnings, [ + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.includes(...)` will produce unexpected results', + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.indexOf(...)` will produce unexpected results', + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.lastIndexOf(...)` will produce unexpected results' + ]); + + flushSync(() => clear.click()); + warnings.length = 0; + + flushSync(() => { + btn1.click(); + btn2.click(); + btn3.click(); + btn4.click(); + btn5.click(); + btn6.click(); + }); + + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte new file mode 100644 index 0000000000..1b7bf444f1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte @@ -0,0 +1,23 @@ + + + + + +
            + + + + +
            + + + + +
            + + diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js new file mode 100644 index 0000000000..707c92118b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
            false
            true
            ` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte new file mode 100644 index 0000000000..cb5c116f37 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte @@ -0,0 +1,10 @@ + + +
            {2 in $state.snapshot(state)}
            +
            {5 in $state.snapshot(state)}
            diff --git a/packages/svelte/tests/runtime-runes/samples/store-reassign-object/_config.js b/packages/svelte/tests/runtime-runes/samples/store-reassign-object/_config.js new file mode 100644 index 0000000000..f9a329889d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-reassign-object/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ target, assert }) { + assert.htmlEqual(target.innerHTML, `

            bar

            `); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/store-reassign-object/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-reassign-object/main.svelte new file mode 100644 index 0000000000..ecffbb2d83 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-reassign-object/main.svelte @@ -0,0 +1,11 @@ + + +

            {clone.name}

            diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte new file mode 100644 index 0000000000..364a4a7aca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/Test.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js new file mode 100644 index 0000000000..bb99988756 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/_config.js @@ -0,0 +1,12 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const input = target.querySelector('input'); + flushSync(() => { + input?.click(); + }); + assert.deepEqual(logs, [0, 1]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte new file mode 100644 index 0000000000..7ba59b5afc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-update-on-destroy/main.svelte @@ -0,0 +1,15 @@ + + + + +{#if checked} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js new file mode 100644 index 0000000000..bd76e4e6b9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/_config.js @@ -0,0 +1,95 @@ +import { flushSync, tick } from 'svelte'; +import { test } from '../../test'; + +// This test counts mutations on hydration +// set_style() should not mutate style on hydration, except if mismatch +export default test({ + mode: ['server', 'hydrate'], + + server_props: { + browser: false + }, + + props: { + browser: true + }, + + ssrHtml: ` +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            + `, + + html: ` +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            + `, + + async test({ target, assert, component, instance }) { + flushSync(); + tick(); + assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']); + + let divs = target.querySelectorAll('div'); + + // Note : we cannot compare HTML because set_style() use dom.style.cssText + // which can alter the format of the attribute... + + divs.forEach((d) => assert.equal(d.style.margin, '')); + divs.forEach((d) => assert.equal(d.style.color, 'red')); + divs.forEach((d) => assert.equal(d.style.fontSize, '18px')); + + component.margin = '1px'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'margin' + ); + divs.forEach((d) => assert.equal(d.style.margin, '1px')); + + component.color = 'yellow'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'color' + ); + divs.forEach((d) => assert.equal(d.style.color, 'yellow')); + + component.fontSize = '10px'; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'fontSize' + ); + divs.forEach((d) => assert.equal(d.style.fontSize, '10px')); + + component.fontSize = null; + flushSync(); + assert.deepEqual( + instance.get_and_clear_mutations(), + ['DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV', 'DIV'], + 'fontSize' + ); + divs.forEach((d) => assert.equal(d.style.fontSize, '')); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte new file mode 100644 index 0000000000..ae4da8ae37 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-directive-mutations/main.svelte @@ -0,0 +1,54 @@ + + +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            +
            diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/_config.js b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js new file mode 100644 index 0000000000..52690a431a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-update/_config.js @@ -0,0 +1,65 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +const style_1 = 'invalid-key:0; margin:4px;;color: green ;color:blue '; +const style_2 = ' other-key : 0 ; padding:2px; COLOR:green; color: blue'; +const style_2_normalized = 'padding: 2px; color: blue;'; + +// https://github.com/sveltejs/svelte/issues/15309 +export default test({ + props: { + style: style_1 + }, + + ssrHtml: ` +
            +
            + + + + `, + + async test({ assert, target, component }) { + component.style = style_2; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + ` + ); + + component.style = ''; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + ` + ); + + component.style = null; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            + + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte new file mode 100644 index 0000000000..d29590d670 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/style-update/main.svelte @@ -0,0 +1,9 @@ + + +
            +
            + + + diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js new file mode 100644 index 0000000000..3c0195ce34 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/_config.js @@ -0,0 +1,33 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +// https://github.com/sveltejs/svelte/issues/15368 +export default test({ + compileOptions: { + dev: true + }, + + mode: ['client'], + + html: ` +

            BOOM

            +

            BOOM

            +
            OK
            +
            OK
            + `, + + async test({ target, assert, component }) { + component.ok = false; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +

            BOOM

            +

            BOOM

            +

            BOOM

            +

            BOOM

            + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte new file mode 100644 index 0000000000..30e074c762 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-boundary-dev-const/main.svelte @@ -0,0 +1,49 @@ + + + +
            {throwError()}
            + + {#snippet failed()} +

            BOOM

            + {/snippet} +
            + + + {@const result = throwError()} +
            {result}
            + + {#snippet failed()} +

            BOOM

            + {/snippet} +
            + + +
            {throwErrorOnUpdate()}
            + + {#snippet failed()} +

            BOOM

            + {/snippet} +
            + + + {@const result = throwErrorOnUpdate()} +
            {result}
            + + {#snippet failed()} +

            BOOM

            + {/snippet} +
            diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/_config.js new file mode 100644 index 0000000000..c7a6213d82 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/_config.js @@ -0,0 +1,60 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +export default test({ + html: ` +
            +
            +
            +
            +
            +
            + `, + + async test({ assert, target, component }) { + component.active = true; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` +
            +
            +
            +
            +
            +
            + ` + ); + + component.tag = 'span'; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` + + + + + + + ` + ); + + component.active = false; + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` + + + + + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/main.svelte new file mode 100644 index 0000000000..709f7b4fa1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-css-hash/main.svelte @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js new file mode 100644 index 0000000000..7373a4043e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js @@ -0,0 +1,11 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ``, + test({ assert, target }) { + const a = target.querySelector('a'); + ok(a); + + assert.equal(a.namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte new file mode 100644 index 0000000000..3676278c6c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte @@ -0,0 +1,7 @@ + + {#if true} + + {name} + + {/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/_config.js new file mode 100644 index 0000000000..7543278b62 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/_config.js @@ -0,0 +1,11 @@ +import { test, ok } from '../../test'; + +export default test({ + html: `potato`, + test({ assert, target }) { + const title = target.querySelector('title'); + ok(title); + + assert.equal(title.namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/main.svelte new file mode 100644 index 0000000000..8be23f8b23 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-3/main.svelte @@ -0,0 +1,5 @@ + + {#if true} + potato + {/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte new file mode 100644 index 0000000000..53e6203dde --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte @@ -0,0 +1,8 @@ + +{#if true} + + + +{:else} +
            lol
            +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js new file mode 100644 index 0000000000..22a2469bfb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js @@ -0,0 +1,14 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ``, + test({ assert, target }) { + const g = target.querySelector('g'); + const rect = target.querySelector('rect'); + ok(g); + ok(rect); + + assert.equal(g.namespaceURI, 'http://www.w3.org/2000/svg'); + assert.equal(rect.namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte new file mode 100644 index 0000000000..8f6154462f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js new file mode 100644 index 0000000000..bc1793e7a4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let btn = target.querySelector('button'); + + btn?.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
            Count 1!
            Count from store 1!
            ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte new file mode 100644 index 0000000000..82d20105b8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/toStore-subscribe2/main.svelte @@ -0,0 +1,11 @@ + + +
            Count {counter}!
            +
            Count from store {$count}!
            + + diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-teardown/_config.js b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/_config.js new file mode 100644 index 0000000000..95904f011f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [, btn2] = target.querySelectorAll('button'); + + btn2.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-teardown/child.svelte b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/child.svelte new file mode 100644 index 0000000000..f1b1b7b497 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/child.svelte @@ -0,0 +1,11 @@ + + +

            + Current value: + {$currentValue} +

            diff --git a/packages/svelte/tests/runtime-runes/samples/toStore-teardown/main.svelte b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/main.svelte new file mode 100644 index 0000000000..7d36dd95cb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/toStore-teardown/main.svelte @@ -0,0 +1,15 @@ + + + + + +{#if data} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/transition-each-4/_config.js b/packages/svelte/tests/runtime-runes/samples/transition-each-4/_config.js new file mode 100644 index 0000000000..a0e29ec8ff --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-each-4/_config.js @@ -0,0 +1,35 @@ +import { flushSync } from '../../../../src/index-client.js'; +import { test } from '../../test'; + +export default test({ + async test({ assert, raf, target, logs }) { + assert.htmlEqual( + target.innerHTML, + '
            1
            2
            3
            ' + ); + + const btn1 = target.querySelector('button'); + btn1?.click(); + flushSync(); + raf.tick(250); + + assert.htmlEqual( + target.innerHTML, + '
            1
            2
            3
            ' + ); + + logs.length = 0; + + await Promise.resolve(); + + flushSync(); + raf.tick(500); + + assert.htmlEqual( + target.innerHTML, + '
            3
            4
            ' + ); + + assert.deepEqual(logs, ['$effect.pre', '$effect.pre', '$effect', '$effect']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/transition-each-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/transition-each-4/main.svelte new file mode 100644 index 0000000000..d399bee691 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-each-4/main.svelte @@ -0,0 +1,39 @@ + + + + +{#if toggle} +
            + {#each items as item (item)} + {(() => { + $effect(() => { + items; + console.log('$effect'); + }); + + $effect.pre(() => { + items; + console.log('$effect.pre'); + }); + })()} +
            {item}
            + {/each} +
            +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte index 3b98dafa05..d1b6452df4 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte @@ -8,6 +8,10 @@ console.log(this); } + function foo(): string { + return ""!; + } + class Foo { public name: string; x = 'x' as const; @@ -16,6 +20,13 @@ } } + class MyClass implements Hello {} + + abstract class MyAbstractClass { + abstract x(): void; + y() {} + } + declare const declared_const: number; declare function declared_fn(): void; declare class declared_class { @@ -24,7 +35,7 @@ declare module 'foobar' {} namespace SomeNamespace { - export type Foo = true + export type Foo = true; } export function overload(a: boolean): boolean; @@ -34,6 +45,8 @@ export type { Hello }; const TypedFoo = Foo; + const typeAssertion = true; + const x = foo(); + + + +{#snippet snip()} + snippet {x} +{/snippet} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js new file mode 100644 index 0000000000..18062b86fb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/_config.js @@ -0,0 +1,20 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, logs }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + + assert.htmlEqual( + target.innerHTML, + ` + +

            1/2

            + class Foo { + value = $state(0); + double = $derived(this.value * 2); + + constructor() { + console.log(this.value, this.double); + } + + increment() { + this.value++; + } + } + + let foo = $state(); + + $effect(() => { + foo = new Foo(); + }); + + + + +{#if foo} +

            {foo.value}/{foo.double}

            +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js new file mode 100644 index 0000000000..1fe92ed2d7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `3` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte new file mode 100644 index 0000000000..0873eb741d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte @@ -0,0 +1,47 @@ + + + + +{test} diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js new file mode 100644 index 0000000000..0310ec4fbb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + test({ assert, target, logs }) { + assert.deepEqual(logs, ['Outer', 'Inner', 'Outer', 'Inner']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte new file mode 100644 index 0000000000..5e95dbfd41 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/untracked-write-pre/main.svelte @@ -0,0 +1,13 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js new file mode 100644 index 0000000000..08f0c5aec7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js @@ -0,0 +1,49 @@ +import { test, ok } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + mode: ['client'], + + async test({ assert, target }) { + /** + * @type {HTMLInputElement | null} + */ + const input = target.querySelector('input[type=text]'); + /** + * @type {HTMLButtonElement | null} + */ + const setString = target.querySelector('#setString'); + /** + * @type {HTMLButtonElement | null} + */ + const setNull = target.querySelector('#setNull'); + /** + * @type {HTMLButtonElement | null} + */ + const setUndefined = target.querySelector('#setUndefined'); + + ok(input); + ok(setString); + ok(setNull); + ok(setUndefined); + + // value should always be blank string when value attribute is set to null or undefined + + assert.equal(input.value, ''); + setString.click(); + flushSync(); + assert.equal(input.value, 'foo'); + + setNull.click(); + flushSync(); + assert.equal(input.value, ''); + + setString.click(); + flushSync(); + assert.equal(input.value, 'foo'); + + setUndefined.click(); + flushSync(); + assert.equal(input.value, ''); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte new file mode 100644 index 0000000000..9f944923c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte @@ -0,0 +1,9 @@ + + + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-2/_config.js b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/_config.js new file mode 100644 index 0000000000..fde3e7b1ea --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + html: `true true`, + + test({ assert, target, window }) {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/main.svelte new file mode 100644 index 0000000000..741aa69125 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/main.svelte @@ -0,0 +1,9 @@ + + +{expect1} {expect2} diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived-2/util.svelte.js b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/util.svelte.js new file mode 100644 index 0000000000..8e862753ab --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived-2/util.svelte.js @@ -0,0 +1,17 @@ +export const createAppState = (options) => { + const source = $derived(options.source()); + let value = $derived(source); + + return { + get value() { + return value; + }, + onChange(nextValue) { + value = nextValue; + } + }; +}; + +const result = createAppState({ source: () => 'wrong' }); +result.onChange('right'); +export const expect2 = result.value === 'right'; diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js new file mode 100644 index 0000000000..b48ccbdfd0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived/_config.js @@ -0,0 +1,46 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` +

            0 * 2 = 0

            + `, + + ssrHtml: ` +

            0 * 2 = 0

            + `, + + test({ assert, target, window }) { + const [input1, input2] = target.querySelectorAll('input'); + + flushSync(() => { + input1.value = '10'; + input1.dispatchEvent(new window.Event('input', { bubbles: true })); + }); + + assert.htmlEqual( + target.innerHTML, + `

            10 * 2 = 20

            ` + ); + + flushSync(() => { + input2.value = '99'; + input2.dispatchEvent(new window.Event('input', { bubbles: true })); + }); + + assert.htmlEqual( + target.innerHTML, + `

            10 * 2 = 99

            ` + ); + + flushSync(() => { + input1.value = '20'; + input1.dispatchEvent(new window.Event('input', { bubbles: true })); + }); + + assert.htmlEqual( + target.innerHTML, + `

            20 * 2 = 40

            ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte new file mode 100644 index 0000000000..ab1dde0b9b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/writable-derived/main.svelte @@ -0,0 +1,9 @@ + + + + + +

            {count} * 2 = {double}

            diff --git a/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/HeadNested.svelte b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/HeadNested.svelte new file mode 100644 index 0000000000..0784208798 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/HeadNested.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/_config.js b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/main.svelte b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/main.svelte new file mode 100644 index 0000000000..e32b40e9ed --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/head-component-props-id/main.svelte @@ -0,0 +1,8 @@ + + + + + + diff --git a/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html new file mode 100644 index 0000000000..ee65cb76c7 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html @@ -0,0 +1 @@ +

            devicePixelRatio:

            innerHeight:

            innerWidth:

            online:

            outerHeight:

            outerWidth:

            screenLeft:

            screenTop:

            scrollX:

            scrollY:

            \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte new file mode 100644 index 0000000000..e84e41bf63 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte @@ -0,0 +1,14 @@ + + +

            devicePixelRatio: {devicePixelRatio.current}

            +

            innerHeight: {innerHeight.current}

            +

            innerWidth: {innerWidth.current}

            +

            online: {online.current}

            +

            outerHeight: {outerHeight.current}

            +

            outerWidth: {outerWidth.current}

            +

            screenLeft: {screenLeft.current}

            +

            screenTop: {screenTop.current}

            +

            scrollX: {scrollX.current}

            +

            scrollY: {scrollY.current}

            \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts index f76c5b539f..3e57539427 100644 --- a/packages/svelte/tests/server-side-rendering/test.ts +++ b/packages/svelte/tests/server-side-rendering/test.ts @@ -15,6 +15,7 @@ import type { CompileOptions } from '#compiler'; interface SSRTest extends BaseTest { compileOptions?: Partial; props?: Record; + id_prefix?: string; withoutNormalizeHtml?: boolean; errors?: string[]; } @@ -33,7 +34,7 @@ const { test, run } = suite(async (config, test_dir) => { const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; const expected_html = try_read_file(`${test_dir}/_expected.html`); - const rendered = render(Component, { props: config.props || {} }); + const rendered = render(Component, { props: config.props || {}, idPrefix: config.id_prefix }); const { body, head } = rendered; fs.writeFileSync(`${test_dir}/_output/rendered.html`, body); diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 6796655cc8..3a427e9392 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,18 +1,20 @@ import { describe, assert, it } from 'vitest'; import { flushSync } from '../../src/index-client'; import * as $ from '../../src/internal/client/runtime'; +import { push, pop } from '../../src/internal/client/context'; import { effect, effect_root, render_effect, user_effect } from '../../src/internal/client/reactivity/effects'; -import { state, set } from '../../src/internal/client/reactivity/sources'; -import type { Derived, Value } from '../../src/internal/client/types'; +import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources'; +import type { Derived, Effect, Value } from '../../src/internal/client/types'; import { proxy } from '../../src/internal/client/proxy'; import { derived } from '../../src/internal/client/reactivity/deriveds'; import { snapshot } from '../../src/internal/shared/clone.js'; import { SvelteSet } from '../../src/reactivity/set'; +import { DESTROYED } from '../../src/internal/client/constants'; /** * @param runes runes mode @@ -22,13 +24,13 @@ import { SvelteSet } from '../../src/reactivity/set'; function run_test(runes: boolean, fn: (runes: boolean) => () => void) { return () => { // Create a component context to test runes vs legacy mode - $.push({}, runes); + push({}, runes); // Create a render context so that effect validations etc don't fail let execute: any; const destroy = effect_root(() => { execute = fn(runes); }); - $.pop(); + pop(); execute(); destroy(); }; @@ -67,7 +69,7 @@ describe('signals', () => { }; }); - test('multiple effects with state and derived in it#1', () => { + test('multiple effects with state and derived in it #1', () => { const log: string[] = []; let count = state(0); @@ -88,7 +90,7 @@ describe('signals', () => { }; }); - test('multiple effects with state and derived in it#2', () => { + test('multiple effects with state and derived in it #2', () => { const log: string[] = []; let count = state(0); @@ -222,20 +224,75 @@ describe('signals', () => { }; }); - test('effects correctly handle unowned derived values that do not change', () => { - const log: number[] = []; + test('https://perf.js.hyoo.ru/#!bench=9h2as6_u0mfnn #2', () => { + let res: number[] = []; - let count = state(0); - const read = () => { - const x = derived(() => ({ count: $.get(count) })); - return $.get(x); + const numbers = Array.from({ length: 2 }, (_, i) => i); + const fib = (n: number): number => (n < 2 ? 1 : fib(n - 1) + fib(n - 2)); + const hard = (n: number, l: string) => n + fib(16); + + const A = state(0); + const B = state(0); + + return () => { + const C = derived(() => ($.get(A) % 2) + ($.get(B) % 2)); + const D = derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2))); + const E = derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E')); + const F = derived(() => hard($.get(D)[0]! && $.get(B), 'F')); + const G = derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F)); + + const destroy = effect_root(() => { + effect(() => { + res.push(hard($.get(G), 'H')); + }); + effect(() => { + res.push($.get(G)); + }); + effect(() => { + res.push(hard($.get(F), 'J')); + }); + }); + + flushSync(); + + let i = 2; + while (--i) { + res.length = 0; + set(B, 1); + set(A, 1 + i * 2); + flushSync(); + + set(A, 2 + i * 2); + set(B, 2); + flushSync(); + + assert.equal(res.length, 4); + assert.deepEqual(res, [3198, 1601, 3195, 1598]); + } + + destroy(); + assert(A.reactions === null); + assert(B.reactions === null); }; - const derivedCount = derived(() => read().count); - user_effect(() => { - log.push($.get(derivedCount)); - }); + }); + + test('effects correctly handle unowned derived values that do not change', () => { + const log: number[] = []; return () => { + let count = state(0); + const read = () => { + const x = derived(() => ({ count: $.get(count) })); + return $.get(x); + }; + const derivedCount = derived(() => read().count); + + const destroy = effect_root(() => { + user_effect(() => { + log.push($.get(derivedCount)); + }); + }); + flushSync(() => set(count, 1)); // Ensure we're not leaking consumers assert.deepEqual(count.reactions?.length, 1); @@ -246,6 +303,8 @@ describe('signals', () => { // Ensure we're not leaking consumers assert.deepEqual(count.reactions?.length, 1); assert.deepEqual(log, [0, 1, 2, 3]); + + destroy(); }; }); @@ -255,12 +314,16 @@ describe('signals', () => { const a = state(0); const b = state(0); - const c = derived(() => { - const a_2 = derived(() => $.get(a) + '!'); - const b_2 = derived(() => $.get(b) + '?'); - nested.push(a_2, b_2); + let c: any; + + const destroy = effect_root(() => { + c = derived(() => { + const a_2 = derived(() => $.get(a) + '!'); + const b_2 = derived(() => $.get(b) + '?'); + nested.push(a_2, b_2); - return { a: $.get(a_2), b: $.get(b_2) }; + return { a: $.get(a_2), b: $.get(b_2) }; + }); }); $.get(c); @@ -273,11 +336,10 @@ describe('signals', () => { $.get(c); - // Ensure we're not leaking dependencies - assert.deepEqual( - nested.slice(0, -2).map((s) => s.deps), - [null, null, null, null] - ); + destroy(); + + assert.equal(a.reactions, null); + assert.equal(b.reactions, null); }; }); @@ -296,6 +358,7 @@ describe('signals', () => { const destroy = effect_root(() => { user_effect(() => { log.push($.get(calc)); + $.get(calc); }); }); @@ -306,7 +369,7 @@ describe('signals', () => { flushSync(() => set(count, 4)); flushSync(() => set(count, 0)); // Ensure we're not leaking consumers - assert.deepEqual(count.reactions?.length, 2); + assert.deepEqual(count.reactions?.length, 1); assert.deepEqual(calc.reactions?.length, 1); assert.deepEqual(log, [0, 2, 'limit', 0]); destroy(); @@ -337,25 +400,69 @@ describe('signals', () => { }; }); - let some_state = state({}); - let some_deps = derived(() => { - return [$.get(some_state)]; - }); - test('two effects with an unowned derived that has some dependencies', () => { const log: Array> = []; - render_effect(() => { - log.push($.get(some_deps)); - }); + return () => { + let some_state = state({}); + let some_deps = derived(() => { + return [$.get(some_state)]; + }); + let destroy2: any; - render_effect(() => { - log.push($.get(some_deps)); - }); + const destroy = effect_root(() => { + render_effect(() => { + $.untrack(() => { + log.push($.get(some_deps)); + }); + }); - return () => { + destroy2 = effect_root(() => { + render_effect(() => { + log.push($.get(some_deps)); + }); + + render_effect(() => { + log.push($.get(some_deps)); + }); + }); + }); + + set(some_state, {}); + flushSync(); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}], [{}]]); + + destroy2(); + + set(some_state, {}); + flushSync(); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}], [{}]]); + + log.length = 0; + + const destroy3 = effect_root(() => { + render_effect(() => { + $.untrack(() => { + log.push($.get(some_deps)); + }); + log.push($.get(some_deps)); + }); + }); + + set(some_state, {}); flushSync(); - assert.deepEqual(log, [[{}], [{}]]); + + assert.deepEqual(log, [[{}], [{}], [{}], [{}]]); + + destroy3(); + + assert(some_state.reactions === null); + + destroy(); + + assert(some_state.reactions === null); }; }); @@ -380,6 +487,26 @@ describe('signals', () => { }; }); + test('schedules rerun when updating deeply nested value', (runes) => { + if (!runes) return () => {}; + + const value = proxy({ a: { b: { c: 0 } } }); + user_effect(() => { + value.a.b.c += 1; + }); + + return () => { + let errored = false; + try { + flushSync(); + } catch (e: any) { + assert.include(e.message, 'effect_update_depth_exceeded'); + errored = true; + } + assert.equal(errored, true); + }; + }); + test('schedules rerun when writing to signal before reading it', (runes) => { if (!runes) return () => {}; @@ -401,6 +528,42 @@ describe('signals', () => { }; }); + test('schedules rerun when writing to signal before reading it from derived', (runes) => { + if (!runes) return () => {}; + let log: any[] = []; + + const value = state(1); + const double = derived(() => $.get(value) * 2); + + user_effect(() => { + set(value, 10); + log.push($.get(double)); + }); + + return () => { + flushSync(); + assert.deepEqual(log, [20]); + }; + }); + + test('schedules rerun when writing to signal after reading it from derived', (runes) => { + if (!runes) return () => {}; + let log: any[] = []; + + const value = state(1); + const double = derived(() => $.get(value) * 2); + + user_effect(() => { + log.push($.get(double)); + set(value, 10); + }); + + return () => { + flushSync(); + assert.deepEqual(log, [2, 20]); + }; + }); + test('effect teardown is removed on re-run', () => { const count = state(0); let first = true; @@ -439,6 +602,7 @@ describe('signals', () => { effect(() => { log.push('inner', $.get(inner)); }); + return $.get(outer); }); }); }); @@ -492,6 +656,103 @@ describe('signals', () => { }; }); + test('mixed nested deriveds correctly cleanup when no longer connected to graph #1', () => { + let a: Derived; + let b: Derived; + let s = state(0); + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + $.get(s); + }); + $.untrack(() => { + $.get(b); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 1); + destroy(); + assert.equal(s?.reactions, null); + }; + }); + + test('mixed nested deriveds correctly cleanup when no longer connected to graph #2', () => { + let a: Derived; + let b: Derived; + let s = state(0); + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + $.get(s); + }); + effect_root(() => { + $.get(b); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 1); + destroy(); + assert.equal(s?.reactions, null); + }; + }); + + test('mixed nested deriveds correctly cleanup when no longer connected to graph #3', () => { + let a: Derived; + let b: Derived; + let s = state(0); + let logs: any[] = []; + + const destroy = effect_root(() => { + render_effect(() => { + a = derived(() => { + b = derived(() => { + return $.get(s); + }); + effect_root(() => { + $.get(b); + }); + render_effect(() => { + logs.push($.get(b)); + }); + $.get(s); + }); + $.get(a); + }); + }); + + return () => { + flushSync(); + assert.equal(a?.deps?.length, 1); + assert.equal(s?.reactions?.length, 2); + + set(s, 1); + flushSync(); + + assert.deepEqual(logs, [0, 1]); + + destroy(); + assert.equal(s?.reactions, null); + }; + }); + test('deriveds update upon reconnection #1', () => { let a = state(false); let b = state(false); @@ -678,6 +939,28 @@ describe('signals', () => { }; }); + test('unowned deriveds dependencies are correctly de-duped', () => { + return () => { + let a = state(0); + let b = state(true); + let c = derived(() => $.get(a)); + let d = derived(() => ($.get(b) ? 1 : $.get(a) + $.get(c) + $.get(a))); + + $.get(d); + + assert.equal(d.deps?.length, 1); + + $.get(d); + + set(a, 1); + set(b, false); + + $.get(d); + + assert.equal(d.deps?.length, 3); + }; + }); + test('unowned deriveds correctly update', () => { return () => { const arr1 = proxy<{ a: number }[]>([]); @@ -695,14 +978,30 @@ describe('signals', () => { }; }); - test('deriveds cannot depend on state they own', () => { + test('deriveds do not depend on state they own', () => { return () => { + let s; + const d = derived(() => { - const s = state(0); + s = state(0); return $.get(s); }); - assert.throws(() => $.get(d), 'state_unsafe_local_read'); + assert.equal($.get(d), 0); + + set(s!, 1); + assert.equal($.get(d), 0); + }; + }); + + test('effects do not depend on state they own', () => { + user_effect(() => { + const value = state(0); + set(value, $.get(value) + 1); + }); + + return () => { + flushSync(); }; }); @@ -740,29 +1039,119 @@ describe('signals', () => { }; }); - test('nested deriveds clean up the relationships when used with untrack', () => { + test('deriveds containing effects work correctly', () => { return () => { let a = render_effect(() => {}); + let b = state(0); + let c; + let effects: Effect[] = []; const destroy = effect_root(() => { a = render_effect(() => { - $.untrack(() => { - const b = derived(() => { - const c = derived(() => {}); - $.untrack(() => { - $.get(c); - }); + c = derived(() => { + effects.push( + effect(() => { + $.get(b); + }) + ); + $.get(b); + }); + $.get(c); + }); + }); + + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); + + set(b, 1); + + flushSync(); + + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); + + destroy(); + + assert.equal(a.first, null); + + assert.equal(effects.length, 2); + assert.equal((effects[0].f & DESTROYED) !== 0, true); + assert.equal((effects[1].f & DESTROYED) !== 0, true); + }; + }); + + test("deriveds set after they are DIRTY doesn't get updated on get", () => { + return () => { + const a = state(0); + const b = derived(() => $.get(a)); + + set(b, 1); + assert.equal($.get(b), 1); + + set(a, 2); + assert.equal($.get(b), 2); + set(b, 3); + + assert.equal($.get(b), 3); + }; + }); + + test("unowned deriveds set after they are DIRTY doesn't get updated on get", () => { + return () => { + const a = state(0); + const b = derived(() => $.get(a)); + const c = derived(() => $.get(b)); + + set(b, 1); + assert.equal($.get(c), 1); + + set(a, 2); + + assert.equal($.get(b), 2); + assert.equal($.get(c), 2); + }; + }); + + test('deriveds containing effects work correctly when used with untrack', () => { + return () => { + let a = render_effect(() => {}); + let b = state(0); + let c; + let effects: Effect[] = []; + + const destroy = effect_root(() => { + a = render_effect(() => { + c = derived(() => { + $.untrack(() => { + effects.push( + effect(() => { + $.get(b); + }) + ); }); $.get(b); }); + $.get(c); }); }); - assert.deepEqual(a.deriveds?.length, 1); + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); + + set(b, 1); + + flushSync(); + + assert.equal(c!.effects?.length, 1); + assert.equal(a.first, a.last); destroy(); - assert.deepEqual(a.deriveds, null); + assert.equal(a.first, null); + + assert.equal(effects.length, 2); + assert.equal((effects[0].f & DESTROYED) !== 0, true); + assert.equal((effects[1].f & DESTROYED) !== 0, true); }; }); @@ -770,15 +1159,89 @@ describe('signals', () => { return () => { const count = state(0n); - assert.doesNotThrow(() => $.update(count)); + assert.doesNotThrow(() => update(count)); assert.equal($.get(count), 1n); - assert.doesNotThrow(() => $.update(count, -1)); + assert.doesNotThrow(() => update(count, -1)); assert.equal($.get(count), 0n); - assert.doesNotThrow(() => $.update_pre(count)); + assert.doesNotThrow(() => update_pre(count)); assert.equal($.get(count), 1n); - assert.doesNotThrow(() => $.update_pre(count, -1)); + assert.doesNotThrow(() => update_pre(count, -1)); assert.equal($.get(count), 0n); }; }); + + test('unowned deriveds correctly re-attach to their source', () => { + const log: any[] = []; + + return () => { + const a = state(0); + const b = state(0); + const c = derived(() => { + $.get(a); + return $.get(b); + }); + + $.get(c); + + set(a, 1); + + const destroy = effect_root(() => { + render_effect(() => { + log.push($.get(c)); + }); + }); + + assert.deepEqual(log, [0]); + + set(b, 1); + + flushSync(); + + assert.deepEqual(log, [0, 1]); + + destroy(); + }; + }); + + test('unowned deriveds correctly update', () => { + const log: any[] = []; + + return () => { + const a = state(0); + const b = state(0); + const c = derived(() => { + return $.get(a); + }); + const d = derived(() => { + return $.get(b); + }); + + const destroy = effect_root(() => { + const e = derived(() => { + return $.get(c) === 1 && $.get(d) === 1; + }); + render_effect(() => { + log.push($.get(e)); + }); + }); + + assert.deepEqual(log, [false]); + + set(a, 1); + set(b, 1); + + flushSync(); + + assert.deepEqual(log, [false, true]); + + set(b, 9); + + flushSync(); + + assert.deepEqual(log, [false, true, false]); + + destroy(); + }; + }); }); diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js index 87fce120fd..3e5a12ed9d 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; function increment(_, counter) { counter.count += 1; @@ -26,11 +26,11 @@ export default function Await_block_scope($$anchor) { var text_1 = $.sibling(node); $.template_effect(() => { - $.set_text(text, `clicks: ${counter.count ?? ""}`); - $.set_text(text_1, ` ${counter.count ?? ""}`); + $.set_text(text, `clicks: ${counter.count ?? ''}`); + $.set_text(text_1, ` ${counter.count ?? ''}`); }); $.append($$anchor, fragment); } -$.delegate(["click"]); \ No newline at end of file +$.delegate(['click']); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js index 44c68a7c96..4b6e32d58e 100644 --- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Await_block_scope($$payload) { let counter = { count: 0 }; @@ -8,7 +8,7 @@ export default function Await_block_scope($$payload) { counter.count += 1; } - $$payload.out += ` `; - $.await(promise, () => {}, (counter) => {}, () => {}); - $$payload.out += ` ${$.escape(counter.count)}`; + $$payload.out += ` `; + $.await($$payload, promise, () => {}, (counter) => {}); + $$payload.out += ` ${$.escape(counter.count)}`; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index bcddb6f658..390e86a351 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -1,11 +1,11 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; import TextInput from './Child.svelte'; const snippet = ($$anchor) => { $.next(); - var text = $.text("Something"); + var text = $.text('Something'); $.append($$anchor, text); }; @@ -23,12 +23,12 @@ export default function Bind_component_snippet($$anchor) { return $.get(value); }, set value($$value) { - $.set(value, $.proxy($$value)); + $.set(value, $$value, true); } }); var text_1 = $.sibling(node); - $.template_effect(() => $.set_text(text_1, ` value: ${$.get(value) ?? ""}`)); + $.template_effect(() => $.set_text(text_1, ` value: ${$.get(value) ?? ''}`)); $.append($$anchor, fragment); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index d223a31502..cadae2cf15 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; import TextInput from './Child.svelte'; function snippet($$payload) { @@ -23,7 +23,7 @@ export default function Bind_component_snippet($$payload) { }); $$payload.out += ` value: ${$.escape(value)}`; - }; + } do { $$settled = true; diff --git a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js index fedcc87696..dfd32a04e5 100644 --- a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; export default function Bind_this($$anchor) { $.bind_this(Foo($$anchor, { $$legacy: true }), ($$value) => foo = $$value, () => foo); diff --git a/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js index badca8d4a0..148573766f 100644 --- a/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Bind_this($$payload) { Foo($$payload, {}); diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index 399fa19b62..2133974176 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; export default function Class_state_field_constructor_assignment($$anchor, $$props) { $.push($$props, true); @@ -12,14 +12,14 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro } set a(value) { - $.set(this.#a, $.proxy(value)); + $.set(this.#a, value, true); } #b = $.state(); constructor() { this.a = 1; - this.#b.v = 2; + $.set(this.#b, 2); } } diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js index bcc7c2e1f2..2a115a4983 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Class_state_field_constructor_assignment($$payload, $$props) { $.push(); diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js index dbda825c4d..47f297bce9 100644 --- a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ /* index.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/client"; +import * as $ from 'svelte/internal/client'; let a = $.state(1); let b = $.state(2); @@ -8,8 +8,8 @@ let d = 4; export function update(array) { ( - $.set(a, $.proxy(array[0])), - $.set(b, $.proxy(array[1])) + $.set(a, array[0], true), + $.set(b, array[1], true) ); [c, d] = array; diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js index 2797d4312b..62b655b266 100644 --- a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js @@ -1,5 +1,5 @@ /* index.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; let a = 1; let b = 2; diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 846acc67a6..219db6ffd5 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; var root = $.template(`
            `, 3); @@ -11,23 +11,24 @@ export default function Main($$anchor) { var div = $.first_child(fragment); var svg = $.sibling(div, 2); var custom_element = $.sibling(svg, 2); - var div_1 = $.sibling(custom_element, 2); - $.template_effect(() => $.set_attribute(div_1, "foobar", y())); + $.template_effect(() => $.set_custom_element_data(custom_element, 'fooBar', x)); + var div_1 = $.sibling(custom_element, 2); var svg_1 = $.sibling(div_1, 2); - - $.template_effect(() => $.set_attribute(svg_1, "viewBox", y())); - var custom_element_1 = $.sibling(svg_1, 2); - $.template_effect(() => $.set_custom_element_data(custom_element_1, "fooBar", y())); - - $.template_effect(() => { - $.set_attribute(div, "foobar", x); - $.set_attribute(svg, "viewBox", x); - $.set_custom_element_data(custom_element, "fooBar", x); - }); + $.template_effect(() => $.set_custom_element_data(custom_element_1, 'fooBar', y())); + + $.template_effect( + ($0, $1) => { + $.set_attribute(div, 'foobar', x); + $.set_attribute(svg, 'viewBox', x); + $.set_attribute(div_1, 'foobar', $0); + $.set_attribute(svg_1, 'viewBox', $1); + }, + [y, y] + ); $.append($$anchor, fragment); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js index 5eaa55aa49..4ea5edb6a0 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js @@ -1,9 +1,9 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Main($$payload) { // needs to be a snapshot test because jsdom does auto-correct the attribute casing let x = 'test'; let y = () => 'test'; - $$payload.out += ` `; + $$payload.out += ` `; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js index 80f2da11a2..c0626bd416 100644 --- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; export default function Each_string_template($$anchor) { var fragment = $.comment(); @@ -11,7 +11,7 @@ export default function Each_string_template($$anchor) { var text = $.text(); - $.template_effect(() => $.set_text(text, `${thing ?? ""}, `)); + $.template_effect(() => $.set_text(text, `${thing ?? ''}, `)); $.append($$anchor, text); }); diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js index d4debe1727..4386c22ebe 100644 --- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Each_string_template($$payload) { const each_array = $.ensure_array_like(['foo', 'bar', 'baz']); diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js index bab47c8c50..c2a6054bc6 100644 --- a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js @@ -1,4 +1,4 @@ /* index.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/client"; +import * as $ from 'svelte/internal/client'; export const object = $.proxy({ ok: true }); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js index a3b619df6e..1f6c244212 100644 --- a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ /* index.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export const object = { ok: true }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index 95d1c72017..762a23754c 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; export default function Function_prop_no_getter($$anchor) { let count = $.state(0); @@ -13,13 +13,13 @@ export default function Function_prop_no_getter($$anchor) { Button($$anchor, { onmousedown: () => $.set(count, $.get(count) + 1), onmouseup, - onmouseenter: () => $.set(count, $.proxy(plusOne($.get(count)))), + onmouseenter: () => $.set(count, plusOne($.get(count)), true), children: ($$anchor, $$slotProps) => { $.next(); var text = $.text(); - $.template_effect(() => $.set_text(text, `clicks: ${$.get(count) ?? ""}`)); + $.template_effect(() => $.set_text(text, `clicks: ${$.get(count) ?? ''}`)); $.append($$anchor, text); }, $$slots: { default: true } diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index 05b343d821..88f6f55ee7 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Function_prop_no_getter($$payload) { let count = 0; diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js index 9f6f291669..899c126001 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; var root = $.template(`

            hello world

            `); diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js index a313799dfd..8766fb1300 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Hello_world($$payload) { $$payload.out += `

            hello world

            `; 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 86c2880abc..3c8322500b 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 @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; var root = $.template(`

            hello world

            `); diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js index 33fe307c09..959e0a403e 100644 --- a/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Hmr($$payload) { $$payload.out += `

            hello world

            `; diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js index 9c7d2f3f23..ebbe191dcb 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; import { random } from './module.svelte'; export default function Imports_in_modules($$anchor) { diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js index 6edbc8af77..0d366e6258 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js @@ -1,5 +1,5 @@ /* module.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/client"; +import * as $ from 'svelte/internal/client'; import { random } from './export'; export { random }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js index 35b64b4186..4cd6bc59d7 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; import { random } from './module.svelte'; export default function Imports_in_modules($$payload) { diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js index e51aae5a25..2e0af8af84 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js @@ -1,5 +1,5 @@ /* module.svelte.js generated by Svelte VERSION */ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; import { random } from './export'; export { random }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_config.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js new file mode 100644 index 0000000000..332c909ebe --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/client/index.svelte.js @@ -0,0 +1,34 @@ +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; + +var on_click = (_, count) => $.update(count); +var root = $.template(`

            `, 1); + +export default function Nullish_coallescence_omittance($$anchor) { + let name = 'world'; + let count = $.state(0); + var fragment = root(); + var h1 = $.first_child(fragment); + + h1.textContent = `Hello, ${name ?? ''}!`; + + var b = $.sibling(h1, 2); + + b.textContent = `${1 ?? 'stuff'}${2 ?? 'more stuff'}${3 ?? 'even more stuff'}`; + + var button = $.sibling(b, 2); + + button.__click = [on_click, count]; + + var text = $.child(button); + + $.reset(button); + + var h1_1 = $.sibling(button, 2); + + h1_1.textContent = `Hello, ${name ?? 'earth' ?? ''}`; + $.template_effect(() => $.set_text(text, `Count is ${$.get(count) ?? ''}`)); + $.append($$anchor, fragment); +} + +$.delegate(['click']); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js new file mode 100644 index 0000000000..8181bfd98e --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js @@ -0,0 +1,8 @@ +import * as $ from 'svelte/internal/server'; + +export default function Nullish_coallescence_omittance($$payload) { + let name = 'world'; + let count = 0; + + $$payload.out += `

            Hello, ${$.escape(name)}!

            ${$.escape(1 ?? 'stuff')}${$.escape(2 ?? 'more stuff')}${$.escape(3 ?? 'even more stuff')}

            Hello, ${$.escape(name ?? 'earth' ?? null)}

            `; +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte new file mode 100644 index 0000000000..a67c574fee --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/index.svelte @@ -0,0 +1,8 @@ + +

            Hello, {null}{name}!

            +{1 ?? 'stuff'}{2 ?? 'more stuff'}{3 ?? 'even more stuff'} + +

            Hello, {name ?? 'earth' ?? null}

            \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.js index 2a10dbc1b1..5a46b9bbef 100644 --- a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.js @@ -1,10 +1,10 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; export default function Props_identifier($$anchor, $$props) { $.push($$props, true); - let props = $.rest_props($$props, ["$$slots", "$$events", "$$legacy"]); + let props = $.rest_props($$props, ['$$slots', '$$events', '$$legacy']); $$props.a; props[a]; diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js index 6fef87d63b..33a3633939 100644 --- a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Props_identifier($$payload, $$props) { $.push(); diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js index 0a627a55ae..940ed8f9e8 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js @@ -1,6 +1,6 @@ -import "svelte/internal/disclose-version"; -import "svelte/internal/flags/legacy"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/legacy'; +import * as $ from 'svelte/internal/client'; var root = $.template(`

            `, 1); diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js index 1eea71e4dd..588332407a 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Purity($$payload) { $$payload.out += `

            ${$.escape(Math.max(0, Math.min(0, 100)))}

            ${$.escape(location.href)}

            `; diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js index 8dd7df82cf..46d376aca2 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; var root = $.template(`

            we don't need to traverse these nodes

            or

            these

            ones

            these

            trailing

            nodes

            can

            be

            completely

            ignored

            `, 3); @@ -20,7 +20,7 @@ export default function Skip_static_subtree($$anchor, $$props) { var cant_skip = $.sibling(main, 2); var custom_elements = $.child(cant_skip); - $.set_custom_element_data(custom_elements, "with", "attributes"); + $.set_custom_element_data(custom_elements, 'with', 'attributes'); $.reset(cant_skip); var div = $.sibling(cant_skip, 2); @@ -38,16 +38,12 @@ export default function Skip_static_subtree($$anchor, $$props) { var select = $.sibling(div_1, 2); var option = $.child(select); - option.value = null == (option.__value = "a") ? "" : "a"; + option.value = null == (option.__value = 'a') ? '' : 'a'; $.reset(select); var img = $.sibling(select, 2); - var div_2 = $.sibling(img, 2); - var img_1 = $.child(div_2); - $.reset(div_2); + $.next(2); $.template_effect(() => $.set_text(text, $$props.title)); - $.handle_lazy_img(img); - $.handle_lazy_img(img_1); $.append($$anchor, fragment); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js index 309f5a2b57..e694c12647 100644 --- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Skip_static_subtree($$payload, $$props) { let { title, content } = $$props; diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index c24023c8a4..a67210e541 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; function reset(_, str, tpl) { $.set(str, ''); @@ -30,4 +30,4 @@ export default function State_proxy_literal($$anchor) { $.append($$anchor, fragment); } -$.delegate(["click"]); \ No newline at end of file +$.delegate(['click']); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js index 3424f807ab..7b2a884d70 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function State_proxy_literal($$payload) { let str = ''; @@ -11,5 +11,5 @@ export default function State_proxy_literal($$payload) { tpl = ``; } - $$payload.out += ` `; + $$payload.out += ` `; } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js index a4bbea582b..2270005ee0 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -1,8 +1,8 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; export default function Svelte_element($$anchor, $$props) { - let tag = $.prop($$props, "tag", 3, 'hr'); + let tag = $.prop($$props, 'tag', 3, 'hr'); var fragment = $.comment(); var node = $.first_child(fragment); diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js index 4a766f7de5..4426ad1164 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Svelte_element($$payload, $$props) { let { tag = 'hr' } = $$props; diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js index 8ba14526aa..d520d1ef24 100644 --- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js @@ -1,5 +1,5 @@ -import "svelte/internal/disclose-version"; -import * as $ from "svelte/internal/client"; +import 'svelte/internal/disclose-version'; +import * as $ from 'svelte/internal/client'; var root = $.template(`

            `); @@ -16,11 +16,9 @@ export default function Text_nodes_deriveds($$anchor) { } var p = root(); - const stringified_text = $.derived(() => text1() ?? ""); - const stringified_text_1 = $.derived(() => text2() ?? ""); var text = $.child(p); - $.template_effect(() => $.set_text(text, `${$.get(stringified_text)}${$.get(stringified_text_1)}`)); $.reset(p); + $.template_effect(($0, $1) => $.set_text(text, `${$0 ?? ''}${$1 ?? ''}`), [text1, text2]); $.append($$anchor, p); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js index f7dc4176e4..6f019647f5 100644 --- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ -import * as $ from "svelte/internal/server"; +import * as $ from 'svelte/internal/server'; export default function Text_nodes_deriveds($$payload) { let count1 = 0; diff --git a/packages/svelte/tests/snapshot/test.ts b/packages/svelte/tests/snapshot/test.ts index 88cf9193c3..0a591c6e2a 100644 --- a/packages/svelte/tests/snapshot/test.ts +++ b/packages/svelte/tests/snapshot/test.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs'; import { assert, expect } from 'vitest'; -import glob from 'tiny-glob/sync.js'; +import { globSync } from 'tinyglobby'; import { compile_directory } from '../helpers.js'; import { suite, type BaseTest } from '../suite.js'; import { VERSION } from 'svelte/compiler'; @@ -18,8 +18,8 @@ const { test, run } = suite(async (config, cwd) => { fs.rmSync(`${cwd}/_expected`, { recursive: true, force: true }); fs.cpSync(`${cwd}/_output`, `${cwd}/_expected`, { recursive: true, force: true }); } else { - const actual = glob('**', { cwd: `${cwd}/_output`, filesOnly: true }); - const expected = glob('**', { cwd: `${cwd}/_expected`, filesOnly: true }); + const actual = globSync('**', { cwd: `${cwd}/_output`, onlyFiles: true }); + const expected = globSync('**', { cwd: `${cwd}/_expected`, onlyFiles: true }); assert.deepEqual(actual, expected); diff --git a/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js b/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js index 74a2185076..c2b2fe766a 100644 --- a/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js +++ b/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js @@ -30,7 +30,7 @@ export default test({ async test({ assert, code_client }) { // Check that the css source map embedded in the js is accurate const match = code_client.match( - /code: "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"/ + /code: '(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?'/ ); assert.ok(match); diff --git a/packages/svelte/tests/store/test.ts b/packages/svelte/tests/store/test.ts index b23ea195d6..77cecca7e5 100644 --- a/packages/svelte/tests/store/test.ts +++ b/packages/svelte/tests/store/test.ts @@ -602,7 +602,7 @@ describe('toStore', () => { assert.deepEqual(log, [0]); set(count, 1); - $.flush_sync(); + $.flushSync(); assert.deepEqual(log, [0, 1]); unsubscribe(); @@ -625,7 +625,7 @@ describe('toStore', () => { assert.deepEqual(log, [0]); set(count, 1); - $.flush_sync(); + $.flushSync(); assert.deepEqual(log, [0, 1]); store.set(2); @@ -654,11 +654,11 @@ describe('fromStore', () => { assert.deepEqual(log, [0]); store.set(1); - $.flush_sync(); + $.flushSync(); assert.deepEqual(log, [0, 1]); count.current = 2; - $.flush_sync(); + $.flushSync(); assert.deepEqual(log, [0, 1, 2]); assert.equal(get(store), 2); diff --git a/packages/svelte/tests/types/bindings.svelte b/packages/svelte/tests/types/bindings.svelte new file mode 100644 index 0000000000..ce99b2c296 --- /dev/null +++ b/packages/svelte/tests/types/bindings.svelte @@ -0,0 +1,8 @@ + + + + + +
            diff --git a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json index a1628842fb..0a759da957 100644 --- a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y_click_events_have_key_events", - "message": "Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as ` + {}}>alert

            {}}>Heading

            Heading

            diff --git a/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-interactions/warnings.json b/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-interactions/warnings.json index 760866d136..e8bcd0cc1a 100644 --- a/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-interactions/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-interactions/warnings.json @@ -3,60 +3,60 @@ "code": "a11y_no_noninteractive_element_interactions", "end": { "column": 51, - "line": 10 + "line": 11 }, "message": "Non-interactive element `
            ` should not be assigned mouse or keyboard event listeners", "start": { "column": 0, - "line": 10 + "line": 11 } }, { "code": "a11y_no_noninteractive_element_interactions", "end": { "column": 58, - "line": 11 + "line": 12 }, "message": "Non-interactive element `

            ` should not be assigned mouse or keyboard event listeners", "start": { "column": 0, - "line": 11 + "line": 12 } }, { "code": "a11y_no_noninteractive_element_interactions", "end": { "column": 50, - "line": 12 + "line": 13 }, "message": "Non-interactive element `

            ` should not be assigned mouse or keyboard event listeners", "start": { "column": 0, - "line": 12 + "line": 13 } }, { "code": "a11y_no_noninteractive_element_interactions", "end": { "column": 30, - "line": 13 + "line": 14 }, "message": "Non-interactive element `

            ` should not be assigned mouse or keyboard event listeners", "start": { "column": 0, - "line": 13 + "line": 14 } }, { "code": "a11y_no_noninteractive_element_interactions", "end": { "column": 50, - "line": 14 + "line": 15 }, "message": "Non-interactive element `

            ` should not be assigned mouse or keyboard event listeners", "start": { "column": 0, - "line": 14 + "line": 15 } } ] diff --git a/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/errors.json b/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/errors.json new file mode 100644 index 0000000000..15e762419f --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "bind_group_invalid_snippet_parameter", + "end": { + "column": 44, + "line": 2 + }, + "message": "Cannot `bind:group` to a snippet parameter", + "start": { + "column": 21, + "line": 2 + } + } +] diff --git a/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/input.svelte b/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/input.svelte new file mode 100644 index 0000000000..368484788a --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind-group-snippet-parameter/input.svelte @@ -0,0 +1,3 @@ +{#snippet test(group)} + +{/snippet} \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json new file mode 100644 index 0000000000..f85363106b --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "bind_group_invalid_expression", + "message": "`bind:group` can only bind to an Identifier or MemberExpression", + "start": { + "line": 8, + "column": 38 + }, + "end": { + "line": 8, + "column": 84 + } + } +] diff --git a/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte new file mode 100644 index 0000000000..3f8afe7655 --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte @@ -0,0 +1,12 @@ + + +{#each values as value} + +{/each} + +

            {selected.name}

            diff --git a/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json b/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte b/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte new file mode 100644 index 0000000000..50200a9eac --- /dev/null +++ b/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte @@ -0,0 +1,10 @@ + + + value, + (v) => value = v.toLowerCase() +} +/> diff --git a/packages/svelte/tests/validator/samples/const-tag-inside-key-block/errors.json b/packages/svelte/tests/validator/samples/const-tag-inside-key-block/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-inside-key-block/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/const-tag-inside-key-block/input.svelte b/packages/svelte/tests/validator/samples/const-tag-inside-key-block/input.svelte new file mode 100644 index 0000000000..008072bc47 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-inside-key-block/input.svelte @@ -0,0 +1,3 @@ +{#key 'key'} + {@const foo = 'bar'} +{/key} diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json new file mode 100644 index 0000000000..32594e4268 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "state_invalid_placement", + "message": "`$derived(...)` can only be used as a variable declaration initializer or a class field", + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 26 + } + } +] diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte new file mode 100644 index 0000000000..a056058cc5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte @@ -0,0 +1,3 @@ +{#snippet test()} + {@const der = $derived(0)} +{/snippet} \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json index 144345527a..514e5d0561 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-1/errors.json @@ -1,7 +1,7 @@ [ { "code": "const_tag_invalid_placement", - "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or ``", + "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, ``", "start": { "line": 5, "column": 0 diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json index 64a24c5f48..6b968f7eda 100644 --- a/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-placement-2/errors.json @@ -1,7 +1,7 @@ [ { "code": "const_tag_invalid_placement", - "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or ``", + "message": "`{@const}` must be the immediate child of `{#snippet}`, `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, ``, ``", "start": { "line": 7, "column": 4 diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte new file mode 100644 index 0000000000..5708cc36ca --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-placement-svelte-boundary/input.svelte @@ -0,0 +1,11 @@ + + + + {@const x = a} + {#snippet failed()} + {x} + {/snippet} + + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte new file mode 100644 index 0000000000..df50ebc1fa --- /dev/null +++ b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/validator/samples/invalid-node-placement-template/errors.json b/packages/svelte/tests/validator/samples/invalid-node-placement-template/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/invalid-node-placement-template/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/invalid-node-placement-template/input.svelte b/packages/svelte/tests/validator/samples/invalid-node-placement-template/input.svelte new file mode 100644 index 0000000000..65aef2a7aa --- /dev/null +++ b/packages/svelte/tests/validator/samples/invalid-node-placement-template/input.svelte @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/multiple-var-same-name/errors.json b/packages/svelte/tests/validator/samples/multiple-var-same-name/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/multiple-var-same-name/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/multiple-var-same-name/input.svelte b/packages/svelte/tests/validator/samples/multiple-var-same-name/input.svelte new file mode 100644 index 0000000000..19a8ef7722 --- /dev/null +++ b/packages/svelte/tests/validator/samples/multiple-var-same-name/input.svelte @@ -0,0 +1,6 @@ + + +{test} \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte new file mode 100644 index 0000000000..0a3df4bc1b --- /dev/null +++ b/packages/svelte/tests/validator/samples/mutate-derived-private-field/input.svelte @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/input.svelte new file mode 100644 index 0000000000..92f3564920 --- /dev/null +++ b/packages/svelte/tests/validator/samples/reassign-derived-private-public-field/input.svelte @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json new file mode 100644 index 0000000000..be59da95fa --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 35, + "line": 3 + }, + "message": "`$derived.by` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte new file mode 100644 index 0000000000..49e8057aa5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json new file mode 100644 index 0000000000..6a333bc362 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 32, + "line": 3 + }, + "message": "`$derived` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte new file mode 100644 index 0000000000..9155493e17 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json new file mode 100644 index 0000000000..e08b498fcb --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 34, + "line": 3 + }, + "message": "`$state.raw` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte new file mode 100644 index 0000000000..d06fb053b3 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json new file mode 100644 index 0000000000..11ae2abce5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 30, + "line": 3 + }, + "message": "`$state` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte new file mode 100644 index 0000000000..02feac893f --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte index 5acb14e409..5d559e614e 100644 --- a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte +++ b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte @@ -1,2 +1,8 @@ valid valid + + {#if true} + valid + valid + {/if} + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/slot-warning/warnings.json b/packages/svelte/tests/validator/samples/slot-warning/warnings.json index a390d1d348..c146a7d716 100644 --- a/packages/svelte/tests/validator/samples/slot-warning/warnings.json +++ b/packages/svelte/tests/validator/samples/slot-warning/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y_click_events_have_key_events", - "message": "Visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as `
            - -
            - -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Input/Migrate.svelte b/sites/svelte-5-preview/src/lib/Input/Migrate.svelte deleted file mode 100644 index e268f3838e..0000000000 --- a/sites/svelte-5-preview/src/lib/Input/Migrate.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
            - -
            - - diff --git a/sites/svelte-5-preview/src/lib/Input/ModuleEditor.svelte b/sites/svelte-5-preview/src/lib/Input/ModuleEditor.svelte deleted file mode 100644 index d047d10637..0000000000 --- a/sites/svelte-5-preview/src/lib/Input/ModuleEditor.svelte +++ /dev/null @@ -1,90 +0,0 @@ - - -
            -
            - { - if (error) { - return [ - { - severity: 'error', - from: error.position[0], - to: error.position[1], - message: error.message, - renderMessage: () => { - // TODO expose error codes, so we can link to docs in future - const span = document.createElement('span'); - span.innerHTML = `${error.message - .replace(/&/g, '&') - .replace(/$1`)}`; - return span; - } - } - ]; - } - - if (warnings) { - return warnings.map((warning) => ({ - severity: 'warning', - from: warning.start.character, - to: warning.end.character, - message: warning.message, - renderMessage: () => { - const span = document.createElement('span'); - span.innerHTML = `${warning.message - .replace(/&/g, '&') - .replace(/$1`)} (${warning.code})`; - return span; - } - })); - } - - return []; - }} - on:change={handle_change} - /> -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Input/RunesInfo.svelte b/sites/svelte-5-preview/src/lib/Input/RunesInfo.svelte deleted file mode 100644 index 7c31ce8c2c..0000000000 --- a/sites/svelte-5-preview/src/lib/Input/RunesInfo.svelte +++ /dev/null @@ -1,149 +0,0 @@ - - - { - if (e.key === 'Escape') open = false; - }} -/> - -
            - - - {#if open} - - - - {/if} -
            - - diff --git a/sites/svelte-5-preview/src/lib/InputOutputToggle.svelte b/sites/svelte-5-preview/src/lib/InputOutputToggle.svelte deleted file mode 100644 index db6fd95802..0000000000 --- a/sites/svelte-5-preview/src/lib/InputOutputToggle.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - diff --git a/sites/svelte-5-preview/src/lib/Message.svelte b/sites/svelte-5-preview/src/lib/Message.svelte deleted file mode 100644 index 55412f40eb..0000000000 --- a/sites/svelte-5-preview/src/lib/Message.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - -
            - {#if details} - - {:else} - - {/if} -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/AstNode.svelte b/sites/svelte-5-preview/src/lib/Output/AstNode.svelte deleted file mode 100644 index f99f21494c..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/AstNode.svelte +++ /dev/null @@ -1,161 +0,0 @@ - - -
          1. - {#if !is_root && is_collapsable} - - {:else if key_text} - {key_text} - {/if} - {#if is_collapsable} - {#if collapsed && !is_root} - - {:else} - {is_ast_array ? '[' : '{'} -
              - {#each Object.entries(value) as [k, v]} - - {/each} -
            - {is_ast_array ? ']' : '}'} - {/if} - {:else} - - {JSON.stringify(value)} - - {/if} -
          2. - - diff --git a/sites/svelte-5-preview/src/lib/Output/AstView.svelte b/sites/svelte-5-preview/src/lib/Output/AstView.svelte deleted file mode 100644 index 0db97578c1..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/AstView.svelte +++ /dev/null @@ -1,105 +0,0 @@ - - -
            -
            -		
            -			{#if typeof ast === 'object'}
            -				
              - -
            - {:else} -

            No AST available

            - {/if} -
            -
            - The AST is not public API and may change at any point in time -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/Compiler.js b/sites/svelte-5-preview/src/lib/Output/Compiler.js deleted file mode 100644 index 94484ecf39..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/Compiler.js +++ /dev/null @@ -1,92 +0,0 @@ -import Worker from '../workers/compiler/index.js?worker'; - -const workers = new Map(); - -let uid = 1; - -export default class Compiler { - /** @type {Worker} */ - worker; - - /** @type {Map void>} */ - handlers = new Map(); - - /** @param {string} svelte_url */ - constructor(svelte_url) { - if (!workers.has(svelte_url)) { - const worker = new Worker(); - worker.postMessage({ type: 'init', svelte_url }); - workers.set(svelte_url, worker); - } - - this.worker = workers.get(svelte_url); - - this.worker.addEventListener( - 'message', - /** - * @param {MessageEvent} event - */ - (event) => { - const handler = this.handlers.get(event.data.id); - - if (handler) { - // if no handler, was meant for a different REPL - handler(event.data); - this.handlers.delete(event.data.id); - } - } - ); - } - - /** - * @param {import('$lib/types').File} file - * @param {import('svelte/compiler').CompileOptions} options - * @param {boolean} return_ast - * @returns {Promise} - */ - compile(file, options, return_ast) { - return new Promise((fulfil) => { - const id = uid++; - - this.handlers.set(id, fulfil); - - this.worker.postMessage({ - id, - type: 'compile', - source: file.source, - options: Object.assign( - { - name: file.name, - filename: `${file.name}.${file.type}` - }, - options - ), - entry: file.name === 'App', - return_ast - }); - }); - } - - /** - * @param {import('$lib/types').File} file - * @returns {Promise} - */ - migrate(file) { - return new Promise((fulfil) => { - const id = uid++; - - this.handlers.set(id, fulfil); - - this.worker.postMessage({ - id, - type: 'migrate', - source: file.source, - filename: `${file.name}.${file.type}` - }); - }); - } - - destroy() { - this.worker.terminate(); - } -} diff --git a/sites/svelte-5-preview/src/lib/Output/CompilerOptions.svelte b/sites/svelte-5-preview/src/lib/Output/CompilerOptions.svelte deleted file mode 100644 index 426e5044a7..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/CompilerOptions.svelte +++ /dev/null @@ -1,157 +0,0 @@ - - - -
            - result = svelte.compile(source, { -
            - generate: - - - - - - -
            - -
            - css: - - - - - - -
            - - - - - }); -
            - -
            -

            - note: these options affect the JS output tab, but not the bundle that executes in the Result - tab. -

            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/ErrorOverlay.svelte b/sites/svelte-5-preview/src/lib/Output/ErrorOverlay.svelte deleted file mode 100644 index a0f59985fe..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/ErrorOverlay.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - -
            -
            -

            Error compiling {error.filename ?? 'component'}

            -
            {error.message}
            - - {#if error.start} - line {error.start.line} column {error.start.column} - {/if} -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/Output.svelte b/sites/svelte-5-preview/src/lib/Output/Output.svelte deleted file mode 100644 index ac6da48d3d..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/Output.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - -
            - {#if selected?.type === 'md'} - - {:else} - - - - {#if showAst} - - {/if} - {/if} -
            - - -
            - -
            - - -
            - {#if embedded} - - {:else} - -
            - -
            - -
            - -
            -
            - {/if} -
            - - -
            - -
            - - -{#if showAst && ast} -
            - - {#if $module_editor} - - {/if} -
            -{/if} - - -
            - -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte b/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte deleted file mode 100644 index 9018a50bee..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - - -
            - -
            - -
            -
            - - -
            - -
            - -
            -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/ReplProxy.js b/sites/svelte-5-preview/src/lib/Output/ReplProxy.js deleted file mode 100644 index 0e45887bd7..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/ReplProxy.js +++ /dev/null @@ -1,96 +0,0 @@ -let uid = 1; - -export default class ReplProxy { - /** @type {HTMLIFrameElement} */ - iframe; - - /** @type {import("./proxy").Handlers} */ - handlers; - - /** @type {Map void, reject: (value: any) => void }>} */ - pending_cmds = new Map(); - - /** @param {MessageEvent} event */ - handle_event = (event) => { - if (event.source !== this.iframe.contentWindow) return; - - const { action, args } = event.data; - - switch (action) { - case 'cmd_error': - case 'cmd_ok': - return this.handle_command_message(event.data); - case 'fetch_progress': - return this.handlers.on_fetch_progress(args.remaining); - case 'error': - return this.handlers.on_error(event.data); - case 'unhandledrejection': - return this.handlers.on_unhandled_rejection(event.data); - case 'console': - return this.handlers.on_console(event.data); - } - }; - - /** - * @param {HTMLIFrameElement} iframe - * @param {import("./proxy").Handlers} handlers - */ - constructor(iframe, handlers) { - this.iframe = iframe; - this.handlers = handlers; - - window.addEventListener('message', this.handle_event, false); - } - - destroy() { - window.removeEventListener('message', this.handle_event); - } - - /** - * @param {string} action - * @param {any} args - */ - iframe_command(action, args) { - return new Promise((resolve, reject) => { - const cmd_id = uid++; - - this.pending_cmds.set(cmd_id, { resolve, reject }); - - this.iframe.contentWindow?.postMessage({ action, cmd_id, args }, '*'); - }); - } - - /** - * @param {{ action: string; cmd_id: number; message: string; stack: any; args: any; }} cmd_data - */ - handle_command_message(cmd_data) { - let action = cmd_data.action; - let id = cmd_data.cmd_id; - let handler = this.pending_cmds.get(id); - - if (handler) { - this.pending_cmds.delete(id); - if (action === 'cmd_error') { - let { message, stack } = cmd_data; - let e = new Error(message); - e.stack = stack; - handler.reject(e); - } - - if (action === 'cmd_ok') { - handler.resolve(cmd_data.args); - } - } else { - console.error('command not found', id, cmd_data, [...this.pending_cmds.keys()]); - } - } - - /** @param {string} script */ - eval(script) { - return this.iframe_command('eval', { script }); - } - - handle_links() { - return this.iframe_command('catch_clicks', {}); - } -} diff --git a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte deleted file mode 100644 index db506ada3b..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte +++ /dev/null @@ -1,337 +0,0 @@ - - -
            - -
            - - - {#if $bundle?.error} - - {/if} -
            - -
            - -
            - -
            - -
            -
            - -
            - {#if error} - - {:else if status || !$bundle} - {status || 'loading Svelte compiler...'} - {/if} -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/console/Console.svelte b/sites/svelte-5-preview/src/lib/Output/console/Console.svelte deleted file mode 100644 index e2a880aff8..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/Console.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -
            - {#each logs as log} - - {/each} -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte b/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte deleted file mode 100644 index 36635c7130..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte +++ /dev/null @@ -1,292 +0,0 @@ - - -{#if log.command === 'table'} - -{/if} - -
            - -
            - {#if log.count && log.count > 1} - {log.count} - {/if} - - {#if log.stack || log.command === 'group'} - {'\u25B6'} - {/if} - - {#if log.command === 'clear'} - Console was cleared - {:else if log.command === 'unclonable'} - Message could not be cloned. Open devtools to see it - {:else if log.command === 'table'} - - {:else} - - {#each format_args(log.args) as part} - - {#if !part.formatted} - {' '} - {/if}{#if part.type === 'value'} - - {:else} - {part.value} - {/if} - {/each} - - {/if} -
            - - {#if log.stack && !log.collapsed} -
            - {#each log.stack as line} - {line.label} - {line.location} - {/each} -
            - {/if} - - {#each new Array(depth) as _, idx} -
            - {/each} -
            - -{#if log.command === 'group' && !log.collapsed} - {#each log.logs ?? [] as childLog} - - {/each} -{/if} - - diff --git a/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte b/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte deleted file mode 100644 index cba9721807..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte +++ /dev/null @@ -1,145 +0,0 @@ - - -
            - - - - - - {#each table.columns as column} - - {/each} - - - - - {#each table.rows as row} - - - - {#each row.values as value} - - {/each} - - {/each} - -
            (index){column}
            - {#if typeof row.key === 'string'} - {row.key} - {:else} - - {/if} - - {#if typeof value === 'string'} - {value} - {:else} - - {/if} -
            -
            - - diff --git a/sites/svelte-5-preview/src/lib/Output/console/console.d.ts b/sites/svelte-5-preview/src/lib/Output/console/console.d.ts deleted file mode 100644 index 540e0b3b02..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/console.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type Log = { - command: 'info' | 'warn' | 'error' | 'table' | 'group' | 'clear' | 'unclonable'; - action?: 'console'; - args?: any[]; - collapsed?: boolean; - expanded?: boolean; - count?: number; - logs?: Log[]; - stack?: Array<{ - label?: string; - location?: string; - }>; - data?: any; - columns?: string[]; -}; diff --git a/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js b/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js deleted file mode 100644 index 3ac3e457d6..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js +++ /dev/null @@ -1,42 +0,0 @@ -import { decode } from '@jridgewell/sourcemap-codec'; - -/** - * @param {string} stack - * @param {import('@jridgewell/sourcemap-codec').SourceMapMappings} map - * @returns - */ -export default function getLocationFromStack(stack, map) { - if (!stack) return; - const last = stack.split('\n')[1]; - const match = /:(\d+):(\d+)\)$/.exec(last); - - if (!match) return null; - - const line = +match[1]; - const column = +match[2]; - - return trace({ line, column }, map); -} - -/** - * - * @param {Omit} loc - * @param {*} map - * @returns - */ -function trace(loc, map) { - const mappings = decode(map.mappings); - const segments = mappings[loc.line - 1]; - - for (let i = 0; i < segments.length; i += 1) { - const segment = segments[i]; - if (segment[0] === loc.column) { - const [, sourceIndex, line, column] = segment; - const source = map.sources[sourceIndex ?? 0].slice(2); - - return { source, line: (line ?? 0) + 1, column }; - } - } - - return null; -} diff --git a/sites/svelte-5-preview/src/lib/Output/proxy.d.ts b/sites/svelte-5-preview/src/lib/Output/proxy.d.ts deleted file mode 100644 index b3f9fa8d1a..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/proxy.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Handlers = Record< - 'on_fetch_progress' | 'on_error' | 'on_unhandled_rejection' | 'on_console', - (data: any) => void ->; diff --git a/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html b/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html deleted file mode 100644 index 202a5f973a..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - diff --git a/sites/svelte-5-preview/src/lib/Repl.svelte b/sites/svelte-5-preview/src/lib/Repl.svelte deleted file mode 100644 index f43f5f899b..0000000000 --- a/sites/svelte-5-preview/src/lib/Repl.svelte +++ /dev/null @@ -1,438 +0,0 @@ - - - - -
            -
            - -
            - - -
            - -
            - -
            -
            -
            - - {#if $toggleable} - - {/if} -
            - - diff --git a/sites/svelte-5-preview/src/lib/autocomplete.js b/sites/svelte-5-preview/src/lib/autocomplete.js deleted file mode 100644 index b535c7ff04..0000000000 --- a/sites/svelte-5-preview/src/lib/autocomplete.js +++ /dev/null @@ -1,209 +0,0 @@ -import { snippetCompletion } from '@codemirror/autocomplete'; -import { syntaxTree } from '@codemirror/language'; - -/** @typedef {(node: import('@lezer/common').SyntaxNode, context: import('@codemirror/autocomplete').CompletionContext, selected: import('./types').File) => boolean} Test */ - -/** - * Returns `true` if `$bindable()` is valid - * @type {Test} - */ -function is_bindable(node, context) { - // disallow outside `let { x = $bindable }` - if (node.parent?.name !== 'PatternProperty') return false; - if (node.parent.parent?.name !== 'ObjectPattern') return false; - if (node.parent.parent.parent?.name !== 'VariableDeclaration') return false; - - let last = node.parent.parent.parent.lastChild; - if (!last) return true; - - // if the declaration is incomplete, assume the best - if (last.name === 'ObjectPattern' || last.name === 'Equals' || last.name === '⚠') { - return true; - } - - if (last.name === ';') { - last = last.prevSibling; - if (!last || last.name === '⚠') return true; - } - - // if the declaration is complete, only return true if it is a `$props()` declaration - return ( - last.name === 'CallExpression' && - last.firstChild?.name === 'VariableName' && - context.state.sliceDoc(last.firstChild.from, last.firstChild.to) === '$props' - ); -} - -/** - * Returns `true` if `$props()` is valid - * TODO only allow in `.svelte` files, and only at the top level - * @type {Test} - */ -function is_props(node, _, selected) { - if (selected.type !== 'svelte') return false; - - return ( - node.name === 'VariableName' && - node.parent?.name === 'VariableDeclaration' && - node.parent.parent?.name === 'Script' - ); -} - -/** - * Returns `true` is this is a valid place to declare state - * @type {Test} - */ -function is_state(node) { - let parent = node.parent; - - if (node.name === '.' || node.name === 'PropertyName') { - if (parent?.name !== 'MemberExpression') return false; - parent = parent.parent; - } - - if (!parent) return false; - - return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} - -/** - * Returns `true` if we're already in a valid call expression, e.g. - * changing an existing `$state()` to `$state.raw()` - * @type {Test} - */ -function is_state_call(node) { - let parent = node.parent; - - if (node.name === '.' || node.name === 'PropertyName') { - if (parent?.name !== 'MemberExpression') return false; - parent = parent.parent; - } - - if (parent?.name !== 'CallExpression') { - return false; - } - - parent = parent.parent; - if (!parent) return false; - - return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} - -/** @type {Test} */ -function is_statement(node) { - if (node.name === 'VariableName') { - return node.parent?.name === 'ExpressionStatement'; - } - - if (node.name === '.' || node.name === 'PropertyName') { - return node.parent?.parent?.name === 'ExpressionStatement'; - } - - return false; -} - -/** @type {Array<{ snippet: string, test?: Test }>} */ -const runes = [ - { snippet: '$state(${})', test: is_state }, - { snippet: '$state', test: is_state_call }, - { snippet: '$props()', test: is_props }, - { snippet: '$derived(${});', test: is_state }, - { snippet: '$derived', test: is_state_call }, - { snippet: '$derived.by(() => {\n\t${}\n});', test: is_state }, - { snippet: '$derived.by', test: is_state_call }, - { snippet: '$effect(() => {\n\t${}\n});', test: is_statement }, - { snippet: '$effect.pre(() => {\n\t${}\n});', test: is_statement }, - { snippet: '$state.raw(${});', test: is_state }, - { snippet: '$state.raw', test: is_state_call }, - { snippet: '$bindable()', test: is_bindable }, - { snippet: '$effect.root(() => {\n\t${}\n})' }, - { snippet: '$state.snapshot(${})' }, - { snippet: '$effect.tracking()' }, - { snippet: '$inspect(${});', test: is_statement } -]; - -const options = runes.map(({ snippet, test }, i) => ({ - option: snippetCompletion(snippet, { - type: 'keyword', - boost: runes.length - i, - label: snippet.includes('(') ? snippet.slice(0, snippet.indexOf('(')) : snippet - }), - test -})); - -/** - * @param {import('@codemirror/autocomplete').CompletionContext} context - * @param {import('./types.js').File} selected - * @param {import('./types.js').File[]} files - */ -export function autocomplete(context, selected, files) { - let node = syntaxTree(context.state).resolveInner(context.pos, -1); - - if (node.name === 'String' && node.parent?.name === 'ImportDeclaration') { - const modules = [ - 'svelte', - 'svelte/animate', - 'svelte/easing', - 'svelte/events', - 'svelte/legacy', - 'svelte/motion', - 'svelte/reactivity', - 'svelte/store', - 'svelte/transition' - ]; - - for (const file of files) { - if (file === selected) continue; - modules.push(`./${file.name}.${file.type}`); - } - - return { - from: node.from + 1, - options: modules.map((label) => ({ - label, - type: 'string' - })) - }; - } - - if ( - selected.type !== 'svelte' && - (selected.type !== 'js' || !selected.name.endsWith('.svelte')) - ) { - return false; - } - - if (node.name === 'VariableName' || node.name === 'PropertyName' || node.name === '.') { - // special case — `$inspect(...).with(...)` is the only rune that 'returns' - // an 'object' with a 'method' - if (node.name === 'PropertyName' || node.name === '.') { - if ( - node.parent?.name === 'MemberExpression' && - node.parent.firstChild?.name === 'CallExpression' && - node.parent.firstChild.firstChild?.name === 'VariableName' && - context.state.sliceDoc( - node.parent.firstChild.firstChild.from, - node.parent.firstChild.firstChild.to - ) === '$inspect' - ) { - const open = context.matchBefore(/\.\w*/); - if (!open) return null; - - return { - from: open.from, - options: [snippetCompletion('.with(${})', { type: 'keyword', label: '.with' })] - }; - } - } - - const open = context.matchBefore(/\$[\w\.]*/); - if (!open) return null; - - return { - from: open.from, - options: options - .filter((option) => (option.test ? option.test(node, context, selected) : true)) - .map((option) => option.option) - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/context.js b/sites/svelte-5-preview/src/lib/context.js deleted file mode 100644 index e983e6d147..0000000000 --- a/sites/svelte-5-preview/src/lib/context.js +++ /dev/null @@ -1,13 +0,0 @@ -import { getContext, setContext } from 'svelte'; - -const key = Symbol('repl'); - -/** @returns {import("./types").ReplContext} */ -export function get_repl_context() { - return getContext(key); -} - -/** @param {import("./types").ReplContext} value */ -export function set_repl_context(value) { - setContext(key, value); -} diff --git a/sites/svelte-5-preview/src/lib/index.js b/sites/svelte-5-preview/src/lib/index.js deleted file mode 100644 index 969b641408..0000000000 --- a/sites/svelte-5-preview/src/lib/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Repl.svelte'; diff --git a/sites/svelte-5-preview/src/lib/theme.js b/sites/svelte-5-preview/src/lib/theme.js deleted file mode 100644 index 867e144acc..0000000000 --- a/sites/svelte-5-preview/src/lib/theme.js +++ /dev/null @@ -1,153 +0,0 @@ -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { EditorView } from '@codemirror/view'; -import { tags as t } from '@lezer/highlight'; - -const ERROR_HUE = 0; -const WARNING_HUE = 40; - -const WARNING_FG = `hsl(${WARNING_HUE} 100% 60%)`; -const WARNING_BG = `hsl(${WARNING_HUE} 100% 40% / 0.5)`; - -const ERROR_FG = `hsl(${ERROR_HUE} 100% 40%)`; -const ERROR_BG = `hsl(${ERROR_HUE} 100% 40% / 0.5)`; - -/** - * @param {string} content - * @param {string} attrs - */ -function svg(content, attrs = `viewBox="0 0 40 40"`) { - return `url('data:image/svg+xml,${encodeURIComponent( - content - )}')`; -} - -/** - * @param {string} color - */ -function underline(color) { - return svg( - ``, - `width="6" height="4"` - ); -} - -const svelteThemeStyles = EditorView.theme( - { - '&': { - color: 'var(--sk-code-base)', - backgroundColor: 'transparent' - }, - - '.cm-content': { - caretColor: 'var(--sk-theme-3)' - }, - - '.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--sk-theme-3)' }, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': - { backgroundColor: 'var(--sk-selection-color)' }, - - '.cm-panels': { backgroundColor: 'var(--sk-back-2)', color: 'var(--sk-text-2)' }, - '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, - '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, - - '.cm-searchMatch': { - backgroundColor: 'var(--sk-theme-2)' - // outline: '1px solid #457dff', - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: '#6199ff2f' - }, - - '.cm-activeLine': { backgroundColor: '#6699ff0b' }, - '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: '#bad0f847' - }, - - '.cm-gutters': { - backgroundColor: 'var(--sk-back-3)', - border: 'none' - }, - - '.cm-activeLineGutter': { - backgroundColor: 'var(--sk-back-4)' - }, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: '#ddd' - }, - - // https://github.com/codemirror/lint/blob/271b35f5d31a7e3645eaccbfec608474022098e1/src/lint.ts#L620 - '.cm-lintRange': { - backgroundPosition: 'left bottom', - backgroundRepeat: 'repeat-x', - paddingBottom: '4px' - }, - '.cm-lintRange-error': { - backgroundImage: underline(ERROR_FG) - }, - '.cm-lintRange-warning': { - backgroundImage: underline(WARNING_FG) - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: 'var(--sk-back-3)', - borderBottomColor: 'var(--sk-back-3)' - }, - '.cm-tooltip-autocomplete': { - color: 'var(--sk-text-2) !important', - perspective: '1px', - '& > ul > li[aria-selected]': { - backgroundColor: 'var(--sk-back-4)', - color: 'var(--sk-text-1) !important' - } - } - }, - { dark: true } -); - -/// The highlighting style for code in the One Dark theme. -const svelteHighlightStyle = HighlightStyle.define([ - { tag: t.keyword, color: 'var(--sk-code-keyword)' }, - { - tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], - color: 'var(--sk-code-base)' - }, - { tag: [t.function(t.variableName), t.labelName], color: 'var(--sk-code-tags)' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: 'var(--sk-code-base)' }, - { tag: [t.definition(t.name), t.separator], color: 'var(--sk-code-base)' }, - { - tag: [ - t.typeName, - t.className, - t.number, - t.changed, - t.annotation, - t.modifier, - t.self, - t.namespace - ], - color: 'var(--sk-code-tags)' - }, - { - tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], - color: 'var(--sk-code-base)' - }, - { tag: [t.meta, t.comment], color: 'var(--sk-code-comment)' }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.link, color: 'var(--sk-code-base)', textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: 'var(--sk-text-1)' }, - { tag: [t.atom, t.bool], color: 'var(--sk-code-atom)' }, - { tag: [t.processingInstruction, t.string, t.inserted], color: 'var(--sk-code-string)' }, - { tag: t.invalid, color: '#ff008c' } -]); - -export const svelteTheme = [svelteThemeStyles, syntaxHighlighting(svelteHighlightStyle)]; diff --git a/sites/svelte-5-preview/src/lib/types.d.ts b/sites/svelte-5-preview/src/lib/types.d.ts deleted file mode 100644 index a758846d29..0000000000 --- a/sites/svelte-5-preview/src/lib/types.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { EditorState } from '@codemirror/state'; -import { OutputChunk, RollupError } from '@rollup/browser'; -import type { Readable, Writable } from 'svelte/store'; -import type { CompileOptions, CompileError } from 'svelte/compiler'; - -export type Lang = 'js' | 'svelte' | 'json' | 'md' | 'css' | (string & Record); - -type StartOrEnd = { - line: number; - column: number; - character: number; -}; - -export type MessageDetails = { - start: StartOrEnd; - end: StartOrEnd; - filename: string; - message: string; -}; - -export type Warning = MessageDetails; - -export type Bundle = { - uid: number; - client: OutputChunk | null; - error: (RollupError & CompileError) | null; - server: OutputChunk | null; - imports: string[]; - warnings: Warning[]; -}; - -export type File = { - name: string; - source: string; - type: Lang; - modified?: boolean; -}; - -export type ReplState = { - files: File[]; - selected_name: string; - selected: File | null; - bundle: Bundle | null; - bundling: Promise; - bundler: import('./Bundler').default | null; - compile_options: CompileOptions; - cursor_pos: number; - toggleable: boolean; - module_editor: import('./CodeMirror.svelte').default | null; -}; - -export type ReplContext = { - files: Writable; - selected_name: Writable; - selected: Readable; - bundle: Writable; - bundling: Writable; - bundler: Writable; - compile_options: Writable; - cursor_pos: Writable; - toggleable: Writable; - module_editor: Writable; - - EDITOR_STATE_MAP: Map; - - // Methods - rebundle(): Promise; - migrate(): Promise; - handle_select(filename: string): Promise; - handle_change( - event: CustomEvent<{ - value: string; - }> - ): Promise; - go_to_warning_pos(item?: MessageDetails): Promise; - clear_state(): void; -}; diff --git a/sites/svelte-5-preview/src/lib/utils.js b/sites/svelte-5-preview/src/lib/utils.js deleted file mode 100644 index d378e1d517..0000000000 --- a/sites/svelte-5-preview/src/lib/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @param {number} min - * @param {number} max - * @param {number} value - */ -export const clamp = (min, max, value) => Math.max(min, Math.min(max, value)); - -/** - * @param {number} ms - */ -export const sleep = (ms) => new Promise((f) => setTimeout(f, ms)); - -/** @param {import('./types').File} file */ -export function get_full_filename(file) { - return `${file.name}.${file.type}`; -} diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/index.js b/sites/svelte-5-preview/src/lib/workers/bundler/index.js deleted file mode 100644 index 5a289fff7d..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/index.js +++ /dev/null @@ -1,576 +0,0 @@ -/// - -import '../patch_window.js'; -import { sleep } from '$lib/utils.js'; -import { rollup } from '@rollup/browser'; -import { DEV } from 'esm-env'; -import * as resolve from 'resolve.exports'; -import commonjs from './plugins/commonjs.js'; -import glsl from './plugins/glsl.js'; -import json from './plugins/json.js'; -import replace from './plugins/replace.js'; -import loop_protect from './plugins/loop-protect.js'; - -/** @type {string} */ -var pkg_name; - -/** @type {string} */ -let packages_url; - -/** @type {string} */ -let svelte_url; - -/** @type {number} */ -let current_id; - -/** @type {(arg?: never) => void} */ -let fulfil_ready; -const ready = new Promise((f) => { - fulfil_ready = f; -}); - -/** - * @type {{ - * compile: typeof import('svelte/compiler').compile; - * compileModule: typeof import('svelte/compiler').compileModule; - * VERSION: string; - * }} - */ -let svelte; - -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ async (event) => { - switch (event.data.type) { - case 'init': { - ({ packages_url, svelte_url } = event.data); - - const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json()); - console.log(`Using Svelte compiler version ${version}`); - - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - - svelte = globalThis.svelte; - - fulfil_ready(); - break; - } - - case 'bundle': { - await ready; - const { uid, files } = event.data; - - if (files.length === 0) return; - - current_id = uid; - - setTimeout(async () => { - if (current_id !== uid) return; - - const result = await bundle({ uid, files }); - - if (JSON.stringify(result.error) === JSON.stringify(ABORT)) return; - if (result && uid === current_id) postMessage(result); - }); - - break; - } - } - } -); - -/** @type {Record<'client' | 'server', Map }>>} */ -let cached = { - client: new Map(), - server: new Map() -}; - -const ABORT = { aborted: true }; - -/** @type {Map>} */ -const FETCH_CACHE = new Map(); - -/** - * @param {string} url - * @param {number} uid - */ -async function fetch_if_uncached(url, uid) { - if (FETCH_CACHE.has(url)) { - return FETCH_CACHE.get(url); - } - - // TODO: investigate whether this is necessary - await sleep(50); - if (uid !== current_id) throw ABORT; - - const promise = fetch(url) - .then(async (r) => { - if (!r.ok) throw new Error(await r.text()); - - return { - url: r.url, - body: await r.text() - }; - }) - .catch((err) => { - FETCH_CACHE.delete(url); - throw err; - }); - - FETCH_CACHE.set(url, promise); - return promise; -} - -/** - * @param {string} url - * @param {number} uid - */ -async function follow_redirects(url, uid) { - const res = await fetch_if_uncached(url, uid); - return res?.url; -} - -/** - * - * @param {number} major - * @param {number} minor - * @param {number} patch - * @returns {number} - */ -function compare_to_version(major, minor, patch) { - const v = svelte.VERSION.match(/^(\d+)\.(\d+)\.(\d+)/); - - // @ts-ignore - return +v[1] - major || +v[2] - minor || +v[3] - patch; -} - -function is_v4() { - return compare_to_version(4, 0, 0) >= 0; -} - -function is_v5() { - return compare_to_version(5, 0, 0) >= 0; -} - -function is_legacy_package_structure() { - return compare_to_version(3, 4, 4) <= 0; -} - -function has_loopGuardTimeout_feature() { - return compare_to_version(3, 14, 0) >= 0; -} - -/** - * - * @param {Record} pkg - * @param {string} subpath - * @param {number} uid - * @param {string} pkg_url_base - */ -async function resolve_from_pkg(pkg, subpath, uid, pkg_url_base) { - // match legacy Rollup logic — pkg.svelte takes priority over pkg.exports - if (typeof pkg.svelte === 'string' && subpath === '.') { - return pkg.svelte; - } - - // modern - if (pkg.exports) { - try { - const [resolved] = - resolve.exports(pkg, subpath, { - browser: true, - conditions: ['svelte', 'development'] - }) ?? []; - - return resolved; - } catch { - throw `no matched export path was found in "${pkg_name}/package.json"`; - } - } - - // legacy - if (subpath === '.') { - let resolved_id = resolve.legacy(pkg, { - fields: ['browser', 'module', 'main'] - }); - - if (typeof resolved_id === 'object' && !Array.isArray(resolved_id)) { - const subpath = resolved_id['.']; - if (subpath === false) return 'data:text/javascript,export {}'; - - resolved_id = - subpath ?? - resolve.legacy(pkg, { - fields: ['module', 'main'] - }); - } - - if (!resolved_id) { - // last ditch — try to match index.js/index.mjs - for (const index_file of ['index.mjs', 'index.js']) { - try { - const indexUrl = new URL(index_file, `${pkg_url_base}/`).href; - return (await follow_redirects(indexUrl, uid)) ?? ''; - } catch { - // maybe the next option will be successful - } - } - - throw `could not find entry point in "${pkg_name}/package.json"`; - } - - return resolved_id; - } - - if (typeof pkg.browser === 'object') { - // this will either return `pkg.browser[subpath]` or `subpath` - return resolve.legacy(pkg, { - browser: subpath - }); - } - - return subpath; -} - -/** - * @param {number} uid - * @param {'client' | 'server'} mode - * @param {typeof cached['client']} cache - * @param {Map} local_files_lookup - */ -async function get_bundle(uid, mode, cache, local_files_lookup) { - let bundle; - - /** A set of package names (without subpaths) to include in pkg.devDependencies when downloading an app */ - /** @type {Set} */ - const imports = new Set(); - - /** @type {import('$lib/types.js').Warning[]} */ - const warnings = []; - - /** @type {{ message: string }[]} */ - const all_warnings = []; - - /** @type {typeof cache} */ - const new_cache = new Map(); - - /** @type {import('@rollup/browser').Plugin} */ - const repl_plugin = { - name: 'svelte-repl', - async resolveId(importee, importer) { - if (uid !== current_id) throw ABORT; - - if (importee === 'esm-env') return importee; - - const v5 = is_v5(); - const v4 = !v5 && is_v4(); - - if (!v5) { - // importing from Svelte - if (importee === `svelte`) - return v4 ? `${svelte_url}/src/runtime/index.js` : `${svelte_url}/index.mjs`; - - if (importee.startsWith(`svelte/`)) { - const sub_path = importee.slice(7); - if (v4) { - return `${svelte_url}/src/runtime/${sub_path}/index.js`; - } - - return is_legacy_package_structure() - ? `${svelte_url}/${sub_path}.mjs` - : `${svelte_url}/${sub_path}/index.mjs`; - } - } - - // importing from another file in REPL - if (local_files_lookup.has(importee) && (!importer || local_files_lookup.has(importer))) - return importee; - if (local_files_lookup.has(importee + '.js')) return importee + '.js'; - if (local_files_lookup.has(importee + '.json')) return importee + '.json'; - - // remove trailing slash - if (importee.endsWith('/')) importee = importee.slice(0, -1); - - // importing from a URL - if (/^https?:/.test(importee)) return importee; - - if (importee.startsWith('.')) { - if (importer && local_files_lookup.has(importer)) { - // relative import in a REPL file - // should've matched above otherwise importee doesn't exist - console.error(`Cannot find file "${importee}" imported by "${importer}" in the REPL`); - return; - } else { - // relative import in an external file - const url = new URL(importee, importer).href; - self.postMessage({ type: 'status', uid, message: `resolving ${url}` }); - - return await follow_redirects(url, uid); - } - } else { - // fetch from unpkg - self.postMessage({ type: 'status', uid, message: `resolving ${importee}` }); - - const match = /^((?:@[^/]+\/)?[^/]+)(\/.+)?$/.exec(importee); - if (!match) { - return console.error(`Invalid import "${importee}"`); - } - - const pkg_name = match[1]; - const subpath = `.${match[2] ?? ''}`; - - // if this was imported by one of our files, add it to the `imports` set - if (importer && local_files_lookup.has(importer)) { - imports.add(pkg_name); - } - - const fetch_package_info = async () => { - try { - const pkg_url = await follow_redirects( - `${pkg_name === 'svelte' ? '' : packages_url}/${pkg_name}/package.json`, - uid - ); - - if (!pkg_url) throw new Error(); - - const pkg_json = (await fetch_if_uncached(pkg_url, uid))?.body; - const pkg = JSON.parse(pkg_json ?? '""'); - - const pkg_url_base = pkg_url.replace(/\/package\.json$/, ''); - - return { - pkg, - pkg_url_base - }; - } catch (_e) { - throw new Error(`Error fetching "${pkg_name}" from unpkg. Does the package exist?`); - } - }; - - const { pkg, pkg_url_base } = await fetch_package_info(); - - try { - const resolved_id = await resolve_from_pkg(pkg, subpath, uid, pkg_url_base); - return new URL(resolved_id + '', `${pkg_url_base}/`).href; - } catch (reason) { - throw new Error(`Cannot import "${importee}": ${reason}.`); - } - } - }, - async load(resolved) { - if (uid !== current_id) throw ABORT; - - if (resolved === 'esm-env') { - return `export const BROWSER = true; export const DEV = true`; - } - - const cached_file = local_files_lookup.get(resolved); - if (cached_file) return cached_file.source; - - if (!FETCH_CACHE.has(resolved)) { - self.postMessage({ type: 'status', uid, message: `fetching ${resolved}` }); - } - - const res = await fetch_if_uncached(resolved, uid); - return res?.body; - }, - transform(code, id) { - if (uid !== current_id) throw ABORT; - - self.postMessage({ type: 'status', uid, message: `bundling ${id}` }); - - if (!/\.(svelte|js)$/.test(id)) return null; - - const name = id.split('/').pop()?.split('.')[0]; - - const cached_id = cache.get(id); - let result; - - if (cached_id && cached_id.code === code) { - result = cached_id.result; - } else if (id.endsWith('.svelte')) { - result = svelte.compile(code, { - filename: name + '.svelte', - generate: 'client', - dev: true - }); - - if (result.css) { - result.js.code += - '\n\n' + - ` - const $$__style = document.createElement('style'); - $$__style.textContent = ${JSON.stringify(result.css.code)}; - document.head.append($$__style); - `.replace(/\t/g, ''); - } - } else if (id.endsWith('.svelte.js')) { - result = svelte.compileModule(code, { - filename: name + '.js', - generate: 'client', - dev: true - }); - if (!result) { - return null; - } - } else { - return null; - } - - new_cache.set(id, { code, result }); - - // @ts-expect-error - (result.warnings || result.stats?.warnings)?.forEach((warning) => { - // This is required, otherwise postMessage won't work - // @ts-ignore - delete warning.toString; - // TODO remove stats post-launch - // @ts-ignore - warnings.push(warning); - }); - - /** @type {import('@rollup/browser').TransformResult} */ - const transform_result = { - code: result.js.code, - map: result.js.map - }; - - return transform_result; - } - }; - - try { - bundle = await rollup({ - input: './__entry.js', - plugins: [ - repl_plugin, - commonjs, - json, - glsl, - loop_protect, - replace({ - 'process.env.NODE_ENV': JSON.stringify('production') - }) - ], - inlineDynamicImports: true, - onwarn(warning) { - all_warnings.push({ - message: warning.message - }); - } - }); - - return { - bundle, - imports: Array.from(imports), - cache: new_cache, - error: null, - warnings, - all_warnings - }; - } catch (error) { - return { error, imports: null, bundle: null, cache: new_cache, warnings, all_warnings }; - } -} - -/** - * @param {{ uid: number; files: import('$lib/types.js').File[] }} param0 - * @returns - */ -async function bundle({ uid, files }) { - if (!DEV) { - console.clear(); - console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold'); - } - - /** @type {Map} */ - const lookup = new Map(); - - lookup.set('./__entry.js', { - name: '__entry', - source: ` - export { mount, unmount, untrack } from 'svelte'; - export {default as App} from './App.svelte'; - `, - type: 'js', - modified: false - }); - - files.forEach((file) => { - const path = `./${file.name}.${file.type}`; - lookup.set(path, file); - }); - - /** @type {Awaited>} */ - let client = await get_bundle(uid, 'client', cached.client, lookup); - let error; - - try { - if (client.error) { - throw client.error; - } - - cached.client = client.cache; - - const client_result = ( - await client.bundle?.generate({ - format: 'iife', - exports: 'named' - // sourcemap: 'inline' - }) - )?.output[0]; - - const server = false // TODO how can we do SSR? - ? await get_bundle(uid, 'server', cached.server, lookup) - : null; - - if (server) { - cached.server = server.cache; - if (server.error) { - throw server.error; - } - } - - const server_result = server - ? ( - await server.bundle?.generate({ - format: 'iife', - name: 'SvelteComponent', - exports: 'named' - // sourcemap: 'inline' - }) - )?.output?.[0] - : null; - - return { - uid, - client: client_result, - server: server_result, - imports: client.imports, - warnings: client.warnings, - error: null - }; - } catch (err) { - console.error(err); - - /** @type {Error} */ - // @ts-ignore - const e = error || err; - - // @ts-ignore - delete e.toString; - - return { - uid, - client: null, - server: null, - imports: null, - warnings: client.warnings, - error: Object.assign({}, e, { - message: e.message, - stack: e.stack - }) - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js deleted file mode 100644 index 9e0a92dbdd..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js +++ /dev/null @@ -1,58 +0,0 @@ -import { parse } from 'acorn'; -import { walk } from 'zimmerframe'; - -const require = `function require(id) { - if (id in __repl_lookup) return __repl_lookup[id]; - throw new Error(\`Cannot require modules dynamically (\${id})\`); -}`; - -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'commonjs', - - transform: (code, id) => { - if (!/\b(require|module|exports)\b/.test(code)) return; - - try { - const ast = parse(code, { - ecmaVersion: 'latest' - }); - - /** @type {string[]} */ - const requires = []; - - walk(/** @type {import('estree').Node} */ (ast), null, { - CallExpression: (node) => { - if (node.callee.type === 'Identifier' && node.callee.name === 'require') { - if (node.arguments.length !== 1) return; - const arg = node.arguments[0]; - if (arg.type !== 'Literal' || typeof arg.value !== 'string') return; - - requires.push(arg.value); - } - } - }); - - const imports = requires.map((id, i) => `import __repl_${i} from '${id}';`).join('\n'); - const lookup = `const __repl_lookup = { ${requires - .map((id, i) => `'${id}': __repl_${i}`) - .join(', ')} };`; - - const transformed = [ - imports, - lookup, - require, - `const exports = {}; const module = { exports };`, - code, - `export default module.exports;` - ].join('\n\n'); - - return { - code: transformed, - map: null - }; - } catch (err) { - return null; - } - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js deleted file mode 100644 index 51e7e062a4..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'glsl', - transform: (code, id) => { - if (!id.endsWith('.glsl')) return; - - return { - code: `export default ${JSON.stringify(code)};`, - map: null - }; - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js deleted file mode 100644 index 2f79b289e4..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'json', - transform: (code, id) => { - if (!id.endsWith('.json')) return; - - return { - code: `export default ${code};`, - map: null - }; - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js deleted file mode 100644 index 9cb4a8e25e..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js +++ /dev/null @@ -1,111 +0,0 @@ -import { parse } from 'acorn'; -import { print } from 'esrap'; -import { walk } from 'zimmerframe'; - -const TIMEOUT = 100; - -const regex = /\b(for|while)\b/; - -/** - * - * @param {string} code - * @returns {import('estree').Statement} - */ -function parse_statement(code) { - return /** @type {import('estree').Statement} */ (parse(code, { ecmaVersion: 'latest' }).body[0]); -} - -const declaration = parse_statement(` - const __start = Date.now(); -`); - -const check = parse_statement(` - if (Date.now() > __start + ${TIMEOUT}) { - throw new Error('Infinite loop detected'); - } -`); - -/** - * - * @param {import('estree').Node[]} path - * @returns {null | import('estree').FunctionExpression | import('estree').FunctionDeclaration | import('estree').ArrowFunctionExpression} - */ -export function get_current_function(path) { - for (let i = path.length - 1; i >= 0; i--) { - const node = path[i]; - if ( - node.type === 'FunctionDeclaration' || - node.type === 'FunctionExpression' || - node.type === 'ArrowFunctionExpression' - ) { - return node; - } - } - return null; -} - -/** - * @template {import('estree').DoWhileStatement | import('estree').ForStatement | import('estree').WhileStatement} Statement - * @param {Statement} node - * @param {import('zimmerframe').Context} context - * @returns {import('estree').Node | void} - */ -function loop_protect(node, context) { - const current_function = get_current_function(context.path); - - if (current_function === null || (!current_function.async && !current_function.generator)) { - const body = /** @type {import('estree').Statement} */ (context.visit(node.body)); - - const statements = body.type === 'BlockStatement' ? [...body.body] : [body]; - - /** @type {import('estree').BlockStatement} */ - const replacement = { - type: 'BlockStatement', - body: [ - declaration, - { - .../** @type {Statement} */ (context.next() ?? node), - body: { - type: 'BlockStatement', - body: [...statements, check] - } - } - ] - }; - - return replacement; - } - - context.next(); -} - -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'loop-protect', - transform: (code, id) => { - // only applies to local files, not imports - if (!id.startsWith('./')) return; - - // only applies to JS and Svelte files - if (!id.endsWith('.js') && !id.endsWith('.svelte')) return; - - // fast path - if (!regex.test(code)) return; - - const ast = parse(code, { - ecmaVersion: 'latest', - sourceType: 'module' - }); - - const transformed = walk(/** @type {import('estree').Node} */ (ast), null, { - WhileStatement: loop_protect, - DoWhileStatement: loop_protect, - ForStatement: loop_protect - }); - - // nothing changed - if (ast === transformed) return null; - - return print(transformed); - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js deleted file mode 100644 index 6ccdeffed8..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js +++ /dev/null @@ -1,72 +0,0 @@ -/** @param {string} str */ -function escape(str) { - return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); -} - -/** @param {unknown} functionOrValue */ -function ensureFunction(functionOrValue) { - if (typeof functionOrValue === 'function') { - return functionOrValue; - } - return function () { - return functionOrValue; - }; -} - -/** - * @param {string} a - * @param {string} b - */ -function longest(a, b) { - return b.length - a.length; -} - -/** @param {Record} object */ -function mapToFunctions(object) { - return Object.keys(object).reduce( - /** @param {Record} functions */ function (functions, key) { - functions[key] = ensureFunction(object[key]); - return functions; - }, - {} - ); -} - -/** - * @param {Record} options - * @returns {import('@rollup/browser').Plugin} - */ -function replace(options) { - const functionValues = mapToFunctions(options); - const keys = Object.keys(functionValues).sort(longest).map(escape); - - const pattern = new RegExp('\\b(' + keys.join('|') + ')\\b', 'g'); - - return { - name: 'replace', - - transform: function transform(code, id) { - let hasReplacements = false; - let match; - let start; - let end; - let replacement; - - code = code.replace(pattern, (_, key) => { - hasReplacements = true; - return String(functionValues[key](id)); - }); - - if (!hasReplacements) { - return null; - } - - return { - code, - map: null - }; - } - }; -} - -export default replace; diff --git a/sites/svelte-5-preview/src/lib/workers/compiler/index.js b/sites/svelte-5-preview/src/lib/workers/compiler/index.js deleted file mode 100644 index 9247894dd6..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/compiler/index.js +++ /dev/null @@ -1,154 +0,0 @@ -/// -self.window = self; //TODO: still need?: egregious hack to get magic-string to work in a worker - -/** - * @type {{ - * parse: typeof import('svelte/compiler').parse; - * compile: typeof import('svelte/compiler').compile; - * compileModule: typeof import('svelte/compiler').compileModule; - * VERSION: string; - * }} - */ -let svelte; - -/** @type {(arg?: never) => void} */ -let fulfil_ready; -const ready = new Promise((f) => { - fulfil_ready = f; -}); - -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ - async (event) => { - switch (event.data.type) { - case 'init': - const { svelte_url } = event.data; - - const { version } = await fetch(`${svelte_url}/package.json`) - .then((r) => r.json()) - .catch(() => ({ version: 'experimental' })); - - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - - svelte = globalThis.svelte; - - fulfil_ready(); - break; - - case 'compile': - await ready; - postMessage(compile(event.data)); - break; - - case 'migrate': - await ready; - postMessage(migrate(event.data)); - break; - } - } -); - -const common_options = { - dev: false, - css: false -}; - -/** @param {import("../workers").CompileMessageData} param0 */ -function compile({ id, source, options, return_ast }) { - try { - const css = `/* Select a component to see compiled CSS */`; - - if (options.filename.endsWith('.svelte')) { - const compiled = svelte.compile(source, { - ...options, - discloseVersion: false // less visual noise in the output tab - }); - - const { js, css, warnings, metadata } = compiled; - - const ast = return_ast ? svelte.parse(source, { modern: true }) : undefined; - - return { - id, - result: { - js: js.code, - css: css?.code || `/* Add a tag to see compiled CSS */`, - error: null, - warnings: warnings.map((warning) => warning.toJSON()), - metadata, - ast - } - }; - } else if (options.filename.endsWith('.svelte.js')) { - const compiled = svelte.compileModule(source, { - filename: options.filename, - generate: options.generate, - dev: options.dev - }); - - if (compiled) { - return { - id, - result: { - js: compiled.js.code, - css, - error: null, - warnings: compiled.warnings.map((warning) => warning.toJSON()), - metadata: compiled.metadata - } - }; - } - } - - return { - id, - result: { - js: `// Select a component, or a '.svelte.js' module that uses runes, to see compiled output`, - css, - error: null, - warnings: [], - metadata: null - } - }; - } catch (err) { - // @ts-ignore - let message = `/*\nError compiling ${err.filename ?? 'component'}:\n${err.message}\n*/`; - - return { - id, - result: { - js: message, - css: message, - error: { - message: err.message, - position: err.position - }, - warnings: [], - metadata: null - } - }; - } -} - -/** @param {import("../workers").MigrateMessageData} param0 */ -function migrate({ id, source, filename }) { - try { - const result = svelte.migrate(source, { filename }); - - return { - id, - result - }; - } catch (err) { - // @ts-ignore - let message = `/*\nError migrating ${err.filename ?? 'component'}:\n${err.message}\n*/`; - - return { - id, - result: { code: source }, - error: message - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/jsconfig.json b/sites/svelte-5-preview/src/lib/workers/jsconfig.json deleted file mode 100644 index 60351b7548..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "include": ["./**/*"], - "compilerOptions": { - "paths": { - "svelte": ["../../../static/svelte/main"], - "svelte/*": ["../../../static/svelte/*"] - } - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/patch_window.js b/sites/svelte-5-preview/src/lib/workers/patch_window.js deleted file mode 100644 index ff7057c9c2..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/patch_window.js +++ /dev/null @@ -1 +0,0 @@ -self.window = self; // hack for magic-sring and rollup inline sourcemaps diff --git a/sites/svelte-5-preview/src/lib/workers/workers.d.ts b/sites/svelte-5-preview/src/lib/workers/workers.d.ts deleted file mode 100644 index e66e075c14..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/workers.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { CompileOptions, File } from '../types'; - -export type CompileMessageData = { - id: number; - type: 'compile' | 'init'; - source: string; - options: CompileOptions; - is_entry: boolean; - return_ast: boolean; - svelte_url?: string; - result: { - js: string; - css: string; - ast?: import('svelte/types/compiler/interfaces').Ast; - metadata?: { - runes: boolean; - }; - }; -}; - -export type BundleMessageData = { - uid: number; - type: 'init' | 'bundle' | 'status'; - message: string; - packages_url: string; - svelte_url: string; - files: File[]; -}; - -export type MigrateMessageData = { - id: number; - result: { code: string }; - error?: string; -}; diff --git a/sites/svelte-5-preview/src/routes/+error.svelte b/sites/svelte-5-preview/src/routes/+error.svelte deleted file mode 100644 index 6d6d8a7d7c..0000000000 --- a/sites/svelte-5-preview/src/routes/+error.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - - {$page.status} - - -
            - {#if online} - {#if $page.status === 404} -

            Not found!

            -

            - If you were expecting to find something here, please drop by the - Discord chatroom - and let us know, or raise an issue on - GitHub. Thanks! -

            - {:else} -

            Yikes!

            -

            Something went wrong when we tried to render this page.

            - {#if $page.error.message} -

            {$page.status}: {$page.error.message}

            - {:else} -

            Encountered a {$page.status} error.

            - {/if} -

            Please try reloading the page.

            -

            - If the error persists, please drop by the - Discord chatroom - and let us know, or raise an issue on - GitHub. Thanks! -

            - {/if} - {:else} -

            It looks like you're offline

            -

            Reload the page once you've found the internet.

            - {/if} -
            - - diff --git a/sites/svelte-5-preview/src/routes/+layout.server.js b/sites/svelte-5-preview/src/routes/+layout.server.js deleted file mode 100644 index 640c4c57df..0000000000 --- a/sites/svelte-5-preview/src/routes/+layout.server.js +++ /dev/null @@ -1,12 +0,0 @@ -export const prerender = true; - -/** @type {import('@sveltejs/adapter-vercel').EdgeConfig} */ -export const config = { - runtime: 'edge' -}; - -export const load = async ({ fetch }) => { - const nav_data = await fetch('/nav.json').then((r) => r.json()); - - return { nav_links: nav_data }; -}; diff --git a/sites/svelte-5-preview/src/routes/+layout.svelte b/sites/svelte-5-preview/src/routes/+layout.svelte deleted file mode 100644 index 99b72d0fad..0000000000 --- a/sites/svelte-5-preview/src/routes/+layout.svelte +++ /dev/null @@ -1,101 +0,0 @@ - - - - Svelte 5 preview - - - - - - - - - - - - - - diff --git a/sites/svelte-5-preview/src/routes/+page.svelte b/sites/svelte-5-preview/src/routes/+page.svelte deleted file mode 100644 index b54c36ee5b..0000000000 --- a/sites/svelte-5-preview/src/routes/+page.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - { - if (!setting_hash) { - change_from_hash(); - } - - setting_hash = false; - }} -/> - - diff --git a/sites/svelte-5-preview/src/routes/defaults.js b/sites/svelte-5-preview/src/routes/defaults.js deleted file mode 100644 index f1bdbbbb35..0000000000 --- a/sites/svelte-5-preview/src/routes/defaults.js +++ /dev/null @@ -1,21 +0,0 @@ -export const default_files = () => [ - { - name: 'App', - type: 'svelte', - source: ` - - - - ` - .replace(/^\t{3}/gm, '') - .trim() - } -]; diff --git a/sites/svelte-5-preview/src/routes/docs/+layout.server.js b/sites/svelte-5-preview/src/routes/docs/+layout.server.js deleted file mode 100644 index cbb6433bf9..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+layout.server.js +++ /dev/null @@ -1,13 +0,0 @@ -export async function load({ url }) { - if (url.pathname === '/docs') { - return { - sections: [] - }; - } - - const { get_docs_data, get_docs_list } = await import('./render.js'); - - return { - sections: get_docs_list(await get_docs_data()) - }; -} diff --git a/sites/svelte-5-preview/src/routes/docs/+layout.svelte b/sites/svelte-5-preview/src/routes/docs/+layout.svelte deleted file mode 100644 index 097fde98ff..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+layout.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - -
            -
            - -
            - -
            - {#if category} -

            {category}

            - {/if} - {#if title} -

            {title}

            - {/if} - - -
            -
            - - diff --git a/sites/svelte-5-preview/src/routes/docs/+page.js b/sites/svelte-5-preview/src/routes/docs/+page.js deleted file mode 100644 index fba7f30e4b..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export function load() { - redirect(307, '/docs/introduction'); -} diff --git a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js b/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js deleted file mode 100644 index 25c78ba281..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js +++ /dev/null @@ -1,19 +0,0 @@ -import { error } from '@sveltejs/kit'; - -export async function entries() { - const { get_docs_data } = await import('../render.js'); - - const data = await get_docs_data(); - return data[0].pages.map((page) => ({ slug: page.slug })); -} - -export async function load({ params }) { - const { get_docs_data, get_parsed_docs } = await import('../render.js'); - - const data = await get_docs_data(); - const processed_page = await get_parsed_docs(data, params.slug); - - if (!processed_page) error(404); - - return { page: processed_page }; -} diff --git a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte b/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte deleted file mode 100644 index bb5c166711..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte +++ /dev/null @@ -1,76 +0,0 @@ - - - - {data.page?.title} • Docs • Svelte 5 preview - - - - - - -
            - - - {@html data.page.content} -
            - -
            -
            - previous - - {#if prev} - {prev.title} - {/if} -
            - -
            - next - {#if next} - {next.title} - {/if} -
            -
            - - diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md deleted file mode 100644 index 2e3cb987c0..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Introduction ---- - -Welcome to the Svelte 5 preview documentation! This is intended as a resource for people who already have some familiarity with Svelte and want to learn about the new runes API, which you can learn about in the [Introducing runes](https://svelte.dev/blog/runes) blog post. - -You can try runes for yourself in the [playground](/), or learn more about our plans via the [FAQ](/docs/faq). diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md deleted file mode 100644 index 84062a1cfa..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md +++ /dev/null @@ -1,700 +0,0 @@ ---- -title: Runes ---- - -Svelte 5 introduces _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and — for the first time — inside `.svelte.js` and `.svelte.ts` modules. - -Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language. - -When you [opt in to runes mode](#how-to-opt-in), the non-runes features listed in the 'What this replaces' sections are no longer available. - -> Check out the [Introducing runes](https://svelte.dev/blog/runes) blog post before diving into the docs! - -## `$state` - -Reactive state is declared with the `$state` rune: - -```svelte - - - -``` - -You can also use `$state` in class fields (whether public or private): - -```js -// @errors: 7006 2554 -class Todo { - done = $state(false); - text = $state(); - - constructor(text) { - this.text = text; - } -} -``` - -> In this example, the compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields - -Only plain objects and arrays [are made deeply reactive](/#H4sIAAAAAAAAE42QwWrDMBBEf2URhUhUNEl7c21DviPOwZY3jVpZEtIqUBz9e-UUt9BTj7M784bdmZ21wciq48xsPyGr2MF7Jhl9-kXEKxrCoqNLQS2TOqqgPbWd7cgggU3TgCFCAw-RekJ-3Et4lvByEq-drbe_dlsPichZcFYZrT6amQto2pXw5FO88FUYtG90gUfYi3zvWrYL75vxL57zfA07_zfr23k1vjtt-aZ0bQTcbrDL5ZifZcAxKeS8lzDc8X0xDhJ2ItdbX1jlOZMb9VnjyCoKCfMpfwG975NFVwEAAA==) by wrapping them with [`Proxies`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy): - -```svelte - - - - - - -

            - {numbers.join(' + ') || 0} - = - {numbers.reduce((a, b) => a + b, 0)} -

            -``` - -### What this replaces - -In non-runes mode, a `let` declaration is treated as reactive state if it is updated at some point. Unlike `$state(...)`, which works anywhere in your app, `let` only behaves this way at the top level of a component. - -## `$state.raw` - -State declared with `$state.raw` cannot be mutated; it can only be _reassigned_. In other words, rather than assigning to a property of an object, or using an array method like `push`, replace the object or array altogether if you'd like to update it: - -```diff - - -- - -- -+ - -

            - {numbers.join(' + ') || 0} - = - {numbers.reduce((a, b) => a + b, 0)} -

            -``` - -This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects). - -## `$state.snapshot` - -To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: - -```svelte - -``` - -This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`. - -## `$derived` - -Derived state is declared with the `$derived` rune: - -```diff - - - - -+

            {count} doubled is {doubled}

            -``` - -The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions. - -As with `$state`, you can mark class fields as `$derived`. - -### What this replaces - -If the value of a reactive variable is being computed it should be replaced with `$derived` whether it previously took the form of `$: double = count * 2` or `$: { double = count * 2; }` There are some important differences to be aware of: - -- With the `$derived` rune, the value of `double` is always current (for example if you update `count` then immediately `console.log(double)`). With `$:` declarations, values are not updated until right before Svelte updates the DOM -- In non-runes mode, Svelte determines the dependencies of `double` by statically analysing the `count * 2` expression. If you refactor it... - ```js - // @errors: 2304 - const doubleCount = () => count * 2; - $: double = doubleCount(); - ``` - ...that dependency information is lost, and `double` will no longer update when `count` changes. With runes, dependencies are instead tracked at runtime. -- In non-runes mode, reactive statements are ordered _topologically_, meaning that in a case like this... - ```js - // @errors: 2304 - $: triple = double + count; - $: double = count * 2; - ``` - ...`double` will be calculated first despite the source order. In runes mode, `triple` cannot reference `double` before it has been declared. - -## `$derived.by` - -Sometimes you need to create complex derivations that don't fit inside a short expression. In these cases, you can use `$derived.by` which accepts a function as its argument. - -```svelte - - - -``` - -In essence, `$derived(expression)` is equivalent to `$derived.by(() => expression)`. - -## `$effect` - -To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)): - -```svelte - - - -``` - -The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. - -Values that are read asynchronously — after an `await` or inside a `setTimeout`, for example — will _not_ be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/#H4sIAAAAAAAAE31T24rbMBD9lUG7kCxsbG_LvrhOoPQP2r7VhSjy2BbIspHGuTT436tLnMtSCiaOzpw5M2dGPrNaKrQs_3VmmnfIcvZ1GNgro9PgD3aPitCdbT8a4ZHCCiMH2pS6JIUEVv5BWMOzJU64fM9evswR0ave3EKLp7r-jFm2iIwri-s9tx5ywDPWNQpaLl9gvYFz4JHotfVqmvBITi9mJA3St4gtF5-qWZUuvEQo5Oa7F8tewT2XrIOsqL2eWpRNS7eGSkpToFZaOEilwODKjBoOLWrco4FtsLQF0XLdoE2S5LGmm6X6QSflBxKod8IW6afssB8_uAslndJuJNA9hWKw9VO91pmJ92XunHlu_J1nMDk8_p_8q0hvO9NFtA47qavcW12fIzJBmM26ZG9ZVjKIs7ke05hdyT0Ixa11Ad-P6ZUtWbgNheI7VJvYQiH14Bz5a-SYxvtwIqHonqsR12ff8ORkQ-chP70T-L9eGO4HvYAFwRh9UCxS13h0YP2CgmoyG5h3setNhWZF_ZDD23AE2ytZwZMQ4jLYgVeV1I2LYgfZBey4aaR-xCppB8VPOdQKjxes4UMgxcVcvwHf4dzAv9K4ko1eScLO5iDQXQFzL5gl7zdJt-nZnXYfbddXspZYsZzMiNPv6S8Bl41G7wMAAA==)): - -```ts -// @filename: index.ts -declare let canvas: { - width: number; - height: number; - getContext( - type: '2d', - options?: CanvasRenderingContext2DSettings - ): CanvasRenderingContext2D; -}; -declare let color: string; -declare let size: number; - -// ---cut--- -$effect(() => { - const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - - // this will re-run whenever `color` changes... - context.fillStyle = color; - - setTimeout(() => { - // ...but not when `size` changes - context.fillRect(0, 0, size, size); - }, 0); -}); -``` - -An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes _inside_ an object at dev time, you can use [`$inspect`](#$inspect).) - -```svelte - - - - -

            {state.value} doubled is {derived.value}

            -``` - -An effect only depends on the values that it read the last time it ran. If `a` is true, changes to `b` will [not cause this effect to rerun](/#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==): - -```ts -let a = false; -let b = false; -// ---cut--- -$effect(() => { - console.log('running'); - - if (a || b) { - console.log('inside if block'); - } -}); -``` - -You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/#H4sIAAAAAAAAE42SzW6DMBCEX2Vl5RDaVCQ9JoDUY--9lUox9lKsGBvZC1GEePcaKPnpqSe86_m0M2t6ViqNnu0_e2Z4jWzP3pqGbRhdmrHwHWrCUHvbOjF2Ei-caijLTU4aCYRtDUEKK0-ccL2NDstNrbRWHoU10t8Eu-121gTVCssSBa3XEaQZ9GMrpziGj0p5OAccCgSHwmEgJZwrNNihg6MyhK7j-gii4uYb_YyGUZ5guQwzPdL7b_U4ZNSOvp9T2B3m1rB5cLx4zMkhtc7AHz7YVCVwEFzrgosTBMuNs52SKDegaPbvWnMH8AhUXaNUIY6-hHCldQhUIcyLCFlfAuHvkCKaYk8iYevGGgy2wyyJnpy9oLwG0sjdNe2yhGhJN32HsUzi2xOapNpl_bSLIYnDeeoVLZE1YI3QSpzSfo7-8J5PKbwOmdf2jC6JZyD7HxpPaMk93aHhF6utVKVCyfbkWhy-hh9Z3o_2nQIAAA==)). - -```svelte - - -

            {count}

            - - - -``` - -### When not to use `$effect` - -In general, `$effect` is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this... - -```svelte - -``` - -...do this: - -```svelte - -``` - -> For things that are more complicated than a simple expression like `count * 2`, you can also use [`$derived.by`](#$derived-by). - -You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/#H4sIAAAAAAAACpVRQWrDMBD8ihA5ONDG7qEXxQ70HXUPir0KgrUsrHWIMf57pXWdlFIKPe6MZmZnNUtjEYJU77N0ugOp5Jv38knS5NMQroAEcQ79ODQJKUMzWE-n2tWEQIJ60igq8VIUxw0LHhxFbBdIE2TF_s4gmG8Ea5mM9A6MgYaybC-qk5gTlDT8fg15Xo3ZbPlTti2w6ZLNQ1bmjw6uRH0G5DqldX6MjWL1qpaDdheopThb16qrxhGqmX0X0elbNbP3InKWfjH5hvKYku7u_wtKC_-aw8Q9Jk0_UgJNCOvvJHC7SGuDRz0pYRBuxxW7aK9EcXiFbr0NX4bl8cO7vrXGQisVDSMsH8sniirsuSsCAAA=)): - -```svelte - - - - - -``` - -Instead, use callbacks where possible ([demo](/#H4sIAAAAAAAACo1SMW6EMBD8imWluFNyQIo0HERKf13KkMKB5WTJGAsvp0OIv8deMEEJRcqdmZ1ZjzzyWiqwPP0YuRYN8JS_GcOfOA7GD_YGCsHNtu270iOZLTtp8LXQBSpAhi0KxXL2nCTngFkDGh32YFEgHJLjyiioNwTtEunoutclylaz3lSOfPceBziy0ZMFBs9HiFB0V8DoJlQP55ldfOdjTvMBRE275hcn33gv2_vWITh4e3GwzuKfNnSmxBcoKiaT2vSuG1diXvBO6CsUnJFrPpLhxFpNonzcvHdijbjnI0VNLCavRR8HlEYfvcb9O9mf_if4QuBOLqnXWD_9SrU4KJg_ggdDm5W0RokhZbWC-1LiVZiUJdELNJvqaN39raatZC2h4il2PUyf0zcIbC-7lgIAAA==)): - -```svelte - - - - - -``` - -If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/#H4sIAAAAAAAACpVRQW7DIBD8CkI9JFIau4deiB2p7yg9kHhtIWGMYG3Fsvh7ARs3qnrpCWZGM8MuC22lAkfZ50K16IEy-mEMPVGcTQRuAoUQsBtGe49M5e5WGrxyzVEBEhxQKFKTt7K8ZM4Z0Bi4F4cC4VAeo7JpCtooLRFz7AIzCTXC4ZgpjhZwtHpLfl3TLqvoT-vpdt_0ZMy92TllVzx8AFXx83pdKXEDlQappDZjmCUMXXNqhe6AU3KTumGppV5StCe9eNRLivekSNZNKTKbYGza0_9XFPdzTvc_257kvTJyvxodzgrWP4pkXlEjnVFiZqRV8NiW0wnDSHl-hz4RPm0p2cO390MjWwkNZWhD5Zf_BkCCa6AxAgAA)): - -```svelte - - - - - -``` - -If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](functions#untrack). - -### What this replaces - -The portions of `$: {}` that are triggering side-effects can be replaced with `$effect` while being careful to migrate updates of reactive variables to use `$derived`. There are some important differences: - -- Effects only run in the browser, not during server-side rendering -- They run after the DOM has been updated, whereas `$:` statements run immediately _before_ -- You can return a cleanup function that will be called whenever the effect refires - -Additionally, you may prefer to use effects in some places where you previously used `onMount` and `afterUpdate` (the latter of which will be deprecated in Svelte 5). There are some differences between these APIs as `$effect` should not be used to compute reactive values and will be triggered each time a referenced reactive variable changes (unless using `untrack`). - -## `$effect.pre` - -In rare cases, you may need to run code _before_ the DOM updates. For this we can use the `$effect.pre` rune: - -```svelte - - -
            - {#each messages as message} -

            {message}

            - {/each} -
            -``` - -Apart from the timing, `$effect.pre` works exactly like [`$effect`](#$effect) — refer to its documentation for more info. - -### What this replaces - -Previously, you would have used `beforeUpdate`, which — like `afterUpdate` — is deprecated in Svelte 5. - -## `$effect.tracking` - -The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template ([demo](/#H4sIAAAAAAAACn3PQWrDMBAF0KtMRSA2xPFeUQU5R92FUUZBVB4N1rgQjO9eKSlkEcjyfz6PmVX5EDEr_bUqGidUWp2Z1UHJjWvIvxgFS85pmV1tTHZzYLEDDeIS5RTxGNO12QcClyZOhCSQURbW-wPs0Ht0cpR5dD-Brk3bnqDvwY8xYzGK8j9pmhY-Lay1eqUfm3eizEsFZWtPA5n-eSYZtkUQnDiOghrWV2IzPVswH113d6DrbHl6SpfgA16UruX2vf0BWo7W2y8BAAA=)): - -```svelte - - -

            in template: {$effect.tracking()}

            -``` - -This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. - -## `$effect.root` - -The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for -nested effects that you want to manually control. This rune also allows for creation of effects outside of the component initialisation phase. - -```svelte - -``` - -## `$props` - -To declare component props, use the `$props` rune: - -```js -let { optionalProp = 42, requiredProp } = $props(); -``` - -You can use familiar destructuring syntax to rename props, in cases where you need to (for example) use a reserved word like `catch` in ``: - -```js -let { catch: theCatch } = $props(); -``` - -To get all properties, use rest syntax: - -```js -let { a, b, c, ...everythingElse } = $props(); -``` - -You can also use an identifier: - -```js -let props = $props(); -``` - -If you're using TypeScript, you can declare the prop types: - - -```ts -interface MyProps { - required: string; - optional?: number; - partOfEverythingElse?: boolean; -}; - -let { required, optional, ...everythingElse }: MyProps = $props(); -``` - -> In an earlier preview, `$props()` took a type argument. This caused bugs, since in a case like this... -> -> ```ts -> // @errors: 2558 -> let { x = 42 } = $props<{ x?: string }>(); -> ``` -> -> ...TypeScript [widens the type](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwBIAHGHIgZwB4AVeAXnilQE8A+ACgEoAueagbgBQgiCAzwA3vAAe9eABYATPAC+c4qQqUp03uQwwsqAOaqOnIfCsB6a-AB6AfiA) of `x` to be `string | number`, instead of erroring. - -If you're using JavaScript, you can declare the prop types using JSDoc: - -```js -/** @type {{ x: string }} */ -let { x } = $props(); - -// or use @typedef if you want to document the properties: - -/** - * @typedef {Object} MyProps - * @property {string} y Some documentation - */ - -/** @type {MyProps} */ -let { y } = $props(); -``` - -By default props are treated as readonly, meaning reassignments will not propagate upwards and mutations will result in a warning at runtime in development mode. You will also get a runtime error when trying to `bind:` to a readonly prop in a parent component. To declare props as bindable, use [`$bindable()`](#$bindable). - -### What this replaces - -`$props` replaces the `export let` and `export { x as y }` syntax for declaring props. It also replaces `$$props` and `$$restProps`, and the little-known `interface $$Props {...}` construct. - -Note that you can still use `export const` and `export function` to expose things to users of your component (if they're using `bind:this`, for example). - -## `$bindable` - -To declare props as bindable, use `$bindable()`. Besides using them as regular props, the parent can (_can_, not _must_) then also `bind:` to them. - -```svelte - -``` - -You can pass an argument to `$bindable()`. This argument is used as a fallback value when the property is `undefined`. - -```svelte - -``` - -Note that the parent is not allowed to pass `undefined` to a property with a fallback if it `bind:`s to that property. - -## `$inspect` - -The `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its -argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object -or array using [fine-grained reactivity](/docs/fine-grained-reactivity) will cause it to re-fire. ([Demo:](/#H4sIAAAAAAAACkWQ0YqDQAxFfyUMhSotdZ-tCvu431AXtGOqQ2NmmMm0LOK_r7Utfby5JzeXTOpiCIPKT5PidkSVq2_n1F7Jn3uIcEMSXHSw0evHpAjaGydVzbUQCmgbWaCETZBWMPlKj29nxBDaHj_edkAiu12JhdkYDg61JGvE_s2nR8gyuBuiJZuDJTyQ7eE-IEOzog1YD80Lb0APLfdYc5F9qnFxjiKWwbImo6_llKRQVs-2u91c_bD2OCJLkT3JZasw7KLA2XCX31qKWE6vIzNk1fKE0XbmYrBTufiI8-_8D2cUWBA_AQAA)) - -```svelte - - - - -``` - -`$inspect` returns a property `with`, which you can invoke with a callback, which will then be invoked instead of `console.log`. The first argument to the callback is either `"init"` or `"update"`, all following arguments are the values passed to `$inspect`. [Demo:](/#H4sIAAAAAAAACkVQ24qDMBD9lSEUqlTqPlsj7ON-w7pQG8c2VCchmVSK-O-bKMs-DefKYRYx6BG9qL4XQd2EohKf1opC8Nsm4F84MkbsTXAqMbVXTltuWmp5RAZlAjFIOHjuGLOP_BKVqB00eYuKs82Qn2fNjyxLtcWeyUE2sCRry3qATQIpJRyD7WPVMf9TW-7xFu53dBcoSzAOrsqQNyOe2XUKr0Xi5kcMvdDB2wSYO-I9vKazplV1-T-d6ltgNgSG1KjVUy7ZtmdbdjqtzRcphxMS1-XubOITJtPrQWMvKnYB15_1F7KKadA_AQAA) - -```svelte - - - -``` - -A convenient way to find the origin of some change is to pass `console.trace` to `with`: - -```js -// @errors: 2304 -$inspect(stuff).with(console.trace); -``` - -> `$inspect` only works during development. - -## `$host` - -Retrieves the `this` reference of the custom element that contains this component. Example: - -```svelte - - - - - -``` - -> Only available inside custom element components, and only on the client-side - -## How to opt in - -Current Svelte code will continue to work without any adjustments. Components using the Svelte 4 syntax can use components using runes and vice versa. - -The easiest way to opt in to runes mode is to just start using them in your code. Alternatively, you can force the compiler into runes or non-runes mode either on a per-component basis... - - -```svelte - - - -``` - -...or for your entire app: - -```js -/// file: svelte.config.js -export default { - compilerOptions: { - runes: true - } -}; -``` diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md deleted file mode 100644 index b3fe34d21a..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: Snippets ---- - -Snippets, and _render tags_, are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like [this](/#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)... - -```svelte -{#each images as image} - {#if image.href} - -
            - {image.caption} -
            {image.caption}
            -
            -
            - {:else} -
            - {image.caption} -
            {image.caption}
            -
            - {/if} -{/each} -``` - -...you can write [this](/#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=): - -```diff -+{#snippet figure(image)} -
            - {image.caption} -
            {image.caption}
            -
            -+{/snippet} - -{#each images as image} - {#if image.href} - -+ {@render figure(image)} - - {:else} -+ {@render figure(image)} - {/if} -{/each} -``` - -Snippet parameters can be destructured ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): - -```svelte -{#snippet figure({ src, caption, width, height })} -
            - {caption} -
            {caption}
            -
            -{/snippet} -``` - -Like function declarations, snippets can have an arbitrary number of parameters, which can have default values. You cannot use rest parameters however. - -## Snippet scope - -Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the ` - -{#snippet hello(name)} -

            hello {name}! {message}!

            -{/snippet} - -{@render hello('alice')} -{@render hello('bob')} -``` - -...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings): - -```svelte -
            - {#snippet x()} - {#snippet y()}...{/snippet} - - - {@render y()} - {/snippet} - - - {@render y()} -
            - - -{@render x()} -``` - -Snippets can reference themselves and each other ([demo](/#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): - -```svelte -{#snippet blastoff()} - 🚀 -{/snippet} - -{#snippet countdown(n)} - {#if n > 0} - {n}... - {@render countdown(n - 1)} - {:else} - {@render blastoff()} - {/if} -{/snippet} - -{@render countdown(10)} -``` - -## Passing snippets to components - -Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): - -```svelte - - -{#snippet header()} - fruit - qty - price - total -{/snippet} - -{#snippet row(d)} - {d.name} - {d.qty} - {d.price} - {d.qty * d.price} -{/snippet} - - -``` - -As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)): - -```svelte - -
            - {#snippet header()} - - - - - {/snippet} - - {#snippet row(d)} - - - - - {/snippet} -
            fruitqtypricetotal{d.name}{d.qty}{d.price}{d.qty * d.price}
            -``` - -Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)): - -```diff - -- {#snippet header()} -- -- -- -- -- {/snippet} -+ -+ -+ -+ - - -
            fruitqtypricetotalfruitqtypricetotal
            -``` - -```diff - - - -- {#if header} -+ {#if children} - -- {@render header()} -+ {@render children()} - - {/if} - - -
            -``` - -> Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name - -## Typing snippets - -Snippets implement the `Snippet` interface imported from `'svelte'`: - -```diff -- -``` - -With this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters. - -We can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type: - -```diff -- -``` - -## Creating snippets programmatically - -In advanced scenarios, you may need to create a snippet programmatically. For this, you can use [`createRawSnippet`](/docs/imports#svelte-createrawsnippet) - -## Snippets and slots - -In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/special-elements#slot). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. - -They continue to work, however, and you can mix and match snippets and slots in your components. - -When using custom elements, you should still use `` like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it. diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md deleted file mode 100644 index 5124ae291d..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: Event handlers ---- - -Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the `on:` directive to attach an event listener to an element, in Svelte 5 they are properties like any other: - -```diff - - -- -``` - -Since they're just properties, you can use the normal shorthand syntax... - -```svelte - - - -``` - -...though when using a named event handler function it's usually better to use a more descriptive name. - -Traditional `on:` event handlers will continue to work, but are deprecated in Svelte 5. - -## Component events - -In Svelte 4, components could emit events by creating a dispatcher with [`createEventDispatcher`](https://svelte.dev/docs/svelte#createeventdispatcher). - -This function is deprecated in Svelte 5. Instead, components should accept _callback props_ - which means you then pass functions as properties to these components ([demo](/#H4sIAAAAAAAACo1US27bMBC9yoBtELu2ZDmAG0CRhPYG3VddyPIwIUKRgjiOkwrcd9VFL5BV75cjFKQo2e5_IQnzeW-GM3zqGRcSDUs_9kxVDbKUvW9btmT01DrDPKAkZEtm9L6rnSczdSdaKkpVkmha3RF82Dct8E43cBmvnBEPsMsbl-QeiQRGfEbI4bWhinC23sxvxsh23xk6hnglDfqoKonvVU1CK-jQIM3m0HtOCmzrzVCDRg4P9j5bqmx1bFZlrjPfteKyIsz7WasP2M0hL85YFzn4QGAWHGbeX8D1Zj41S90-1LHuvcM_kp4QJPNhDNFpCUew8i32rwQfCnjObLsn0gq0qqWo7_Pez8AWCg-wraTUWmWrIcevIzNtpaCWlTF5ybZaNyUrXp6_fc9WLlKUqk9RGrS_SR7oSgaGniTmJTN1JTGFPomTNbzxbduSFcORXp6_fvEkE_FKcOun7PE-zRcIM2i1EW6NKXDxiLswWomcUkiCRbo9Ggexo7sU1klyETx3KG7v6MzFtaLIdea9D4eRCB8pqqS4VSnUqGhapRQKo4nnZmxNuJQIH1CRSUFpNV0g94nDbMajUFep8TB-SJDEV-YcoXUzpldKNNWQ7d1JvDHAdXeout0Z6t09PvGuatDAKT65gB7CMpL4LdjBfbU5819vxoAbz0lkcA9aCJthS9boneACdyx119guJ_E7jfyv-p10ewhqWkJQAFin5LbTrZkdJe5v-1HiXvzn6vz5rs-8hAJ7EJUtgn1y7f8ADN1MwGD_G-gBUWSLaModfnA-kELvvxb-Bl8sbLGY4L_O-5P9ATwVcA54BQAA)): - -```svelte - - - { - size += power; - if (size > 75) burst = true; - }} - deflate={(power) => { - if (size > 0) size -= power; - }} -/> - -{#if burst} - - 💥 -{:else} - - 🎈 - -{/if} -``` - -```svelte - - - - - -Pump power: {power} - -``` - -## Bubbling events - -Instead of doing ` -``` - -Note that this also means you can 'spread' event handlers onto the element along with other props: - -```svelte - - - -``` - -## Event modifiers - -In Svelte 4, you can add event modifiers to handlers: - -```svelte - -``` - -Modifiers are specific to `on:` and as such do not work with modern event handlers. Adding things like `event.preventDefault()` inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers. - -Since event handlers are just functions, you can create your own wrappers as necessary: - -```svelte - - - -``` - -There are three modifiers — `capture`, `passive` and `nonpassive` — that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs. - -For `capture`, we add the modifier to the event name: - -```svelte - -``` - -Changing the [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! - then you will need to use an action to apply the event handler yourself. - -## Multiple event handlers - -In Svelte 4, this is possible: - -```svelte - -``` - -This is something of an anti-pattern, since it impedes readability (if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other) and implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called. - -Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this: - -```svelte - -``` - -When spreading props, local event handlers must go _after_ the spread, or they risk being overwritten: - -```svelte - -``` - -## Why the change? - -By deprecating `createEventDispatcher` and the `on:` directive in favour of callback props and normal element properties, we: - -- reduce Svelte's learning curve -- remove boilerplate, particularly around `createEventDispatcher` -- remove the overhead of creating `CustomEvent` objects for events that may not even have listeners -- add the ability to spread event handlers -- add the ability to know which event handlers were provided to a component -- add the ability to express whether a given event handler is required or optional -- increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md deleted file mode 100644 index 7cbec56e17..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Imports ---- - -As well as runes, Svelte 5 introduces a handful of new things you can import, alongside existing ones like `getContext`, `setContext` and `tick`. - -## `svelte` - -### `flushSync` - -Forces any pending effects (including DOM updates) to be applied immediately, rather than in the future. This is mainly useful in a testing context — you'll rarely need it in application code. - -```svelte - - -{count} - -``` - -### `mount` - -Instantiates a component and mounts it to the given target: - -```js -// @errors: 2322 -import { mount } from 'svelte'; -import App from './App.svelte'; - -const app = mount(App, { - target: document.querySelector('#app'), - props: { some: 'property' } -}); -``` - -Note that unlike calling `new App(...)` in Svelte 4, things like effects (including `onMount` callbacks, and action functions) will not run during `mount`. If you need to force pending effects to run (in the context of a test, for example) you can do so with `flushSync()`. - -### `hydrate` - -Like `mount`, but will reuse up any HTML rendered by Svelte's SSR output (from the [`render`](#svelte-server-render) function) inside the target and make it interactive: - -```js -// @errors: 2322 -import { hydrate } from 'svelte'; -import App from './App.svelte'; - -const app = hydrate(App, { - target: document.querySelector('#app'), - props: { some: 'property' } -}); -``` - -As with `mount`, effects will not run during `hydrate` — use `flushSync()` immediately afterwards if you need them to. - -### `unmount` - -Unmounts a component created with [`mount`](#svelte-mount) or [`hydrate`](#svelte-hydrate): - -```js -// @errors: 1109 -import { mount, unmount } from 'svelte'; -import App from './App.svelte'; - -const app = mount(App, {...}); - -// later -unmount(app); -``` - -### `untrack` - -To prevent something from being treated as an `$effect`/`$derived` dependency, use `untrack`: - -```svelte - -``` - -### `createRawSnippet` - -An advanced API designed for people building frameworks that integrate with Svelte, `createRawSnippet` allows you to create [snippets](/docs/snippets) programmatically for use with `{@render ...}` tags: - -```js -import { createRawSnippet } from 'svelte'; - -const greet = createRawSnippet((name) => { - return { - render: () => ` -

            Hello ${name()}!

            - `, - setup: (node) => { - $effect(() => { - node.textContent = `Hello ${name()}!`; - }); - } - }; -}); -``` - -The `render` function is called during server-side rendering, or during `mount` (but not during `hydrate`, because it already ran on the server), and must return HTML representing a single element. - -The `setup` function is called during `mount` or `hydrate` with that same element as its sole argument. It is responsible for ensuring that the DOM is updated when the arguments change their value — in this example, when `name` changes: - -```svelte -{@render greet(name)} -``` - -If `setup` returns a function, it will be called when the snippet is unmounted. If the snippet is fully static, you can omit the `setup` function altogether. - -## `svelte/reactivity` - -Svelte provides reactive `SvelteMap`, `SvelteSet`, `SvelteDate` and `SvelteURL` classes. These can be imported from `svelte/reactivity` and used just like their native counterparts. [Demo:](https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE32QwUrEMBBAf2XMpQrb9t7tFrx7UjxZYWM6NYFkEpJJ16X03yWK9OQeZ3iPecwqZmMxie5tFSQdik48hiAOgq-hDGlByygOIvkcVdn0SUUTeBhpZOOCjwwrvPxgr89PsMEcvYPqV2wjSsVmMXytjiMVR3lKDDlaOAHhZVfvK80cUte2-CVdsNgo79ogWVcPx5H6dj9M_V1dg9KSPjEBe2CNCZumgboeRuoNhczwYWjqFmkzntYcbROiZ6-83f5HtE9c3nADKUF_yEi9jnvQxVgLOUySEc464nwGSRMsRiEsGJO8mVeEbRAH4fxkZoOT6Dhm3N63b9_bGfOlAQAA) - -```svelte - - - - - - - -
            - - - -``` - -## `svelte/events` - -Where possible, event handlers added with [attributes like `onclick`](/docs/event-handlers) use a technique called _event delegation_. It works by creating a single handler for each event type on the root DOM element, rather than creating a handler for each element, resulting in better performance and memory usage. - -Delegated event handlers run after other event handlers. In other words, a handler added programmatically with [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) will run _before_ a handler added declaratively with `onclick`, regardless of their relative position in the DOM ([demo](/#H4sIAAAAAAAAE41Sy2rDMBD8lUUXJxDiu-sYeugt_YK6h8RaN6LyykgrQzH6965shxJooQc_RhrNzA6aVW8sBlW9zYouA6pKPY-jOij-GjMIE1pGwcFF3-WVOnTejNy01LIZRucZZnD06iIxJOi9G6BYjxVPmZQfiwzaTBkL2ti73R5ODcwLiftIHRtHcLuQtuhlc9tpuSyBbyZAuLloNfhIELBzpO8E-Q_O4tG6j13hIqO_y0BvPOpiv0bhtJ1Y3pLoeNH6ZULiswmMJLZFZ033WRzuAvstdMseOXqCh9SriMfBTfgPnZxg-aYM6_KnS6pFCK6GdJVHPc0C01JyfY0slUnHi-JpfgjwSzUycdgmfOjFEP3RS1qdhJ8dYMDFt1yNmxxU0jRyCwanTW9Qq4p9xPSevgHI3m43QAIAAA==)). It also means that calling `event.stopPropagation()` inside a declarative handler _won't_ prevent the programmatic handler (created inside an action, for example) from running. - -To preserve the relative order, use `on` rather than `addEventListener` ([demo](/#H4sIAAAAAAAAE3VRy26DMBD8lZUvECkqdwpI_YB-QdJDgpfGqlkjex2pQv73rnmoStQeMB52dnZmmdVgLAZVn2ZFlxFVrd6mSR0Vf08ZhDtaRsHBRd_nL03ovZm4O9OZzTg5zzCDo3cXiSHB4N0IxdpWvD6RnuoV3pE4rLT8WGTQ5p6xoE20LA_QdjAvJB4i9WxE6nYhbdFLcaucuaqAbyZAuLloNfhIELB3pHeC3IOz-GLdZ1m4yOh3GRiMR10cViucto7l9MjRk9gvxdsRit6a_qs47q1rT8qvpvpdDjXChqshXWdT7SwwLVtrrpElnAguSu38EPCPEOItbF4eEhiifxKkdZLw8wQYcZlbrYO7bFTcdPJbR6fNYFCrmn3E9JF-AJZOg9MRAgAA)): - -```js -// @filename: index.ts -const element: Element = null as any; -// ---cut--- -import { on } from 'svelte/events'; - -const off = on(element, 'click', () => { - console.log('element was clicked'); -}); - -// later, if we need to remove the event listener: -off(); -``` - -`on` also accepts an optional fourth argument which matches the options argument for `addEventListener`. - -## `svelte/server` - -### `render` - -Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app: - -```js -// @errors: 2724 2305 2307 -import { render } from 'svelte/server'; -import App from './App.svelte'; - -const result = render(App, { - props: { some: 'property' } -}); -``` - -If the `css` compiler option was set to `'injected'`, ` diff --git a/sites/svelte-5-preview/src/routes/status/data.json/+server.js b/sites/svelte-5-preview/src/routes/status/data.json/+server.js deleted file mode 100644 index cfa65b0065..0000000000 --- a/sites/svelte-5-preview/src/routes/status/data.json/+server.js +++ /dev/null @@ -1,6 +0,0 @@ -import { json } from '@sveltejs/kit'; -import results from '../results.json'; - -export function GET() { - return json(results); -} diff --git a/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js b/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js deleted file mode 100644 index 4e22542435..0000000000 --- a/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js +++ /dev/null @@ -1,37 +0,0 @@ -import compiler_js from '../../../../../../packages/svelte/compiler/index.js?url'; -import package_json from '../../../../../../packages/svelte/package.json?url'; -import { read } from '$app/server'; - -const files = import.meta.glob('../../../../../../packages/svelte/src/**/*.js', { - eager: true, - query: '?url', - import: 'default' -}); - -const prefix = '../../../../../../packages/svelte/'; - -export const prerender = true; - -export function entries() { - const entries = Object.keys(files).map((path) => ({ path: path.replace(prefix, '') })); - entries.push({ path: 'compiler/index.js' }, { path: 'package.json' }); - return entries; -} - -// service worker requests files under this path to load the compiler and runtime -export async function GET({ params }) { - let file = ''; - - if (params.path === 'compiler/index.js') { - file = compiler_js; - } else if (params.path === 'package.json') { - file = package_json; - } else { - file = /** @type {string} */ (files[prefix + params.path]); - - // remove query string added by Vite when changing source code locally - file = file.split('?')[0]; - } - - return read(file); -} diff --git a/sites/svelte-5-preview/static/favicon.png b/sites/svelte-5-preview/static/favicon.png deleted file mode 100644 index 825b9e65af..0000000000 Binary files a/sites/svelte-5-preview/static/favicon.png and /dev/null differ diff --git a/sites/svelte-5-preview/svelte.config.js b/sites/svelte-5-preview/svelte.config.js deleted file mode 100644 index d2d2d8019d..0000000000 --- a/sites/svelte-5-preview/svelte.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import adapter from '@sveltejs/adapter-vercel'; - -/** @type {import('@sveltejs/kit').Config} */ -export default { - compilerOptions: { - compatibility: { - // site-kit manually instantiates components inside an action - componentApi: 4 - } - }, - kit: { - adapter: adapter({ - runtime: 'nodejs18.x' - }), - - prerender: { - handleMissingId(details) { - // do nothing - } - } - }, - - vitePlugin: { - inspector: false - } -}; diff --git a/sites/svelte-5-preview/vercel.json b/sites/svelte-5-preview/vercel.json deleted file mode 100644 index f9883a3d36..0000000000 --- a/sites/svelte-5-preview/vercel.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://openapi.vercel.sh/vercel.json", - "outputDirectory": "sites/svelte-5-preview/.vercel", - "buildCommand": "cd ../../ && pnpm build && (pnpm test-output || true) && pnpm preview-site" -} diff --git a/sites/svelte-5-preview/vite.config.js b/sites/svelte-5-preview/vite.config.js deleted file mode 100644 index 29642f5f78..0000000000 --- a/sites/svelte-5-preview/vite.config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - plugins: [sveltekit()], - resolve: { - dedupe: ['@codemirror/state', '@codemirror/language', '@codemirror/view'] - }, - optimizeDeps: { - exclude: ['@sveltejs/site-kit', '@sveltejs/kit', 'svelte'] - }, - ssr: { noExternal: ['@sveltejs/site-kit', '@sveltejs/kit', 'svelte'] }, - server: { - fs: { - strict: false - } - } -}; - -export default config;