Merge branch 'main' into oxlint

oxlint
Rich Harris 6 months ago
commit 2ba3a12ab6

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure infinite effect loops are cleared after flushing

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow `{#key NaN}`

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: detect store in each block expression regardless of AST shape

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: treat `<menu>` like `<ul>`/`<ol>` for a11y role checks

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: add vite-ignore comment inside dynamic crypto import

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: wrap JSDoc URLs in `@see` and `@link` tags

@ -0,0 +1,5 @@
---
'svelte': minor
---
feat: allow use of createContext when instantiating components programmatically

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: properly hydrate already-resolved async blocks

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: emit `each_key_duplicate` error in production

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: exit resolved async blocks on correct node when hydrating

@ -1,115 +0,0 @@
name: Update pkg.pr.new comment
on:
workflow_run:
workflows: ['Publish Any Commit']
types:
- completed
permissions:
pull-requests: write
jobs:
build:
name: 'Update comment'
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v7
with:
name: output
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- run: ls -R .
- name: 'Post or update comment'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const output = JSON.parse(fs.readFileSync('output.json', 'utf8'));
const bot_comment_identifier = `<!-- pkg.pr.new comment -->`;
const body = (number) => `${bot_comment_identifier}
[Playground](https://svelte.dev/playground?version=pr-${number})
\`\`\`
${output.packages.map((p) => `pnpm add https://pkg.pr.new/${p.name}@${number}`).join('\n')}
\`\`\`
`;
async function find_bot_comment(issue_number) {
if (!issue_number) return null;
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
});
return comments.data.find((comment) =>
comment.body.includes(bot_comment_identifier)
);
}
async function create_or_update_comment(issue_number) {
if (!issue_number) {
console.log('No issue number provided. Cannot post or update comment.');
return;
}
const existing_comment = await find_bot_comment(issue_number);
if (existing_comment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing_comment.id,
body: body(issue_number),
});
} else {
await github.rest.issues.createComment({
issue_number: issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body(issue_number),
});
}
}
async function log_publish_info() {
const svelte_package = output.packages.find(p => p.name === 'svelte');
const svelte_sha = svelte_package.url.replace(/^.+@([^@]+)$/, '$1');
console.log('\n' + '='.repeat(50));
console.log('Publish Information');
console.log('='.repeat(50));
console.log('\nPublished Packages:');
console.log(output.packages.map((p) => `${p.name} - pnpm add https://pkg.pr.new/${p.name}@${p.url.replace(/^.+@([^@]+)$/, '$1')}`).join('\n'));
if(svelte_sha){
console.log('\nPlayground URL:');
console.log(`\nhttps://svelte.dev/playground?version=commit-${svelte_sha}`)
}
console.log('\n' + '='.repeat(50));
}
if (output.event_name === 'pull_request') {
if (output.number) {
await create_or_update_comment(output.number);
}
} else if (output.event_name === 'push') {
const pull_requests = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${output.ref.replace('refs/heads/', '')}`,
});
if (pull_requests.data.length > 0) {
await create_or_update_comment(pull_requests.data[0].number);
} else {
console.log(
'No open pull request found for this push. Logging publish information to console:'
);
await log_publish_info();
}
}

@ -1,16 +1,51 @@
name: Publish Any Commit
on: [push, pull_request]
name: pkg.pr.new
on:
pull_request_target:
types: [opened, synchronize]
push:
branches: [main]
permissions: {}
jobs:
build:
permissions: {}
# This job determines the environment to use for the build job. It ensures that:
# - For pushes to main, we use the "Publish pkg.pr.new (maintainers)" environment.
# - For PRs from the same repository, we also use the "Publish pkg.pr.new (maintainers)" environment, since these are trusted.
# - For PRs from forks, we use the "Publish pkg.pr.new (external contributors)" environment, which requires manual approval by a maintainer before the build job can run.
# This protects us from running untrusted code while still allowing external contributors to use pkg.pr.new.
resolve-env:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.resolve.outputs.environment }}
steps:
- name: Determine environment
id: resolve
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
echo "environment=Publish pkg.pr.new (maintainers)" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then
echo "environment=Publish pkg.pr.new (maintainers)" >> "$GITHUB_OUTPUT"
else
echo "environment=Publish pkg.pr.new (external contributors)" >> "$GITHUB_OUTPUT"
fi
build:
needs: resolve-env
runs-on: ubuntu-latest
# This is the line that ensures forks require manual approval before running the build job
environment: ${{ needs.resolve-env.outputs.environment }}
# No permissions — this job runs user-controlled code
permissions: {}
steps:
- uses: actions/checkout@v6
with:
# For pull_request_target, we must explicitly check out the PR head.
# This is safe because the environment gate above has already fired —
# an org member has approved this specific commit for external PRs.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
@ -24,21 +59,161 @@ jobs:
run: pnpm build
- run: pnpx pkg-pr-new publish --comment=off --json output.json --compact --no-template './packages/svelte'
- name: Add metadata to output
- name: Upload output
uses: actions/upload-artifact@v4
with:
name: output
path: ./output.json
# Sanitizes the untrusted output from the build job before it's consumed by
# jobs with elevated permissions. This ensures that only known package names
# and valid SHA prefixes make it through.
sanitize:
needs: build
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Download artifact
uses: actions/download-artifact@v7
with:
name: output
- name: Sanitize output
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const raw = JSON.parse(fs.readFileSync('output.json', 'utf8'));
const ALLOWED_PACKAGES = new Set(['svelte']);
const SHA_PATTERN = /^[0-9a-f]{7}$/;
const packages = (raw.packages || [])
.filter(p => {
if (!ALLOWED_PACKAGES.has(p.name)) {
console.log(`Skipping unexpected package: ${JSON.stringify(p.name)}`);
return false;
}
const sha = p.url?.replace(/^.+@([^@]+)$/, '$1');
if (!sha || !SHA_PATTERN.test(sha)) {
console.log(`Skipping package with invalid SHA: ${JSON.stringify(p.url)}`);
return false;
}
return true;
})
.map(p => ({
name: p.name,
sha: p.url.replace(/^.+@([^@]+)$/, '$1'),
}));
fs.writeFileSync('sanitized-output.json', JSON.stringify({ packages }), 'utf8');
- name: Upload sanitized output
uses: actions/upload-artifact@v4
with:
name: sanitized-output
path: ./sanitized-output.json
comment:
needs: sanitize
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Download sanitized artifact
uses: actions/download-artifact@v7
with:
name: sanitized-output
- name: Post or update comment
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const output = JSON.parse(fs.readFileSync('output.json', 'utf8'));
output.number = context.issue.number;
output.event_name = context.eventName;
output.ref = context.ref;
fs.writeFileSync('output.json', JSON.stringify(output), 'utf8');
- name: Upload output
uses: actions/upload-artifact@v6
const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8'));
if (packages.length === 0) {
console.log('No valid packages found. Skipping comment.');
return;
}
// Issue number from trusted event context, never from the artifact
const issue_number = context.issue.number;
const bot_comment_identifier = `<!-- pkg.pr.new comment -->`;
const body = `${bot_comment_identifier}
[Playground](https://svelte.dev/playground?version=pr-${issue_number})
\`\`\`
${packages.map(p => `pnpm add https://pkg.pr.new/${p.name}@${issue_number}`).join('\n')}
\`\`\`
`;
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
});
const existing = comments.data.find(c => c.body.includes(bot_comment_identifier));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body,
});
}
log:
needs: sanitize
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Download sanitized artifact
uses: actions/download-artifact@v7
with:
name: output
path: ./output.json
name: sanitized-output
- name: Log publish info
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8'));
if (packages.length === 0) {
console.log('No valid packages found.');
return;
}
- run: ls -R .
console.log('\n' + '='.repeat(50));
console.log('Publish Information');
console.log('='.repeat(50));
for (const p of packages) {
console.log(`${p.name} - pnpm add https://pkg.pr.new/${p.name}@${p.sha}`);
}
const svelte = packages.find(p => p.name === 'svelte');
if (svelte) {
console.log(`\nPlayground: https://svelte.dev/playground?version=commit-${svelte.sha}`);
}
console.log('='.repeat(50));

