diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index aa08df2f84..8a6d1bf345 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -11,14 +11,13 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') permissions: - issues: write # to add / delete reactions + issues: write # to add / delete reactions, post comments pull-requests: write # to read PR data, and to add labels actions: read # to check workflow status contents: read # to clone the repo steps: - - name: monitor action permissions - - name: check user authorization # user needs triage permission - uses: actions/github-script@v7 + - name: Check User Permissions + uses: actions/github-script@v8 id: check-permissions with: script: | @@ -57,7 +56,7 @@ jobs: } - name: Get PR Data - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: get-pr-data with: script: | @@ -67,6 +66,37 @@ jobs: repo: context.repo.repo, pull_number: context.issue.number }) + + const commentCreatedAt = new Date(context.payload.comment.created_at) + const commitPushedAt = new Date(pr.head.repo.pushed_at) + + console.log(`Comment created at: ${commentCreatedAt.toISOString()}`) + console.log(`PR last pushed at: ${commitPushedAt.toISOString()}`) + + // Check if any commits were pushed after the comment was created + if (commitPushedAt > commentCreatedAt) { + const errorMsg = [ + '⚠️ Security warning: PR was updated after the trigger command was posted.', + '', + `Comment posted at: ${commentCreatedAt.toISOString()}`, + `PR last pushed at: ${commitPushedAt.toISOString()}`, + '', + 'This could indicate an attempt to inject code after approval.', + 'Please review the latest changes and re-run /ecosystem-ci run if they are acceptable.' + ].join('\n') + + core.setFailed(errorMsg) + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: errorMsg + }) + + throw new Error('PR was pushed to after comment was created') + } + return { num: context.issue.number, branchName: pr.head.ref, @@ -85,15 +115,16 @@ jobs: svelte-ecosystem-ci - name: Trigger Downstream Workflow - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: trigger env: COMMENT: ${{ github.event.comment.body }} + PR_DATA: ${{ steps.get-pr-data.outputs.result }} with: github-token: ${{ steps.generate-token.outputs.token }} script: | const comment = process.env.COMMENT.trim() - const prData = ${{ steps.get-pr-data.outputs.result }} + const prData = JSON.parse(process.env.PR_DATA) const suite = comment.split('\n')[0].replace(/^\/ecosystem-ci run/, '').trim() diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index 0123868c4e..d3e46eb22d 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -85,8 +85,9 @@ Derived expressions are recalculated when their dependencies change, but you can 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([...]); +```js +// @errors: 7005 +let items = $state([ /*...*/ ]); let index = $state(0); let selected = $derived(items[index]); diff --git a/documentation/docs/03-template-syntax/12-bind.md b/documentation/docs/03-template-syntax/12-bind.md index c21ed35919..be84969b87 100644 --- a/documentation/docs/03-template-syntax/12-bind.md +++ b/documentation/docs/03-template-syntax/12-bind.md @@ -364,6 +364,8 @@ Components also support `bind:this`, allowing you to interact with component ins ``` +> [!NOTE] In case of using [the function bindings](#Function-bindings), the getter is required to ensure that the correct value is nullified on component or element destruction. + ## bind:_property_ for components ```svelte diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index 3e91af5d83..40e8d144e1 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -24,7 +24,7 @@ For the boundary to do anything, one or more of the following must be provided. ### `pending` -As of Svelte 5.36, boundaries with a `pending` snippet can contain [`await`](await-expressions) expressions. This snippet will be shown when the boundary is first created, and will remain visible until all the `await` expressions inside the boundary have resolved ([demo](/playground/untitled#H4sIAAAAAAAAE21QQW6DQAz8ytY9BKQVpFdKkPqDHnorPWzAaSwt3tWugUaIv1eE0KpKD5as8YxnNBOw6RAKKOOAVrA4up5bEy6VGknOyiO3xJ8qMnmPAhpOZDFC8T6BXPyiXADQ258X77P1FWg4moj_4Y1jQZZ49W0CealqruXUcyPkWLVozQXbZDC2R606spYiNo7bqA7qab_fp2paFLUElD6wYhzVa3AdRUySgNHZAVN1qDZaLRHljTp0vSTJ9XJjrSbpX5f0eZXN6zLXXOa_QfmurIVU-moyoyH5ib87o7XuYZfOZe6vnGWmx1uZW7lJOq9upa-sMwuUZdkmmfIbfQ1xZwwaBL8ECgk9zh8axJAdiVsoTsZGnL8Bg4tX_OMBAAA=)): +This snippet will be shown when the boundary is first created, and will remain visible until all the [`await`](await-expressions) expressions inside the boundary have resolved ([demo](/playground/untitled#H4sIAAAAAAAAE21QQW6DQAz8ytY9BKQVpFdKkPqDHnorPWzAaSwt3tWugUaIv1eE0KpKD5as8YxnNBOw6RAKKOOAVrA4up5bEy6VGknOyiO3xJ8qMnmPAhpOZDFC8T6BXPyiXADQ258X77P1FWg4moj_4Y1jQZZ49W0CealqruXUcyPkWLVozQXbZDC2R606spYiNo7bqA7qab_fp2paFLUElD6wYhzVa3AdRUySgNHZAVN1qDZaLRHljTp0vSTJ9XJjrSbpX5f0eZXN6zLXXOa_QfmurIVU-moyoyH5ib87o7XuYZfOZe6vnGWmx1uZW7lJOq9upa-sMwuUZdkmmfIbfQ1xZwwaBL8ECgk9zh8axJAdiVsoTsZGnL8Bg4tX_OMBAAA=)): ```svelte diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index f395de421c..0dfb996164 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -83,27 +83,18 @@ Svelte will warn you if you get it wrong. ## Type-safe context -A useful pattern is to wrap the calls to `setContext` and `getContext` inside helper functions that let you preserve type safety: +As an alternative to using `setContext` and `getContext` directly, you can use them via `createContext`. This gives you type safety and makes it unnecessary to use a key: -```js -/// file: context.js +```ts +/// file: context.ts // @filename: ambient.d.ts interface User {} -// @filename: index.js +// @filename: index.ts // ---cut--- -import { getContext, setContext } from 'svelte'; - -const key = {}; - -/** @param {User} user */ -export function setUserContext(user) { - setContext(key, user); -} +import { createContext } from 'svelte'; -export function getUserContext() { - return /** @type {User} */ (getContext(key)); -} +export const [getUserContext, setUserContext] = createContext(); ``` ## Replacing global state diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index 1816998299..86489e87e4 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -99,7 +99,7 @@ However, you can use any router library. A sampling of available routers are hig While most mobile apps are written without using JavaScript, if you'd like to leverage your existing Svelte components and knowledge of Svelte when building mobile apps, you can turn a [SvelteKit SPA](https://kit.svelte.dev/docs/single-page-apps) into a mobile app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/) or [Capacitor](https://capacitorjs.com/solution/svelte). Mobile features like the camera, geolocation, and push notifications are available via plugins for both platforms. -Svelte Native was an option available for Svelte 4, but note that Svelte 5 does not currently support it. Svelte Native lets you write NativeScript apps using Svelte components that contain [NativeScript UI components](https://docs.nativescript.org/ui/) rather than DOM elements, which may be familiar for users coming from React Native. +Some work has been completed towards [custom renderer support in Svelte 5](https://github.com/sveltejs/svelte/issues/15470), but this feature is not yet available. The custom rendering API would support additional mobile frameworks like Lynx JS and Svelte Native. Svelte Native was an option available for Svelte 4, but Svelte 5 does not currently support it. Svelte Native lets you write NativeScript apps using Svelte components that contain [NativeScript UI components](https://docs.nativescript.org/ui/) rather than DOM elements, which may be familiar for users coming from React Native. ## Can I tell Svelte not to remove my unused styles? diff --git a/documentation/docs/98-reference/.generated/server-warnings.md b/documentation/docs/98-reference/.generated/server-warnings.md deleted file mode 100644 index 26b3628be9..0000000000 --- a/documentation/docs/98-reference/.generated/server-warnings.md +++ /dev/null @@ -1,9 +0,0 @@ - - -### experimental_async_ssr - -``` -Attempted to use asynchronous rendering without `experimental.async` enabled -``` - -Set `experimental.async: true` in your compiler options (usually in `svelte.config.js`) to use async server rendering. This render ran synchronously. diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index 6c31aaafd0..07e13dea45 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -60,6 +60,14 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +### missing_context + +``` +Context was not set in a parent component +``` + +The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component. + ### snippet_without_render_tag ``` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 18a726277f..a779431a29 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,69 @@ # svelte +## 5.40.1 + +### Patch Changes + +- chore: Remove sync-in-async warning for server rendering ([#16949](https://github.com/sveltejs/svelte/pull/16949)) + +## 5.40.0 + +### Minor Changes + +- feat: add `createContext` utility for type-safe context ([#16948](https://github.com/sveltejs/svelte/pull/16948)) + +### Patch Changes + +- chore: simplify `batch.apply()` ([#16945](https://github.com/sveltejs/svelte/pull/16945)) + +- fix: don't rerun async effects unnecessarily ([#16944](https://github.com/sveltejs/svelte/pull/16944)) + +## 5.39.13 + +### Patch Changes + +- fix: add missing type for `fr` attribute for `radialGradient` tags in svg ([#16943](https://github.com/sveltejs/svelte/pull/16943)) + +- fix: unset context on stale promises ([#16935](https://github.com/sveltejs/svelte/pull/16935)) + +## 5.39.12 + +### Patch Changes + +- fix: better input cursor restoration for `bind:value` ([#16925](https://github.com/sveltejs/svelte/pull/16925)) + +- fix: track the user's getter of `bind:this` ([#16916](https://github.com/sveltejs/svelte/pull/16916)) + +- fix: generate correct SSR code for the case where `pending` is an attribute ([#16919](https://github.com/sveltejs/svelte/pull/16919)) + +- fix: generate correct code for `each` blocks with async body ([#16923](https://github.com/sveltejs/svelte/pull/16923)) + +## 5.39.11 + +### Patch Changes + +- fix: flush batches whenever an async value resolves ([#16912](https://github.com/sveltejs/svelte/pull/16912)) + +## 5.39.10 + +### Patch Changes + +- fix: hydrate each blocks inside element correctly ([#16908](https://github.com/sveltejs/svelte/pull/16908)) + +- fix: allow await in if block consequent and alternate ([#16890](https://github.com/sveltejs/svelte/pull/16890)) + +- fix: don't replace rest props with `$$props` for excluded props ([#16898](https://github.com/sveltejs/svelte/pull/16898)) + +- fix: correctly transform `$derived` private fields on server ([#16894](https://github.com/sveltejs/svelte/pull/16894)) + +- fix: add `UNKNOWN` evaluation value before breaking for `binding.initial===SnippetBlock` ([#16910](https://github.com/sveltejs/svelte/pull/16910)) + +## 5.39.9 + +### Patch Changes + +- fix: flush when pending boundaries resolve ([#16897](https://github.com/sveltejs/svelte/pull/16897)) + ## 5.39.8 ### Patch Changes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index b0c2fae2de..17ff100729 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -1658,6 +1658,7 @@ export interface SVGAttributes extends AriaAttributes, DO 'font-variant'?: number | string | undefined | null; 'font-weight'?: number | string | undefined | null; format?: number | string | undefined | null; + fr?: number | string | undefined | null; from?: number | string | undefined | null; fx?: number | string | undefined | null; fy?: number | string | undefined | null; diff --git a/packages/svelte/messages/server-warnings/warnings.md b/packages/svelte/messages/server-warnings/warnings.md deleted file mode 100644 index 4df89d0176..0000000000 --- a/packages/svelte/messages/server-warnings/warnings.md +++ /dev/null @@ -1,5 +0,0 @@ -## experimental_async_ssr - -> Attempted to use asynchronous rendering without `experimental.async` enabled - -Set `experimental.async: true` in your compiler options (usually in `svelte.config.js`) to use async server rendering. This render ran synchronously. diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 4b4d332202..e3959034a3 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -52,6 +52,12 @@ Certain lifecycle methods can only be used during component initialisation. To f ``` +## missing_context + +> Context was not set in a parent component + +The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in a parent component. + ## snippet_without_render_tag > Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 3764cc7040..1595cc72d3 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.39.8", + "version": "5.40.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index d655fb648a..1f1b0e7b5e 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -100,7 +100,7 @@ declare namespace $state { * you must reassign it. * * Example: - * ```ts + * ```svelte * * - * * ``` @@ -124,7 +124,7 @@ declare namespace $state { * To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: * * Example: - * ```ts + * ```svelte * + +
    +{#each await array as item} +
  • {item}
  • +{/each} +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js new file mode 100644 index 0000000000..8905ee4bf5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/_config.js @@ -0,0 +1,57 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const shift = document.querySelector('button'); + shift?.click(); + await tick(); + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` +

true

+ + + ` + ); + + const toggle = target.querySelector('button'); + toggle?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` +

true

+ + + ` + ); + + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` +

true

+ + + ` + ); + + shift?.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte new file mode 100644 index 0000000000..0b3b21f28c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-inner-after-outer/main.svelte @@ -0,0 +1,41 @@ + + + + {#if await foo()} +

{await bar()}

+ {/if} + + + + {#snippet pending()} +

loading...

+ {/snippet} +
+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js new file mode 100644 index 0000000000..50bb414afc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js @@ -0,0 +1,25 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + // We gotta wait a bit more in this test because of the macrotasks in App.svelte + function macrotask(t = 3) { + return new Promise((r) => setTimeout(r, t)); + } + + await macrotask(); + assert.htmlEqual(target.innerHTML, ' 1 | '); + + const [input] = target.querySelectorAll('input'); + + input.value = '1'; + input.dispatchEvent(new Event('input', { bubbles: true })); + await macrotask(); + assert.htmlEqual(target.innerHTML, ' 1 | '); + + input.value = '12'; + input.dispatchEvent(new Event('input', { bubbles: true })); + await macrotask(6); + assert.htmlEqual(target.innerHTML, ' 3 | 12'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte new file mode 100644 index 0000000000..dc4a157928 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte @@ -0,0 +1,34 @@ + + + + +{count} | {x} diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js new file mode 100644 index 0000000000..9c8b4fc6c0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [btn] = target.querySelectorAll('button'); + + flushSync(() => { + btn.click(); + }); + assert.htmlEqual( + target.innerHTML, + '
51423
51423' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte new file mode 100644 index 0000000000..60444e8978 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-loop/main.svelte @@ -0,0 +1,13 @@ + + +
+{#each arr as item, i (item)} + elements[i], (v) => elements[i] = v }>{item} +{/each} +
+{#each elements as elem} + {elem.textContent} +{/each} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte index d971566396..92a1f5ab94 100644 --- a/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/class-state-derived-private/main.svelte @@ -9,7 +9,8 @@ } get embiggened1() { - return this.#doubled; + const self = this; + return self.#doubled; } get embiggened2() { diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte new file mode 100644 index 0000000000..ba0e19e0a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/Component.svelte @@ -0,0 +1,5 @@ + + +{@render test?.()} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js new file mode 100644 index 0000000000..5f6a0172ca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/_config.js @@ -0,0 +1,10 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const btn = target.querySelector('button'); + flushSync(() => btn?.click()); + assert.htmlEqual(target.innerHTML, `

snip

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte new file mode 100644 index 0000000000..c2b75c362f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/const-snippet-reactive/main.svelte @@ -0,0 +1,15 @@ + + +{#snippet snip()} +

snip

+{/snippet} + + +{#if true} + {@const test = count % 2 === 0 ? undefined: snip} + +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte new file mode 100644 index 0000000000..3e39d5043e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte @@ -0,0 +1,7 @@ + + +

{message}

diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/_config.js b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js new file mode 100644 index 0000000000..4ae28e68bd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

hello

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte new file mode 100644 index 0000000000..8d3c50ba55 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js new file mode 100644 index 0000000000..66a42c08db --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + assert.equal(target.textContent, ' false'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte new file mode 100644 index 0000000000..40561218db --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte @@ -0,0 +1,4 @@ + +{rest.name} {'name' in rest} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte new file mode 100644 index 0000000000..d9f6d8a21c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-const/_expected.html @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte new file mode 100644 index 0000000000..1437ac8f14 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-const/main.svelte @@ -0,0 +1,10 @@ +{#if false} + {@const one = await 1} + {one} +{:else if false} + {@const two = await 2} + {two} +{:else} + {@const three = await 3} + {three} +{/if} diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html new file mode 100644 index 0000000000..ac7cd985f1 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/_expected.html @@ -0,0 +1 @@ +Loading... \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte new file mode 100644 index 0000000000..7e0d4c62ea --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-pending-prop-async/main.svelte @@ -0,0 +1,8 @@ +{#snippet pending()} + Loading... +{/snippet} + + + {@const data = await Promise.resolve('hello')} +

{data}

+
\ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js new file mode 100644 index 0000000000..05de37a8bd --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + mode: ['async'] +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html new file mode 100644 index 0000000000..605299c2ae --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/_expected.html @@ -0,0 +1 @@ +each else \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte b/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte new file mode 100644 index 0000000000..86e15c4e7e --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/each-body-async/main.svelte @@ -0,0 +1,11 @@ +{#each { length: 1 }} + {@const data = await Promise.resolve("each")} + {data} +{/each} + +{#each { length: 0 }} + should not see this +{:else} + {@const data = await Promise.resolve("else")} + {data} +{/each} \ No newline at end of file diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 2aedd1d923..0531d750a6 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -450,10 +450,20 @@ declare module 'svelte' { type NotFunction = T extends Function ? never : T; type MaybePromise = T | Promise; + /** + * Returns a `[get, set]` pair of functions for working with context in a type-safe way. + * + * `get` will throw an error if no parent component called `set`. + * + * @since 5.40.0 + */ + export function createContext(): [() => T, (context: T) => T]; /** * Retrieves the context that belongs to the closest parent component with the specified `key`. * Must be called during component initialisation. * + * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) is a type-safe alternative. + * * */ export function getContext(key: any): T; /** @@ -463,6 +473,8 @@ declare module 'svelte' { * * Like lifecycle functions, this must be called during component initialisation. * + * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) is a type-safe alternative. + * * */ export function setContext(key: any, context: T): T; /** @@ -3230,7 +3242,7 @@ declare namespace $state { * you must reassign it. * * Example: - * ```ts + * ```svelte * * - * * ``` @@ -3254,7 +3266,7 @@ declare namespace $state { * To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: * * Example: - * ```ts + * ```svelte *