Merge branch 'main' into proxy-parent

pull/11184/head^2
Dominic Gannaway 2 years ago
commit 47e35cf745

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: return ast from `compile` (like Svelte 4 does)

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure bind:this unmount behavior for members is conditional

@ -44,6 +44,7 @@ export function compile(source, options) {
const analysis = analyze_component(parsed, source, combined_options);
const result = transform_component(analysis, source, combined_options);
result.ast = to_public_ast(source, parsed, options.modernAst);
return result;
} catch (e) {
if (e instanceof CompileError) {
@ -121,7 +122,16 @@ export function parse(source, options = {}) {
throw e;
}
if (options.modern) {
return to_public_ast(source, ast, options.modern);
}
/**
* @param {string} source
* @param {import('#compiler').Root} ast
* @param {boolean | undefined} modern
*/
function to_public_ast(source, ast, modern) {
if (modern) {
// remove things that we don't want to treat as public API
return walk(ast, null, {
_(node, { next }) {

@ -141,29 +141,37 @@ export function convert(source, ast) {
};
if (node.pending) {
const first = /** @type {import('#compiler').BaseNode} */ (node.pending.nodes.at(0));
const last = /** @type {import('#compiler').BaseNode} */ (node.pending.nodes.at(-1));
const first = node.pending.nodes.at(0);
const last = node.pending.nodes.at(-1);
pendingblock.start = first.start;
pendingblock.end = last.end;
pendingblock.start = first?.start ?? source.indexOf('}', node.expression.end) + 1;
pendingblock.end = last?.end ?? pendingblock.start;
pendingblock.skip = false;
}
if (node.then) {
const first = /** @type {import('#compiler').BaseNode} */ (node.then.nodes.at(0));
const last = /** @type {import('#compiler').BaseNode} */ (node.then.nodes.at(-1));
const first = node.then.nodes.at(0);
const last = node.then.nodes.at(-1);
thenblock.start = pendingblock.end ?? first.start;
thenblock.end = last.end;
thenblock.start =
pendingblock.end ?? first?.start ?? source.indexOf('}', node.expression.end) + 1;
thenblock.end =
last?.end ?? source.lastIndexOf('}', pendingblock.end ?? node.expression.end) + 1;
thenblock.skip = false;
}
if (node.catch) {
const first = /** @type {import('#compiler').BaseNode} */ (node.catch.nodes.at(0));
const last = /** @type {import('#compiler').BaseNode} */ (node.catch.nodes.at(-1));
catchblock.start = thenblock.end ?? pendingblock.end ?? first.start;
catchblock.end = last.end;
const first = node.catch.nodes.at(0);
const last = node.catch.nodes.at(-1);
catchblock.start =
thenblock.end ??
pendingblock.end ??
first?.start ??
source.indexOf('}', node.expression.end) + 1;
catchblock.end =
last?.end ??
source.lastIndexOf('}', thenblock.end ?? pendingblock.end ?? node.expression.end) + 1;
catchblock.skip = false;
}

@ -917,6 +917,15 @@ function serialize_bind_this(bind_this, context, node) {
/** @type {import('estree').Expression[]} */
const args = [node, b.arrow([b.id('$$value'), ...ids], update), b.arrow([...ids], bind_this_id)];
// If we're mutating a property, then it might already be non-existent.
// If we make all the object nodes optional, then it avoids any runtime exceptions.
/** @type {import('estree').Expression | import('estree').Super} */
let bind_node = bind_this_id;
while (bind_node?.type === 'MemberExpression') {
bind_node.optional = true;
bind_node = bind_node.object;
}
if (each_ids.size) {
args.push(b.thunk(b.array(Array.from(each_ids.values()).map((id) => id[1]))));
}

@ -20,7 +20,8 @@ export function transform_component(analysis, source, options) {
warnings: transform_warnings(source, options.filename, analysis.warnings),
metadata: {
runes: analysis.runes
}
},
ast: /** @type {any} */ (null) // set afterwards
};
}
@ -62,7 +63,8 @@ export function transform_component(analysis, source, options) {
warnings: transform_warnings(source, options.filename, analysis.warnings), // TODO apply preprocessor sourcemap
metadata: {
runes: analysis.runes
}
},
ast: /** @type {any} */ (null) // set afterwards
};
}
@ -80,7 +82,8 @@ export function transform_module(analysis, source, options) {
warnings: transform_warnings(source, analysis.name, analysis.warnings),
metadata: {
runes: true
}
},
ast: /** @type {any} */ (null) // set afterwards
};
}
@ -105,7 +108,8 @@ export function transform_module(analysis, source, options) {
warnings: transform_warnings(source, analysis.name, analysis.warnings),
metadata: {
runes: true
}
},
ast: /** @type {any} */ (null) // set afterwards
};
}