@ -97,6 +97,32 @@ import { createContext } from 'svelte';
export const [getUserContext, setUserContext] = createContext<User>();
```
When writing [component tests](testing#Unit-and-component-tests-with-Vitest-Component-testing), it can be useful to create a wrapper component that sets the context in order to check the behaviour of a component that uses it. As of version 5.49, you can do this sort of thing:
```js
import { mount, unmount } from 'svelte';
import { expect, test } from 'vitest';
import { setUserContext } from './context';
import MyComponent from './MyComponent.svelte';
test('MyComponent', () => {
function Wrapper(...args) {
setUserContext({ name: 'Bob' });
return MyComponent(...args);
}
const component = mount(Wrapper, {
target: document.body
});
expect(document.body.innerHTML).toBe('<h1>Hello Bob!</h1>');
unmount(component);
});
```
This approach also works with [`hydrate`](imperative-component-api#hydrate) and [`render`](imperative-component-api#render).
## Replacing global state
When you have state shared by many different components, you might be tempted to put it in its own module and just import it wherever it's needed:

@ -181,7 +181,7 @@ export default defineConfig({
/* ... */
],
test: {
// If you are testing components client-side, you need to setup a DOM environment.
// If you are testing components client-side, you need to set up a DOM environment.
// If not all your files should have this environment, you can use a
// `// @vitest-environment jsdom` comment at the top of the test files instead.
environment: 'jsdom'