@ -46,6 +46,8 @@ export interface CompileResult {
*/
runes: boolean;
};
/** The AST */
ast: any;
}
export interface Warning {
@ -184,6 +186,13 @@ export interface CompileOptions extends ModuleCompileOptions {
* @default false
*/
hmr?: boolean;
/**
* If `true`, returns the modern version of the AST.
* Will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* @default false
*/
modernAst?: boolean;
}
export interface ModuleCompileOptions {

@ -137,7 +137,7 @@ export {
$window as window,
$document as document
} from './dom/operations.js';
export { noop, call_once } from '../shared/utils.js';
export { noop } from '../shared/utils.js';
export {
add_snippet_symbol,
validate_component,

@ -23,19 +23,3 @@ export function run_all(arr) {
arr[i]();
}
}
/**
* @param {Function} fn
*/
export function call_once(fn) {
let called = false;
/** @type {unknown} */
let result;
return function () {
if (!called) {
called = true;
result = fn();
}
return result;
};
}

@ -0,0 +1,7 @@
<script>
let { item = $bindable() } = $props();
</script>
<div bind:this={item.dom}>
{item.text}
</div>

@ -0,0 +1,25 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, component }) {
const [b1, b2] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
b1.click();
b1.click();
});
assert.htmlEqual(
target.innerHTML,
`<button>add item</button><button>clear</button><div>Item 1</div><div>Item 2</div><div>Item 3</div>`
);
flushSync(() => {
b2.click();
});
assert.htmlEqual(target.innerHTML, `<button>add item</button><button>clear</button>`);
}
});