@ -778,9 +778,9 @@ In Svelte 4, doing the following triggered reactivity:
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
### Touch 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()`.
When using `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()`.
In the very rare cases that you need to prevent these event defaults, you should use [`on`](/docs/svelte/svelte-events#on) instead (for example inside an action).

@ -1,5 +1,17 @@
# svelte
## 5.49.2
### Patch Changes
- chore: remove SvelteKit data attributes from elements.d.ts ([#17613](https://github.com/sveltejs/svelte/pull/17613))
- fix: avoid erroneous async derived expressions for blocks ([#17604](https://github.com/sveltejs/svelte/pull/17604))
- fix: avoid Cloudflare warnings about not having the "node:crypto" module ([#17612](https://github.com/sveltejs/svelte/pull/17612))
- fix: reschedule effects inside unskipped branches ([#17604](https://github.com/sveltejs/svelte/pull/17604))
## 5.49.1
### Patch Changes

@ -851,23 +851,6 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
readonly 'bind:offsetWidth'?: number | undefined | null;
readonly 'bind:offsetHeight'?: number | undefined | null;
// SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
// allow any data- attribute
[key: `data-${string}`]: any;

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.49.1",
"version": "5.49.2",
"type": "module",
"types": "./types/index.d.ts",
"engines": {

@ -16,7 +16,7 @@ declare module '*.svelte' {
* let count = $state(0);
* ```
*
* https://svelte.dev/docs/svelte/$state
* @see {@link https://svelte.dev/docs/svelte/$state Documentation}
*
* @param initial The initial value
*/
@ -126,7 +126,7 @@ declare namespace $state {
* </button>
* ```
*
* https://svelte.dev/docs/svelte/$state#$state.raw
* @see {@link https://svelte.dev/docs/svelte/$state#$state.raw Documentation}
*
* @param initial The initial value
*/
@ -147,7 +147,7 @@ declare namespace $state {
* </script>
* ```
*
* https://svelte.dev/docs/svelte/$state#$state.snapshot
* @see {@link https://svelte.dev/docs/svelte/$state#$state.snapshot Documentation}
*
* @param state The value to snapshot
*/
@ -187,7 +187,7 @@ declare namespace $state {
* let double = $derived(count * 2);
* ```
*
* https://svelte.dev/docs/svelte/$derived
* @see {@link https://svelte.dev/docs/svelte/$derived Documentation}
*
* @param expression The derived state expression
*/
@ -209,7 +209,7 @@ declare namespace $derived {
* });
* ```
*
* https://svelte.dev/docs/svelte/$derived#$derived.by
* @see {@link https://svelte.dev/docs/svelte/$derived#$derived.by Documentation}
*/
export function by<T>(fn: () => T): T;
@ -251,7 +251,7 @@ declare namespace $derived {
*
* Does not run during server-side rendering.
*
* https://svelte.dev/docs/svelte/$effect
* @see {@link https://svelte.dev/docs/svelte/$effect Documentation}
* @param fn The function to execute
*/
declare function $effect(fn: () => void | (() => void)): void;
@ -270,7 +270,7 @@ declare namespace $effect {
*
* Does not run during server-side rendering.
*
* https://svelte.dev/docs/svelte/$effect#$effect.pre
* @see {@link https://svelte.dev/docs/svelte/$effect#$effect.pre Documentation}
* @param fn The function to execute
*/
export function pre(fn: () => void | (() => void)): void;
@ -278,7 +278,7 @@ declare namespace $effect {
/**
* Returns the number of promises that are pending in the current boundary, not including child boundaries.
*
* https://svelte.dev/docs/svelte/$effect#$effect.pending
* @see {@link https://svelte.dev/docs/svelte/$effect#$effect.pending Documentation}
*/
export function pending(): number;
@ -300,7 +300,7 @@ declare namespace $effect {
*
* This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects.
*
* https://svelte.dev/docs/svelte/$effect#$effect.tracking
* @see {@link https://svelte.dev/docs/svelte/$effect#$effect.tracking Documentation}
*/
export function tracking(): boolean;
@ -328,7 +328,7 @@ declare namespace $effect {
* <button onclick={() => cleanup()}>cleanup</button>
* ```
*
* https://svelte.dev/docs/svelte/$effect#$effect.root
* @see {@link https://svelte.dev/docs/svelte/$effect#$effect.root Documentation}
*/
export function root(fn: () => void | (() => void)): () => void;
@ -364,7 +364,7 @@ declare namespace $effect {
* let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();
* ```
*
* https://svelte.dev/docs/svelte/$props
* @see {@link https://svelte.dev/docs/svelte/$props Documentation}
*/
declare function $props(): any;
@ -410,7 +410,7 @@ declare namespace $props {
* let { propName = $bindable() }: { propName: boolean } = $props();
* ```
*
* https://svelte.dev/docs/svelte/$bindable
* @see {@link https://svelte.dev/docs/svelte/$bindable Documentation}
*/
declare function $bindable<T>(fallback?: T): T;
@ -456,7 +456,7 @@ declare namespace $bindable {
* $inspect(x, y).with(() => { debugger; });
* ```
*
* https://svelte.dev/docs/svelte/$inspect
* @see {@link https://svelte.dev/docs/svelte/$inspect Documentation}
*/
declare function $inspect<T extends any[]>(
...values: T
@ -522,7 +522,7 @@ declare namespace $inspect {
*
* Only available inside custom element components, and only on the client-side.
*
* https://svelte.dev/docs/svelte/$host
* @see {@link https://svelte.dev/docs/svelte/$host Documentation}
*/
declare function $host<El extends HTMLElement = HTMLElement>(): El;

@ -568,7 +568,7 @@ function read_attribute_value(parser) {
}
/**
* https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
* @see {@link https://www.w3.org/TR/css-syntax-3/#ident-token-diagram CSS Syntax Module Level 3}
* @param {Parser} parser
*/
function read_identifier(parser) {

@ -174,6 +174,7 @@ export const input_type_to_implicit_role = new Map([
export const a11y_non_interactive_element_to_interactive_role_exceptions = {
ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
menu: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
table: ['grid'],
td: ['gridcell'],

@ -167,7 +167,7 @@ export function check_element(node, context) {
if (
current_role === get_implicit_role(node.name, attribute_map) &&
// <ul role="list"> is ok because CSS list-style:none removes the semantics and this is a way to bring them back
!['ul', 'ol', 'li'].includes(node.name) &&
!['ul', 'ol', 'li', 'menu'].includes(node.name) &&
// <a role="link" /> is ok because without href the a tag doesn't have a role of link
!(node.name === 'a' && !attribute_map.has('href'))
) {

@ -166,6 +166,7 @@ export function client_component(analysis, options) {
in_constructor: false,
instance_level_snippets: [],
module_level_snippets: [],
is_standalone: false,
// these are set inside the `Fragment` visitor, and cannot be used until then
init: /** @type {any} */ (null),

@ -83,6 +83,9 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly instance_level_snippets: VariableDeclaration[];
/** Snippets hoisted to the module */
readonly module_level_snippets: VariableDeclaration[];
/** True if the current node is a) a component or render tag and b) the sole child of a block */
readonly is_standalone: boolean;
}
export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>;

@ -101,15 +101,11 @@ export function EachBlock(node, context) {
}
// If the array is a store expression, we need to invalidate it when the array is changed.
// This doesn't catch all cases, but all the ones that Svelte 4 catches, too.
let store_to_invalidate = '';
if (node.expression.type === 'Identifier' || node.expression.type === 'MemberExpression') {
const id = object(node.expression);
if (id) {
const binding = context.state.scope.get(id.name);
if (binding?.kind === 'store_sub') {
store_to_invalidate = id.name;
}
for (const binding of node.metadata.expression.dependencies) {
if (binding.kind === 'store_sub') {
store_to_invalidate = binding.node.name;
break;
}
}
@ -312,10 +308,10 @@ export function EachBlock(node, context) {
declarations.push(b.let(node.index, index));
}
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
const get_collection = b.thunk(collection, node.metadata.expression.has_await);
const thunk = is_async ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
const get_collection = b.thunk(collection, has_await);
const thunk = has_await ? b.thunk(b.call('$.get', b.id('$$collection'))) : get_collection;
const render_args = [b.id('$$anchor'), item];
if (uses_index || collection_id) render_args.push(index);
@ -338,19 +334,18 @@ export function EachBlock(node, context) {
const statements = [add_svelte_meta(b.call('$.each', ...args), node, 'each')];
if (dev && node.metadata.keyed) {
statements.unshift(b.stmt(b.call('$.validate_each_keys', thunk, key_function)));
}
if (is_async) {
if (node.metadata.expression.is_async()) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
b.array([get_collection]),
b.arrow([context.state.node, b.id('$$collection')], b.block(statements))
has_await ? b.array([get_collection]) : b.void0,
b.arrow(
has_await ? [context.state.node, b.id('$$collection')] : [context.state.node],
b.block(statements)
)
)
)
);

@ -120,34 +120,35 @@ export function Fragment(node, context) {
state.init.unshift(b.var(id, b.call('$.text')));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else if (is_standalone) {
// no need to create a template, we can just use the existing block's anchor
process_children(trimmed, () => b.id('$$anchor'), false, {
...context,
state: { ...state, is_standalone }
});
} else {
if (is_standalone) {
// no need to create a template, we can just use the existing block's anchor
process_children(trimmed, () => b.id('$$anchor'), false, { ...context, state });
} else {
/** @type {(is_text: boolean) => Expression} */
const expression = (is_text) => b.call('$.first_child', id, is_text && b.true);
process_children(trimmed, expression, false, { ...context, state });
/** @type {(is_text: boolean) => Expression} */
const expression = (is_text) => b.call('$.first_child', id, is_text && b.true);
let flags = TEMPLATE_FRAGMENT;
process_children(trimmed, expression, false, { ...context, state });
if (state.template.needs_import_node) {
flags |= TEMPLATE_USE_IMPORT_NODE;
}
let flags = TEMPLATE_FRAGMENT;
if (state.template.nodes.length === 1 && state.template.nodes[0].type === 'comment') {
// special case — we can use `$.comment` instead of creating a unique template
state.init.unshift(b.var(id, b.call('$.comment')));
} else {
const template = transform_template(state, namespace, flags);
state.hoisted.push(b.var(template_name, template));
if (state.template.needs_import_node) {
flags |= TEMPLATE_USE_IMPORT_NODE;
}
state.init.unshift(b.var(id, b.call(template_name)));
}
if (state.template.nodes.length === 1 && state.template.nodes[0].type === 'comment') {
// special case — we can use `$.comment` instead of creating a unique template
state.init.unshift(b.var(id, b.call('$.comment')));
} else {
const template = transform_template(state, namespace, flags);
state.hoisted.push(b.var(template_name, template));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
state.init.unshift(b.var(id, b.call(template_name)));
}
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
}
}

@ -11,10 +11,11 @@ import { build_expression } from './shared/utils.js';
export function HtmlTag(node, context) {
context.state.template.push_comment();
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
const has_blockers = node.metadata.expression.has_blockers();
const expression = build_expression(context, node.expression, node.metadata.expression);
const html = is_async ? b.call('$.get', b.id('$$html')) : expression;
const html = has_await ? b.call('$.get', b.id('$$html')) : expression;
const is_svg = context.state.metadata.namespace === 'svg';
const is_mathml = context.state.metadata.namespace === 'mathml';
@ -31,15 +32,18 @@ export function HtmlTag(node, context) {
);
// push into init, so that bindings run afterwards, which might trigger another run and override hydration
if (is_async) {
if (has_await || has_blockers) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
b.array([b.thunk(expression, node.metadata.expression.has_await)]),
b.arrow([context.state.node, b.id('$$html')], b.block([statement]))
has_await ? b.array([b.thunk(expression, true)]) : b.void0,
b.arrow(
has_await ? [context.state.node, b.id('$$html')] : [context.state.node],
b.block([statement])
)
)
)
);

@ -25,10 +25,11 @@ export function IfBlock(node, context) {
statements.push(b.var(alternate_id, b.arrow([b.id('$$anchor')], alternate)));
}
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
const has_blockers = node.metadata.expression.has_blockers();
const expression = build_expression(context, node.test, node.metadata.expression);
const test = is_async ? b.call('$.get', b.id('$$condition')) : expression;
const test = has_await ? b.call('$.get', b.id('$$condition')) : expression;
/** @type {Expression[]} */
const args = [
@ -72,15 +73,18 @@ export function IfBlock(node, context) {
statements.push(add_svelte_meta(b.call('$.if', ...args), node, 'if'));
if (is_async) {
if (has_await || has_blockers) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
b.array([b.thunk(expression, node.metadata.expression.has_await)]),
b.arrow([context.state.node, b.id('$$condition')], b.block(statements))
has_await ? b.array([b.thunk(expression, true)]) : b.void0,
b.arrow(
has_await ? [context.state.node, b.id('$$condition')] : [context.state.node],
b.block(statements)
)
)
)
);

@ -11,29 +11,35 @@ import { build_expression, add_svelte_meta } from './shared/utils.js';
export function KeyBlock(node, context) {
context.state.template.push_comment();
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
const has_blockers = node.metadata.expression.has_blockers();
const expression = build_expression(context, node.expression, node.metadata.expression);
const key = b.thunk(is_async ? b.call('$.get', b.id('$$key')) : expression);
const key = b.thunk(has_await ? b.call('$.get', b.id('$$key')) : expression);
const body = /** @type {Expression} */ (context.visit(node.fragment));
let statement = add_svelte_meta(
const statement = add_svelte_meta(
b.call('$.key', context.state.node, key, b.arrow([b.id('$$anchor')], body)),
node,
'key'
);
if (is_async) {
statement = b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
b.array([b.thunk(expression, node.metadata.expression.has_await)]),
b.arrow([context.state.node, b.id('$$key')], b.block([statement]))
if (has_await || has_blockers) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
has_await ? b.array([b.thunk(expression, true)]) : b.void0,
b.arrow(
has_await ? [context.state.node, b.id('$$key')] : [context.state.node],
b.block([statement])
)
)
)
);
} else {
context.state.init.push(statement);
}
context.state.init.push(statement);
}

@ -85,6 +85,10 @@ export function RenderTag(node, context) {
)
)
);
if (context.state.is_standalone) {
context.state.init.push(b.stmt(b.call('$.next')));
}
} else {
context.state.init.push(statements.length === 1 ? statements[0] : b.block(statements));
}

@ -93,10 +93,11 @@ export function SvelteElement(node, context) {
);
}
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
const has_blockers = node.metadata.expression.has_blockers();
const expression = /** @type {Expression} */ (context.visit(node.tag));
const get_tag = b.thunk(is_async ? b.call('$.get', b.id('$$tag')) : expression);
const get_tag = b.thunk(has_await ? b.call('$.get', b.id('$$tag')) : expression);
/** @type {Statement[]} */
const inner = inner_context.state.init;
@ -139,15 +140,18 @@ export function SvelteElement(node, context) {
)
);
if (is_async) {
if (has_await || has_blockers) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
node.metadata.expression.blockers(),
b.array([b.thunk(expression, node.metadata.expression.has_await)]),
b.arrow([context.state.node, b.id('$$tag')], b.block(statements))
has_await ? b.array([b.thunk(expression, true)]) : b.void0,
b.arrow(
has_await ? [context.state.node, b.id('$$tag')] : [context.state.node],
b.block(statements)
)
)
)
);

@ -461,7 +461,7 @@ export function build_component(node, component_name, loc, context) {
memoizer.check_blockers(node.metadata.expression);
}
const statements = [...snippet_declarations, ...memoizer.deriveds(context.state.analysis.runes)];
let statements = [...snippet_declarations, ...memoizer.deriveds(context.state.analysis.runes)];
if (is_component_dynamic) {
const prev = fn;
@ -515,15 +515,21 @@ export function build_component(node, component_name, loc, context) {
const blockers = memoizer.blockers();
if (async_values || blockers) {
return b.stmt(
b.call(
'$.async',
anchor,
blockers,
async_values,
b.arrow([b.id('$$anchor'), ...memoizer.async_ids()], b.block(statements))
statements = [
b.stmt(
b.call(
'$.async',
anchor,
blockers,
async_values,
b.arrow([b.id('$$anchor'), ...memoizer.async_ids()], b.block(statements))
)
)
);
];
if (context.state.is_standalone) {
statements.push(b.stmt(b.call('$.next')));
}
}
return statements.length > 1 ? b.block(statements) : statements[0];

@ -41,7 +41,6 @@ import { TitleElement } from './visitors/TitleElement.js';
import { UpdateExpression } from './visitors/UpdateExpression.js';
import { VariableDeclaration } from './visitors/VariableDeclaration.js';
import { SvelteBoundary } from './visitors/SvelteBoundary.js';
import { call_component_renderer } from './visitors/shared/utils.js';
/** @type {Visitors} */
const global_visitors = {
@ -105,7 +104,7 @@ export function server_component(analysis, options) {
namespace: options.namespace,
preserve_whitespace: options.preserveWhitespace,
state_fields: new Map(),
skip_hydration_boundaries: false,
is_standalone: false,
is_instance: false
};
@ -260,7 +259,13 @@ export function server_component(analysis, options) {
if (should_inject_context) {
component_block = b.block([
call_component_renderer(component_block, dev && b.id(component_name))
b.stmt(
b.call(
'$$renderer.component',
b.arrow([b.id('$$renderer')], component_block, false),
dev && b.id(component_name)
)
)
]);
}

@ -26,7 +26,8 @@ export interface ComponentServerTransformState extends ServerTransformState {
readonly template: Array<Statement | Expression>;
readonly namespace: Namespace;
readonly preserve_whitespace: boolean;
readonly skip_hydration_boundaries: boolean;
/** True if the current node is a) a component or render tag and b) the sole child of a block */
readonly is_standalone: boolean;
/** Transformed async `{@const }` declarations (if any) and those coming after them */
async_consts?: {
id: Identifier;

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
import { block_close, create_async_block } from './shared/utils.js';
import { block_close, create_child_block } from './shared/utils.js';
/**
* @param {AST.AwaitBlock} node
@ -25,13 +25,12 @@ export function AwaitBlock(node, context) {
)
);
if (node.metadata.expression.is_async()) {
statement = create_async_block(
b.block([statement]),
context.state.template.push(
...create_child_block(
[statement],
node.metadata.expression.blockers(),
node.metadata.expression.has_await
);
}
context.state.template.push(statement, block_close);
),
block_close
);
}

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
import { block_close, block_open, block_open_else, create_async_block } from './shared/utils.js';
import { block_close, block_open, block_open_else, create_child_block } from './shared/utils.js';
/**
* @param {AST.EachBlock} node
@ -18,8 +18,8 @@ export function EachBlock(node, context) {
const array_id = state.scope.root.unique('each_array');
/** @type {Statement} */
let block = b.block([b.const(array_id, b.call('$.ensure_array_like', collection))]);
/** @type {Statement[]} */
let statements = [b.const(array_id, b.call('$.ensure_array_like', collection))];
/** @type {Statement[]} */
const each = [];
@ -53,7 +53,7 @@ export function EachBlock(node, context) {
fallback.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open_else)));
block.body.push(
statements.push(
b.if(
b.binary('!==', b.member(array_id, 'length'), b.literal(0)),
b.block([open, for_loop]),
@ -62,19 +62,15 @@ export function EachBlock(node, context) {
);
} else {
state.template.push(block_open);
block.body.push(for_loop);
statements.push(for_loop);
}
if (node.metadata.expression.is_async()) {
state.template.push(
create_async_block(
block,
node.metadata.expression.blockers(),
node.metadata.expression.has_await
),
block_close
);
} else {
state.template.push(...block.body, block_close);
}
state.template.push(
...create_child_block(
statements,
node.metadata.expression.blockers(),
node.metadata.expression.has_await
),
block_close
);
}

@ -28,7 +28,7 @@ export function Fragment(node, context) {
init: [],
template: [],
namespace,
skip_hydration_boundaries: is_standalone,
is_standalone,
async_consts: undefined
};

@ -2,25 +2,24 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
import { block_close, block_open, create_push } from './shared/utils.js';
import { create_child_block } from './shared/utils.js';
/**
* @param {AST.HtmlTag} node
* @param {ComponentContext} context
*/
export function HtmlTag(node, context) {
const expression = /** @type {Expression} */ (context.visit(node.expression));
const call = b.call('$.html', expression);
const expression = b.call('$.html', /** @type {Expression} */ (context.visit(node.expression)));
const has_await = node.metadata.expression.has_await;
if (has_await) {
context.state.template.push(block_open);
}
context.state.template.push(create_push(call, node.metadata.expression, true));
if (has_await) {
context.state.template.push(block_close);
if (node.metadata.expression.is_async()) {
context.state.template.push(
...create_child_block(
[b.stmt(b.call('$$renderer.push', expression))],
node.metadata.expression.blockers(),
node.metadata.expression.has_await
)
);
} else {
context.state.template.push(expression);
}
}

@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
import { block_close, block_open, block_open_else, create_async_block } from './shared/utils.js';
import { block_close, block_open, block_open_else, create_child_block } from './shared/utils.js';
/**
* @param {AST.IfBlock} node
@ -23,17 +23,12 @@ export function IfBlock(node, context) {
/** @type {Statement} */
let statement = b.if(test, consequent, alternate);
const is_async = node.metadata.expression.is_async();
const has_await = node.metadata.expression.has_await;
if (is_async || has_await) {
statement = create_async_block(
b.block([statement]),
context.state.template.push(
...create_child_block(
[statement],
node.metadata.expression.blockers(),
!!has_await
);
}
context.state.template.push(statement, block_close);
node.metadata.expression.has_await
),
block_close
);
}

@ -1,5 +1,4 @@
/** @import { Expression } from 'estree' */
/** @import { Location } from 'locate-character' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext, ComponentServerTransformState } from '../types.js' */
/** @import { Scope } from '../../../scope.js' */
@ -8,13 +7,7 @@ import { dev, locator } from '../../../../state.js';
import * as b from '#compiler/builders';
import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
import { build_element_attributes, prepare_element_spread_object } from './shared/element.js';
import {
process_children,
build_template,
create_child_block,
PromiseOptimiser,
create_async_block
} from './shared/utils.js';
import { process_children, build_template, PromiseOptimiser } from './shared/utils.js';
import { is_customizable_select_element } from '../../../nodes.js';
/**
@ -66,17 +59,9 @@ export function RegularElement(node, context) {
b.literal(`</${node.name}>`)
);
// TODO this is a real edge case, would be good to DRY this out
if (optimiser.expressions.length > 0) {
context.state.template.push(
create_child_block(
b.block([optimiser.apply(), ...state.init, ...build_template(state.template)])
)
);
} else {
context.state.init.push(...state.init);
context.state.template.push(...state.template);
}
context.state.template.push(
...optimiser.render([...state.init, ...build_template(state.template)])
);
return;
}
@ -130,13 +115,7 @@ export function RegularElement(node, context) {
const statement = b.stmt(b.call('$$renderer.select', attributes, fn, ...rest));
if (optimiser.expressions.length > 0) {
context.state.template.push(
create_child_block(b.block([optimiser.apply(), ...state.init, statement]))
);
} else {
context.state.template.push(...state.init, statement);
}
context.state.template.push(...optimiser.render([...state.init, statement]));
return;
}
@ -183,13 +162,7 @@ export function RegularElement(node, context) {
const statement = b.stmt(b.call('$$renderer.option', attributes, body, ...rest));
if (optimiser.expressions.length > 0) {
context.state.template.push(
create_child_block(b.block([optimiser.apply(), ...state.init, statement]))
);
} else {
context.state.template.push(...state.init, statement);
}
context.state.template.push(...optimiser.render([...state.init, statement]));
return;
}
@ -235,19 +208,9 @@ export function RegularElement(node, context) {
}
if (optimiser.is_async()) {
let statements = [...state.init, ...build_template(state.template)];
if (optimiser.has_await) {
statements = [create_child_block(b.block([optimiser.apply(), ...statements]))];
}
const blockers = optimiser.blockers();
if (blockers.elements.length > 0) {
statements = [create_async_block(b.block(statements), blockers, false, false)];
}
context.state.template.push(...statements);
context.state.template.push(
...optimiser.render([...state.init, ...build_template(state.template)])
);
} else {
context.state.init.push(...state.init);
context.state.template.push(...state.template);

@ -3,7 +3,7 @@
/** @import { ComponentContext } from '../types.js' */
import { unwrap_optional } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { create_async_block, empty_comment, PromiseOptimiser } from './shared/utils.js';
import { empty_comment, PromiseOptimiser } from './shared/utils.js';
/**
* @param {AST.RenderTag} node
@ -35,17 +35,9 @@ export function RenderTag(node, context) {
)
);
if (optimiser.is_async()) {
statement = create_async_block(
b.block([optimiser.apply(), statement]),
optimiser.blockers(),
optimiser.has_await
);
}
context.state.template.push(statement);
context.state.template.push(...optimiser.render_block([statement]));
if (!context.state.skip_hydration_boundaries) {
if (!context.state.is_standalone) {
context.state.template.push(empty_comment);
}
}

@ -5,7 +5,6 @@ import * as b from '#compiler/builders';
import {
build_attribute_value,
PromiseOptimiser,
create_async_block,
block_open,
block_close
} from './shared/utils.js';
@ -65,13 +64,5 @@ export function SlotElement(node, context) {
fallback
);
const statement = optimiser.is_async()
? create_async_block(
b.block([optimiser.apply(), b.stmt(slot)]),
optimiser.blockers(),
optimiser.has_await
)
: b.stmt(slot);
context.state.template.push(block_open, statement, block_close);
context.state.template.push(block_open, ...optimiser.render_block([b.stmt(slot)]), block_close);
}

@ -1,4 +1,3 @@
/** @import { Location } from 'locate-character' */
/** @import { BlockStatement, Expression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
@ -6,12 +5,7 @@ import { dev, locator } from '../../../../state.js';
import * as b from '#compiler/builders';
import { determine_namespace_for_children } from '../../utils.js';
import { build_element_attributes } from './shared/element.js';
import {
build_template,
create_async_block,
create_child_block,
PromiseOptimiser
} from './shared/utils.js';
import { build_template, create_child_block, PromiseOptimiser } from './shared/utils.js';
/**
* @param {AST.SvelteElement} node
@ -67,36 +61,29 @@ export function SvelteElement(node, context) {
const attributes = b.block([...state.init, ...build_template(state.template)]);
const children = /** @type {BlockStatement} */ (context.visit(node.fragment, state));
/** @type {Statement} */
let statement = b.stmt(
b.call(
'$.element',
b.id('$$renderer'),
tag,
attributes.body.length > 0 && b.thunk(attributes),
children.body.length > 0 && b.thunk(children)
)
statements.push(
...optimiser.render([
b.stmt(
b.call(
'$.element',
b.id('$$renderer'),
tag,
attributes.body.length > 0 && b.thunk(attributes),
children.body.length > 0 && b.thunk(children)
)
)
])
);
if (optimiser.expressions.length > 0) {
statement = create_child_block(b.block([optimiser.apply(), statement]));
}
statements.push(statement);
if (dev) {
statements.push(b.stmt(b.call('$.pop_element')));
}
if (node.metadata.expression.is_async()) {
statements = [
create_async_block(
b.block(statements),
node.metadata.expression.blockers(),
node.metadata.expression.has_await
)
];
}
context.state.template.push(...statements);
context.state.template.push(
...create_child_block(
statements,
node.metadata.expression.blockers(),
node.metadata.expression.has_await
)
);
}

@ -1,12 +1,7 @@
/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */
import {
empty_comment,
build_attribute_value,
create_async_block,
PromiseOptimiser
} from './utils.js';
import { empty_comment, build_attribute_value, PromiseOptimiser } from './utils.js';
import * as b from '#compiler/builders';
import { is_element_node } from '../../../../nodes.js';
import { dev } from '../../../../../state.js';
@ -106,10 +101,16 @@ export function build_inline_component(node, expression, context) {
}
push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
} else if (attribute.type === 'BindDirective') {
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
optimiser.check_blockers(attribute.metadata.expression);
if (attribute.name === 'this') {
// bind:this is client-only, but we still need to check for blockers to ensure
// the server generates matching hydration markers if the client wraps in $.async
continue;
}
if (attribute.expression.type === 'SequenceExpression') {
const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression))
.expressions;
@ -325,32 +326,16 @@ export function build_inline_component(node, expression, context) {
optimiser.check_blockers(node.metadata.expression);
}
const is_async = optimiser.is_async();
if (is_async) {
statement = create_async_block(
b.block([
optimiser.apply(),
dynamic && custom_css_props.length === 0
? b.stmt(b.call('$$renderer.push', empty_comment))
: b.empty,
statement
]),
optimiser.blockers(),
optimiser.has_await
);
} else if (dynamic && custom_css_props.length === 0) {
context.state.template.push(empty_comment);
}
context.state.template.push(statement);
context.state.template.push(
...optimiser.render_block([
dynamic && custom_css_props.length === 0
? b.stmt(b.call('$$renderer.push', empty_comment))
: b.empty,
statement
])
);
if (
!is_async &&
!context.state.skip_hydration_boundaries &&
custom_css_props.length === 0 &&
optimiser.expressions.length === 0
) {
if (!optimiser.is_async() && !context.state.is_standalone && custom_css_props.length === 0) {
context.state.template.push(empty_comment);
}
}

@ -81,7 +81,19 @@ export function process_children(nodes, { visit, state }) {
flush();
const expression = /** @type {Expression} */ (visit(node.expression));
state.template.push(create_push(b.call('$.escape', expression), node.metadata.expression));
let call = b.call(
'$$renderer.push',
b.thunk(b.call('$.escape', expression), node.metadata.expression.has_await)
);
const blockers = node.metadata.expression.blockers();
if (blockers.elements.length > 0) {
call = b.call('$$renderer.async', blockers, b.arrow([b.id('$$renderer')], call));
}
state.template.push(b.stmt(call));
} else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') {
sequence.push(node);
} else {
@ -262,72 +274,20 @@ export function build_getter(node, state) {
}
/**
* Creates a `$$renderer.child(...)` expression statement
* @param {BlockStatement | Expression} body
* @returns {Statement}
*/
export function create_child_block(body) {
return b.stmt(b.call('$$renderer.child', b.arrow([b.id('$$renderer')], body, true)));
}
/**
* Creates a `$$renderer.async(...)` expression statement
* @param {BlockStatement | Expression} body
* @param {Statement[]} statements
* @param {ArrayExpression} blockers
* @param {boolean} has_await
* @param {boolean} needs_hydration_markers
*/
export function create_async_block(
body,
blockers = b.array([]),
has_await = true,
needs_hydration_markers = true
) {
return b.stmt(
b.call(
needs_hydration_markers ? '$$renderer.async_block' : '$$renderer.async',
blockers,
b.arrow([b.id('$$renderer')], body, has_await)
)
);
}
/**
* @param {Expression} expression
* @param {ExpressionMetadata} metadata
* @param {boolean} needs_hydration_markers
* @returns {Expression | Statement}
*/
export function create_push(expression, metadata, needs_hydration_markers = false) {
if (metadata.is_async()) {
let statement = b.stmt(b.call('$$renderer.push', b.thunk(expression, metadata.has_await)));
const blockers = metadata.blockers();
if (blockers.elements.length > 0) {
statement = create_async_block(
b.block([statement]),
blockers,
false,
needs_hydration_markers
);
}
return statement;
export function create_child_block(statements, blockers, has_await) {
if (blockers.elements.length === 0 && !has_await) {
return statements;
}
return expression;
}
const fn = b.arrow([b.id('$$renderer')], b.block(statements), has_await);
/**
* @param {BlockStatement | Expression} body
* @param {Identifier | false} component_fn_id
* @returns {Statement}
*/
export function call_component_renderer(body, component_fn_id) {
return b.stmt(
b.call('$$renderer.component', b.arrow([b.id('$$renderer')], body, false), component_fn_id)
);
return blockers.elements.length > 0
? [b.stmt(b.call('$$renderer.async_block', blockers, fn))]
: [b.stmt(b.call('$$renderer.child_block', fn))];
}
/**
@ -373,7 +333,7 @@ export class PromiseOptimiser {
}
}
apply() {
#apply() {
if (this.expressions.length === 0) {
return b.empty;
}
@ -403,4 +363,38 @@ export class PromiseOptimiser {
is_async() {
return this.expressions.length > 0 || this.#blockers.size > 0;
}
/**
* @param {Statement[]} statements
* @returns {Statement[]}
*/
render(statements) {
if (!this.is_async()) {
return statements;
}
const fn = b.arrow(
[b.id('$$renderer')],
b.block([this.#apply(), ...statements]),
this.has_await
);
const blockers = this.blockers();
return blockers.elements.length > 0
? [b.stmt(b.call('$$renderer.async', blockers, fn))]
: [b.stmt(b.call('$$renderer.child', fn))];
}
/**
* @param {Statement[]} statements
* @returns {Statement[]}
*/
render_block(statements) {
if (!this.is_async()) {
return statements;
}
return create_child_block([this.#apply(), ...statements], this.blockers(), this.has_await);
}
}

@ -20,13 +20,27 @@ import { get_boundary } from './boundary.js';
*/
export function async(node, blockers = [], expressions = [], fn) {
var was_hydrating = hydrating;
var end = null;
if (was_hydrating) {
hydrate_next();
end = skip_nodes(false);
}
if (expressions.length === 0 && blockers.every((b) => b.settled)) {
fn(node);
// This is necessary because it is not guaranteed that the render function will
// advance the hydration node to $.async's end marker: it may stop at an inner
// block's end marker (in case of an inner if block for example), but it also may
// stop at the correct $.async end marker (in case of component child) - hence
// we can't just use hydrate_next()
// TODO this feels indicative of a bug elsewhere; ideally we wouldn't need
// to double-traverse in the already-resolved case
if (was_hydrating) {
set_hydrate_node(end);
}
return;
}
@ -39,7 +53,6 @@ export function async(node, blockers = [], expressions = [], fn) {
if (was_hydrating) {
var previous_hydrate_node = hydrate_node;
var end = skip_nodes(false);
set_hydrate_node(end);
}

@ -200,17 +200,17 @@ export class BranchManager {
if (defer) {
for (const [k, effect] of this.#onscreen) {
if (k === key) {
batch.skipped_effects.delete(effect);
batch.unskip_effect(effect);
} else {
batch.skipped_effects.add(effect);
batch.skip_effect(effect);
}
}
for (const [k, branch] of this.#offscreen) {
if (k === key) {
batch.skipped_effects.delete(branch.effect);
batch.unskip_effect(branch.effect);
} else {
batch.skipped_effects.add(branch.effect);
batch.skip_effect(branch.effect);
}
}

@ -40,6 +40,7 @@ import { get } from '../../runtime.js';
import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js';
import { current_batch } from '../../reactivity/batch.js';
import * as e from '../../errors.js';
// When making substantive changes to this file, validate them with the each block stress test:
// https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b
@ -257,7 +258,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
if (item.i) internal_set(item.i, index);
if (defer) {
batch.skipped_effects.delete(item.e);
batch.unskip_effect(item.e);
}
} else {
item = create_item(
@ -290,6 +291,15 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
}
}
if (length > keys.size) {
if (DEV) {
validate_each_keys(array, get_key);
} else {
// in prod, the additional information isn't printed, so don't bother computing it
e.each_key_duplicate('', '', '');
}
}
// remove excess nodes
if (hydrating && length > 0) {
set_hydrate_node(skip_nodes());
@ -299,7 +309,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
if (defer) {
for (const [key, item] of items) {
if (!keys.has(key)) {
batch.skipped_effects.add(item.e);
batch.skip_effect(item.e);
}
}
@ -676,3 +686,30 @@ function link(state, prev, next) {
next.prev = prev;
}
}
/**
* @param {Array<any>} array
* @param {(item: any, index: number) => string} key_fn
* @returns {void}
*/
function validate_each_keys(array, key_fn) {
const keys = new Map();
const length = array.length;
for (let i = 0; i < length; i++) {
const key = key_fn(array[i], i);
if (keys.has(key)) {
const a = String(keys.get(key));
const b = String(i);
/** @type {string | null} */
let k = String(key);
if (k.startsWith('[object ')) k = null;
e.each_key_duplicate(a, b, k);
}
keys.set(key, i);
}
}

@ -4,6 +4,8 @@ import { block } from '../../reactivity/effects.js';
import { hydrate_next, hydrating } from '../hydration.js';
import { BranchManager } from './branches.js';
const NAN = Symbol('NaN');
/**
* @template V
* @param {TemplateNode} node
@ -23,6 +25,11 @@ export function key(node, get_key, render_fn) {
block(() => {
var key = get_key();
// NaN !== NaN, hence we do this workaround to not trigger remounts unnecessarily
if (key !== key) {
key = /** @type {any} */ (NAN);
}
// key blocks in Svelte <5 had stupid semantics
if (legacy && key !== null && typeof key === 'object') {
key = /** @type {V} */ ({});

@ -2,9 +2,8 @@ import { effect, teardown } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
/**
* Resize observer singleton.
* One listener per element only!
* https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ
* We create one listener for all elements
* @see {@link https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ Explanation}
*/
class ResizeObserverSingleton {
/** */

@ -158,7 +158,7 @@ export {
deep_read_state,
active_effect
} from './runtime.js';
export { validate_binding, validate_each_keys } from './validate.js';
export { validate_binding } from './validate.js';
export { raf } from './timing.js';
export { proxy } from './proxy.js';
export { create_custom_element } from './dom/elements/custom-element.js';

@ -130,11 +130,13 @@ export class Batch {
#maybe_dirty_effects = new Set();
/**
* A set of branches that still exist, but will be destroyed when this batch
* is committed we skip over these during `process`
* @type {Set<Effect>}
* A map of branches that still exist, but will be destroyed when this batch
* is committed we skip over these during `process`.
* The value contains child effects that were dirty/maybe_dirty before being reset,
* so they can be rescheduled if the branch survives.
* @type {Map<Effect, { d: Effect[], m: Effect[] }>}
*/
skipped_effects = new Set();
#skipped_branches = new Map();
is_fork = false;
@ -144,6 +146,38 @@ export class Batch {
return this.is_fork || this.#blocking_pending > 0;
}
/**
* Add an effect to the #skipped_branches map and reset its children
* @param {Effect} effect
*/
skip_effect(effect) {
if (!this.#skipped_branches.has(effect)) {
this.#skipped_branches.set(effect, { d: [], m: [] });
}
}
/**
* Remove an effect from the #skipped_branches map and reschedule
* any tracked dirty/maybe_dirty child effects
* @param {Effect} effect
*/
unskip_effect(effect) {
var tracked = this.#skipped_branches.get(effect);
if (tracked) {
this.#skipped_branches.delete(effect);
for (var e of tracked.d) {
set_signal_status(e, DIRTY);
schedule_effect(e);
}
for (e of tracked.m) {
set_signal_status(e, MAYBE_DIRTY);
schedule_effect(e);
}
}
}
/**
*
* @param {Effect[]} root_effects
@ -172,8 +206,8 @@ export class Batch {
this.#defer_effects(render_effects);
this.#defer_effects(effects);
for (const e of this.skipped_effects) {
reset_branch(e);
for (const [e, t] of this.#skipped_branches) {
reset_branch(e, t);
}
} else {
// append/remove branches
@ -220,7 +254,7 @@ export class Batch {
var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
var is_skippable_branch = is_branch && (flags & CLEAN) !== 0;
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect);
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.#skipped_branches.has(effect);
// Inside a `<svelte:boundary>` with a pending snippet,
// all effects are deferred until the boundary resolves
@ -613,8 +647,9 @@ function flush_effects() {
}
}
} finally {
is_flushing = false;
queued_root_effects = [];
is_flushing = false;
last_scheduled_effect = null;
if (DEV) {
@ -807,7 +842,8 @@ export function schedule_effect(signal) {
var flags = effect.f;
// if the effect is being scheduled because a parent (each/await/etc) block
// updated an internal source, bail out or we'll cause a second flush
// updated an internal source, or because a branch is being unskipped,
// bail out or we'll cause a second flush
if (
is_flushing &&
effect === active_effect &&
@ -887,20 +923,28 @@ export function eager(fn) {
/**
* Mark all the effects inside a skipped branch CLEAN, so that
* they can be correctly rescheduled later
* they can be correctly rescheduled later. Tracks dirty and maybe_dirty
* effects so they can be rescheduled if the branch survives.
* @param {Effect} effect
* @param {{ d: Effect[], m: Effect[] }} tracked
*/
function reset_branch(effect) {
function reset_branch(effect, tracked) {
// clean branch = nothing dirty inside, no need to traverse further
if ((effect.f & BRANCH_EFFECT) !== 0 && (effect.f & CLEAN) !== 0) {
return;
}
if ((effect.f & DIRTY) !== 0) {
tracked.d.push(effect);
} else if ((effect.f & MAYBE_DIRTY) !== 0) {
tracked.m.push(effect);
}
set_signal_status(effect, CLEAN);
var e = effect.first;
while (e !== null) {
reset_branch(e);
reset_branch(e, tracked);
e = e.next;
}
}

@ -208,11 +208,9 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
pending: () => {}
},
(anchor_node) => {
if (context) {
push({});
var ctx = /** @type {ComponentContext} */ (component_context);
ctx.c = context;
}
push({});
var ctx = /** @type {ComponentContext} */ (component_context);
if (context) ctx.c = context;
if (events) {
// We can't spread the object or else we'd lose the state proxy stuff, if it is one
@ -241,9 +239,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
}
}
if (context) {
pop();
}
pop();
}
);

@ -1,45 +1,11 @@
/** @import { Blocker } from '#client' */
import { dev_current_component_function } from './context.js';
import { is_array } from '../shared/utils.js';
import * as e from './errors.js';
import { FILENAME } from '../../constants.js';
import { render_effect } from './reactivity/effects.js';
import * as w from './warnings.js';
import { capture_store_binding } from './reactivity/store.js';
import { run_after_blockers } from './reactivity/async.js';
/**
* @param {() => any} collection
* @param {(item: any, index: number) => string} key_fn
* @returns {void}
*/
export function validate_each_keys(collection, key_fn) {
render_effect(() => {
const keys = new Map();
const maybe_array = collection();
const array = is_array(maybe_array)
? maybe_array
: maybe_array == null
? []
: Array.from(maybe_array);
const length = array.length;
for (let i = 0; i < length; i++) {
const key = key_fn(array[i], i);
if (keys.has(key)) {
const a = String(keys.get(key));
const b = String(i);
/** @type {string | null} */
let k = String(key);
if (k.startsWith('[object ')) k = null;
e.each_key_duplicate(a, b, k);
}
keys.set(key, i);
}
});
}
/**
* @param {string} binding
* @param {Blocker[]} blockers

@ -12,7 +12,8 @@ export async function sha256(data) {
crypto ??= globalThis.crypto?.subtle?.digest
? globalThis.crypto
: // @ts-ignore - we don't install node types in the prod build
(await import('node:crypto')).webcrypto;
// don't use 'node:crypto' because static analysers will think we rely on node when we don't
(await import(/* @vite-ignore */ 'node:' + 'crypto')).webcrypto;
const hash_buffer = await crypto.subtle.digest('SHA-256', text_encoder.encode(data));

@ -171,6 +171,15 @@ export class Renderer {
return promises;
}
/**
* @param {(renderer: Renderer) => MaybePromise<void>} fn
*/
child_block(fn) {
this.#out.push(BLOCK_OPEN);
this.child(fn);
this.#out.push(BLOCK_CLOSE);
}
/**
* Create a child renderer. The child renderer inherits the state from the parent,
* but has its own content.
@ -617,18 +626,14 @@ export class Renderer {
renderer.push(BLOCK_OPEN);
if (options.context) {
push();
/** @type {SSRContext} */ (ssr_context).c = options.context;
/** @type {SSRContext} */ (ssr_context).r = renderer;
}
push();
if (options.context) /** @type {SSRContext} */ (ssr_context).c = options.context;
/** @type {SSRContext} */ (ssr_context).r = renderer;
// @ts-expect-error
component(renderer, options.props ?? {});
if (options.context) {
pop();
}
pop();
renderer.push(BLOCK_CLOSE);

@ -7,7 +7,7 @@ const parenthesis_regex = /\(.+\)/;
//
// eg: new MediaQuery('screen')
//
// however because of the auto-parenthesis logic in the constructor since there's no parentehesis
// however because of the auto-parenthesis logic in the constructor since there's no parenthesis
// in the media query they'll be surrounded by parenthesis
//
// however we can check if the media query is only composed of these keywords

@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
export const VERSION = '5.49.1';
export const VERSION = '5.49.2';
export const PUBLIC_VERSION = '5';

@ -9,12 +9,12 @@ export default test({
start: {
line: 73,
column: 1,
character: 964
character: 966
},
end: {
line: 73,
column: 16,
character: 979
character: 981
}
},
{
@ -23,12 +23,12 @@ export default test({
start: {
line: 104,
column: 29,
character: 1270
character: 1272
},
end: {
line: 104,
column: 43,
character: 1284
character: 1286
}
}
]

@ -39,7 +39,7 @@
/*}*/
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
/* ...which is equivalent to `div :global { &.x { ...} }` ... */
div.svelte-xyz {
&.x {
color: green;
@ -51,7 +51,7 @@
color: green;
}
/* ...and therefore `div { :global.x { ... }` aswell */
/* ...and therefore `div { :global.x { ... }` as well */
div.svelte-xyz {
&.x {
color: green;

@ -41,7 +41,7 @@
}
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
/* ...which is equivalent to `div :global { &.x { ...} }` ... */
div :global {
&.x {
color: green;
@ -53,7 +53,7 @@
color: green;
}
/* ...and therefore `div { :global.x { ... }` aswell */
/* ...and therefore `div { :global.x { ... }` as well */
div {
:global.x {
color: green;

@ -0,0 +1,22 @@
import { flushSync } from 'svelte';
import { ok, test } from '../../test';
export default test({
test({ assert, target, window }) {
const input = target.querySelector('input');
ok(input);
const event = new window.Event('input');
input.value = 'changed';
input.dispatchEvent(event);
flushSync();
assert.htmlEqual(
target.innerHTML,
`
<input>
<p>changed</p>
`
);
}
});

@ -0,0 +1,13 @@
<script>
import { writable } from 'svelte/store';
const items = writable([
{ id: 0, text: 'initial' }
]);
</script>
{#each $items ?? [] as item}
<input bind:value={item.text}>
{/each}
<p>{$items[0].text}</p>

@ -47,9 +47,9 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string,
mode?: Array<'server' | 'async-server' | 'client' | 'hydrate'>;
/** Temporarily skip specific modes, without skipping the entire test */
skip_mode?: Array<'server' | 'async-server' | 'client' | 'hydrate'>;
/** Skip if running with process.env.NO_ASYNC */
/** Skip if running with process.env.SVELTE_NO_ASYNC */
skip_no_async?: boolean;
/** Skip if running without process.env.NO_ASYNC */
/** Skip if running without process.env.SVELTE_NO_ASYNC */
skip_async?: boolean;
html?: string;
ssrHtml?: string;
@ -521,6 +521,8 @@ async function run_test_variant(
errors,
hydrate: hydrate_fn
});
flushSync();
}
if (config.runtime_error && !unhandled_rejection) {

@ -0,0 +1,12 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
let button = target.querySelector('button');
button?.click();
assert.throws(flushSync, 'https://svelte.dev/e/each_key_duplicate');
}
});

@ -0,0 +1,8 @@
<script>
let data = $state([1, 2, 3]);
</script>
<button onclick={() => data = [1, 1, 1]}>add</button>
{#each data as d (d)}
{d}
{/each}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
error: 'each_key_duplicate'
});

@ -0,0 +1,7 @@
<script>
let data = [1, 1, 1];
</script>
{#each data as d (d)}
{d}
{/each}

@ -0,0 +1,12 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
let button = target.querySelector('button');
button?.click();
assert.throws(flushSync, 'https://svelte.dev/e/each_key_duplicate');
}
});

@ -0,0 +1,8 @@
<script>
let data = $state([1, 2, 3, 4, 5]);
</script>
<button onclick={() => data = [1, 1, 3, 1]}>add</button>
{#each data as d (d)}
{d}
{/each}

@ -0,0 +1,4 @@
<script>
let { children } = $props()
</script>
<div>{@render children?.()}</div>

@ -0,0 +1,4 @@
<script>
let { children } = $props()
</script>
<div>{@render children?.()}</div>

@ -0,0 +1,7 @@
<script lang='ts'>
import type { Attachment } from 'svelte/attachments'
export function action(): Attachment<HTMLElement> {
return ()=>{}
}
</script>

@ -0,0 +1,10 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['hydrate'],
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, '<div><div>foo</div></div>');
}
});

@ -0,0 +1,16 @@
<script>
import Outer from './Outer.svelte'
import Inner from './Inner.svelte'
import Trigger from './Trigger.svelte'
const data = $derived(await Promise.resolve(['a', 'b']))
let trigger = $state()
</script>
<Outer>
<Inner {@attach trigger?.action}>
foo
</Inner>
</Outer>
<Trigger bind:this={trigger} />

@ -0,0 +1,9 @@
<script>
let { id } = $props();
// BUG: This logs 'undefined' on unmount when parent has async derived
const data = $derived(await Promise.resolve(id).then((x) => {
console.log('promise resolved with:', x);
return x;
}));
</script>

@ -0,0 +1,16 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
assert.deepEqual(logs, ['promise resolved with:', 'some-id']);
const button = target.querySelector('button');
button?.click();
await tick();
assert.deepEqual(logs, ['promise resolved with:', 'some-id']);
}
});

@ -0,0 +1,14 @@
<script>
import Child from './Child.svelte';
// This async derived in parent triggers the bug
const something = $derived(await Promise.resolve('test'));
let active = $state('some-id');
</script>
{#if active}
<Child id={active} />
{/if}
<button onclick={() => active = undefined}>close</button>

@ -0,0 +1,5 @@
<script>
let { message, another } = $props()
</script>
<p>{message}</p>

@ -0,0 +1,14 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['hydrate'],
ssrHtml: `<p>item 1</p><p>item 2</p><p>item 3</p>`,
html: `<p>item 1</p><p>item 2</p><p>item 3</p>`,
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, '<p>item 1</p><p>item 2</p><p>item 3</p>');
}
});

@ -0,0 +1,10 @@
<script>
import Component from './Component.svelte'
const messages = await Promise.resolve(["item 1", "item 2", "item 3"])
const another = { test: 'test' }
</script>
{#each messages as message}
<Component {message} {another} />
{/each}

@ -0,0 +1,5 @@
<script>
let { b } = $props();
</script>
{b}

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['hydrate'],
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, `<div><p>hello</p></div> <div>true</div>`);
}
});

@ -0,0 +1,18 @@
<script lang="ts">
import Child from "./Child.svelte";
await 1;
let b = true;
let a = true;
</script>
{#if a}
<div>
{#if b}
<p>hello</p>
{/if}
</div>
<div>
<Child {b} />
</div>
{/if}

@ -0,0 +1,7 @@
<script>
import { get } from './main.svelte';
const message = get();
</script>
<h1>{message}</h1>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
ssrHtml: `<div></div>`,
html: `<div><h1>hello</h1></div>`,
test() {}
});

@ -0,0 +1,20 @@
<script module>
import { createContext, mount } from 'svelte';
import Child from './Child.svelte';
/** @type {ReturnType<typeof createContext<string>>} */
const [get, set] = createContext();
export { get };
function Wrapper(Component) {
return (...args) => {
set('hello');
return Component(...args);
};
}
</script>
<div {@attach (target) => {
mount(Wrapper(Child), { target });
}}></div>

@ -11,11 +11,12 @@ export default test({
test({ assert, errors }) {
const [button] = document.querySelectorAll('button');
try {
assert.throws(() => {
flushSync(() => button.click());
} catch (e) {
assert.equal(errors.length, 1); // for whatever reason we can't get the name which should be 'updated at'
assert.ok(/** @type {Error} */ (e).message.startsWith('effect_update_depth_exceeded'));
}
}, /effect_update_depth_exceeded/);
assert.equal(errors.length, 1);
assert.doesNotThrow(flushSync);
}
});

@ -15,6 +15,6 @@
{d}
{#snippet failed()}
<p>Error occured</p>
<p>Error occurred</p>
{/snippet}
</svelte:boundary>

@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: '<button>update</button><p>it rendered</p>',
test({ assert, target, logs }) {
assert.deepEqual(logs, ['rendering']);
const btn = target.querySelector('button');
flushSync(() => btn?.click());
// should not re-render
assert.deepEqual(logs, ['rendering']);
assert.htmlEqual(target.innerHTML, '<button>update</button><p>it rendered</p>');
}
});

@ -0,0 +1,10 @@
<script>
let x = $state(NaN);
</script>
<button onclick={() => x = NaN}>update</button>
{#key x}
{console.log('rendering')}
<p>it rendered</p>
{/key}

@ -3,7 +3,7 @@ import { test } from '../../test';
/**
* $.component() should not break transition
* https://github.com/sveltejs/svelte/issues/13645
* @see {@link https://github.com/sveltejs/svelte/issues/13645}
*/
export default test({
test({ assert, raf, target }) {

@ -21,7 +21,7 @@ import { disable_async_mode_flag, enable_async_mode_flag } from '../../src/inter
/**
* @param runes runes mode
* @param fn A function that returns a function because we first need to setup all the signals
* @param fn A function that returns a function because we first need to set up all the signals
* and then execute the test in order to simulate a real component
*/
function run_test(runes: boolean, fn: (runes: boolean) => () => void) {

@ -19,11 +19,7 @@ export default function Async_const($$renderer) {
]);
$$renderer.push(`<p>`);
$$renderer.async([promises[1]], ($$renderer) => {
$$renderer.push(() => $.escape(b));
});
$$renderer.async([promises[1]], ($$renderer) => $$renderer.push(() => $.escape(b)));
$$renderer.push(`</p>`);
} else {
$$renderer.push('<!--[!-->');

@ -2,7 +2,7 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_each_fallback_hoisting($$renderer) {
$$renderer.async_block([], async ($$renderer) => {
$$renderer.child_block(async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([])))());
if (each_array.length !== 0) {

@ -8,7 +8,7 @@ export default function Async_each_hoisting($$renderer) {
$$renderer.push(`<!--[-->`);
$$renderer.async_block([], async ($$renderer) => {
$$renderer.child_block(async ($$renderer) => {
const each_array = $.ensure_array_like((await $.save(Promise.resolve([first, second, third])))());
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {

@ -2,7 +2,7 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_if_alternate_hoisting($$renderer) {
$$renderer.async_block([], async ($$renderer) => {
$$renderer.child_block(async ($$renderer) => {
if ((await $.save(Promise.resolve(false)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.reject('no no no')));

@ -2,7 +2,7 @@ import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';
export default function Async_if_hoisting($$renderer) {
$$renderer.async_block([], async ($$renderer) => {
$$renderer.child_block(async ($$renderer) => {
if ((await $.save(Promise.resolve(true)))()) {
$$renderer.push('<!--[-->');
$$renderer.push(async () => $.escape(await Promise.resolve('yes yes yes')));

@ -6,10 +6,6 @@ export default function Async_top_level_inspect_server($$renderer) {
var $$promises = $$renderer.run([async () => data = await Promise.resolve(42),,]);
$$renderer.push(`<p>`);
$$renderer.async([$$promises[1]], ($$renderer) => {
$$renderer.push(() => $.escape(data));
});
$$renderer.async([$$promises[1]], ($$renderer) => $$renderer.push(() => $.escape(data)));
$$renderer.push(`</p>`);
}

@ -8,9 +8,9 @@ import { decode } from '@jridgewell/sourcemap-codec';
type SourceMapEntry =
| string
| {
/** If not the first occurence, but the nth should be found */
/** If not the first occurrence, but the nth should be found */
idxOriginal?: number;
/** If not the first occurence, but the nth should be found */
/** If not the first occurrence, but the nth should be found */
idxGenerated?: number;
/** The original string to find */
str: string;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save