@ -0,0 +1,23 @@
<script>
import Child from './Child.svelte';
let items = $state([]);
function add_item() {
items.push({
id: items.length,
text: 'Item ' + (items.length + 1),
dom: null,
})
}
function clear() {
items = [];
}
</script>
<button on:click={add_item}>add item</button>
<button on:click={clear}>clear</button>
{#each items as item, index (item.id)}
<Child bind:item={items[index]} />
{/each}

@ -541,6 +541,8 @@ declare module 'svelte/compiler' {
*/
runes: boolean;
};
/** The AST */
ast: any;
}
interface Warning {
@ -675,6 +677,13 @@ declare module 'svelte/compiler' {
* @default false
*/
hmr?: boolean;
/**
* If `true`, returns the modern version of the AST.
* Will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* @default false
*/
modernAst?: boolean;
}
interface ModuleCompileOptions {
@ -2476,6 +2485,13 @@ declare module 'svelte/types/compiler/interfaces' {
* @default false
*/
hmr?: boolean;
/**
* If `true`, returns the modern version of the AST.
* Will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* @default false
*/
modernAst?: boolean;
}
interface ModuleCompileOptions {

@ -93,25 +93,6 @@ This can improve performance with large arrays and objects that you weren't plan
> Objects and arrays passed to `$state.frozen` will be shallowly frozen using `Object.freeze()`. If you don't want this, pass in a clone of the object or array instead.
### Reactive Map, Set and Date
Svelte provides reactive `Map`, `Set` and `Date` classes. These can be imported from `svelte/reactivity` and used just like their native counterparts.
```svelte
<script>
import { Map } from 'svelte/reactivity';
const map = new Map();
map.set('message', 'hello');
function update_message() {
map.set('message', 'goodbye');
}
</script>
<p>{map.get('message')}</p>
```
## `$state.snapshot`
To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:

@ -1,68 +0,0 @@
---
title: Functions
---
As well as runes, Svelte 5 will introduce a couple of new functions, in addition to existing functions like `getContext`, `setContext` and `tick`. These are introduced as functions rather than runes because they are used directly and the compiler does not need to touch them to make them function as it does with runes. However, these functions may still use Svelte internals.
## `untrack`
To prevent something from being treated as an `$effect`/`$derived` dependency, use `untrack`:
```svelte
<script>
import { untrack } from 'svelte';
let { a, b } = $props();
$effect(() => {
// this will run when `a` changes,
// but not when `b` changes
console.log(a);
console.log(untrack(() => b));
});
</script>
```
## `mount`
Instantiates a component and mounts it to the given target:
```js
// @errors: 2322
import { mount } from 'svelte';
import App from './App.svelte';
const app = mount(App, {
target: document.querySelector('#app'),
props: { some: 'property' }
});
```
## `hydrate`
Like `mount`, but will pick up any HTML rendered by Svelte's SSR output (from the `render` function) inside the target and make it interactive:
```js
// @errors: 2322
import { hydrate } from 'svelte';
import App from './App.svelte';
const app = hydrate(App, {
target: document.querySelector('#app'),
props: { some: 'property' }
});
```
## `render`
Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `html` and `head` properties on it, which you can use to populate the HTML when server-rendering your app:
```js
// @errors: 2724 2305 2307
import { render } from 'svelte/server';
import App from './App.svelte';
const result = render(App, {
props: { some: 'property' }
});
```

@ -0,0 +1,109 @@
---
title: Imports
---
As well as runes, Svelte 5 introduces a handful of new things you can import, alongside existing ones like `getContext`, `setContext` and `tick`.
## `svelte`
### `mount`
Instantiates a component and mounts it to the given target:
```js
// @errors: 2322
import { mount } from 'svelte';
import App from './App.svelte';
const app = mount(App, {
target: document.querySelector('#app'),
props: { some: 'property' }
});
```
### `hydrate`
Like `mount`, but will reuse up any HTML rendered by Svelte's SSR output (from the [`render`](#svelte-server-render) function) inside the target and make it interactive:
```js
// @errors: 2322
import { hydrate } from 'svelte';
import App from './App.svelte';
const app = hydrate(App, {
target: document.querySelector('#app'),
props: { some: 'property' }
});
```
### `unmount`
Unmounts a component created with [`mount`](#svelte-mount) or [`hydrate`](#svelte-hydrate):
```js
// @errors: 1109
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, {...});
// later
unmount(app);
```
### `untrack`
To prevent something from being treated as an `$effect`/`$derived` dependency, use `untrack`:
```svelte
<script>
import { untrack } from 'svelte';
let { a, b } = $props();
$effect(() => {
// this will run when `a` changes,
// but not when `b` changes
console.log(a);
console.log(untrack(() => b));
});
</script>
```
## `svelte/reactivity`
Svelte provides reactive `Map`, `Set`, `Date` and `URL` classes. These can be imported from `svelte/reactivity` and used just like their native counterparts. [Demo:](https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE32QzWrDMBCEX2Wri1uo7bvrBHrvqdBTUogqryuBfhZp5SQYv3slSsmpOc7uN8zsrmI2FpMYDqvw0qEYxCuReBZ8pSrSgpax6BRyVHUyJhUN8f7oj2wchciwwsf7G2wwx-Cg-bX0EaVisxi-Ni-FLbQKPjHkaGEHHs_V9NhoZkpD3-NFOrLYqeB6kqybp-Ia-1uYHx_aFpSW_hsTcADWmLDrOmjbsh-Np8zwZfw0LNJm3K0lqaMYOKhgt_8RHRLX0-8gtdAfUiAdb4XOxlrINElGOOmI8wmkn2AxCmHBmOTdetWw7ct7XZjMbHASA8eM2-f2A-JarmyZAQAA)
```svelte
<script>
import { URL } from 'svelte/reactivity';
const url = new URL('https://example.com/path');
</script>
<!-- changes to these... -->
<input bind:value={url.protocol} />
<input bind:value={url.hostname} />
<input bind:value={url.pathname} />
<hr />
<!-- will update `href` and vice versa -->
<input bind:value={url.href} />
```
## `svelte/server`
### `render`
Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `html` and `head` properties on it, which you can use to populate the HTML when server-rendering your app:
```js
// @errors: 2724 2305 2307
import { render } from 'svelte/server';
import App from './App.svelte';
const result = render(App, {
props: { some: 'property' }
});
```

@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit';
export function load() {
redirect(308, '/docs/imports');
}
Loading…
Cancel
Save