pull/12507/head
Rich Harris 2 years ago
commit fb6471b8aa

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: add warning for invalid render function of createRawSnippet

@ -185,6 +185,7 @@
"fluffy-dolls-share",
"fluffy-humans-worry",
"fluffy-ravens-juggle",
"forty-bikes-buy",
"forty-comics-invent",
"forty-dogs-divide",
"forty-dolls-wave",
@ -193,6 +194,7 @@
"four-balloons-beam",
"four-flies-hammer",
"four-mice-hammer",
"four-papayas-turn",
"four-pugs-listen",
"fresh-beds-wash",
"fresh-dots-destroy",

@ -72,13 +72,31 @@ If you're using TypeScript, you can declare the prop types:
```svelte
<script lang="ts">
interface Props {
a: number;
b: boolean;
c: string;
required: string;
optional?: number;
[key: string]: unknown;
}
let { a, b, c, ...everythingElse }: Props = $props();
let { required, optional, ...everythingElse }: Props = $props();
</script>
```
If you're using JavaScript, you can declare the prop types using JSDoc:
```svelte
<script>
/** @type {{ x: string }} */
let { x } = $props();
// or use @typedef if you want to document the properties:
/**
* @typedef {Object} MyProps
* @property {string} y Some documentation
*/
/** @type {MyProps} */
let { y } = $props();
</script>
```

@ -1,5 +1,13 @@
# svelte
## 5.0.0-next.193
### Patch Changes
- fix: improve validation error that occurs when using `{@render ...}` to render default slotted content ([#12521](https://github.com/sveltejs/svelte/pull/12521))
- fix: reset hydrate node after `hydrate(...)` ([#12512](https://github.com/sveltejs/svelte/pull/12512))
## 5.0.0-next.192
### Patch Changes

@ -20,6 +20,10 @@
> Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%
## invalid_raw_snippet_render
> The `render` function passed to `createRawSnippet` should return HTML for a single element
## lifecycle_double_unmount
> Tried to unmount a component that was not mounted

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

@ -2352,7 +2352,6 @@ export const template_visitors = {
EachBlock(node, context) {
const each_node_meta = node.metadata;
const collection = /** @type {Expression} */ (context.visit(node.expression));
let each_item_is_reactive = true;
if (!each_node_meta.is_controlled) {
context.state.template.push('<!>');
@ -2362,36 +2361,29 @@ export const template_visitors = {
context.state.init.push(b.const(each_node_meta.array_name, b.thunk(collection)));
}
// The runtime needs to know what kind of each block this is in order to optimize for the
// key === item (we avoid extra allocations). In that case, the item doesn't need to be reactive.
// We can guarantee this by knowing that in order for the item of the each block to change, they
// would need to mutate the key/item directly in the array. Given that in runes mode we use ===
// equality, we can apply a fast-path (as long as the index isn't reactive).
let each_type = 0;
let flags = 0;
if (
node.key &&
(node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index)
) {
each_type |= EACH_KEYED;
// If there's a destructuring, then we likely need the generated $$index
if (node.index || node.context.type !== 'Identifier') {
each_type |= EACH_INDEX_REACTIVE;
flags |= EACH_KEYED;
if (node.index) {
flags |= EACH_INDEX_REACTIVE;
}
if (
context.state.analysis.runes &&
// In runes mode, if key === item, we don't need to wrap the item in a source
const key_is_item =
node.key.type === 'Identifier' &&
node.context.type === 'Identifier' &&
node.context.name === node.key.name &&
(each_type & EACH_INDEX_REACTIVE) === 0
) {
// Fast-path for when the key === item
each_item_is_reactive = false;
} else {
each_type |= EACH_ITEM_REACTIVE;
node.context.name === node.key.name;
if (!context.state.analysis.runes || !key_is_item) {
flags |= EACH_ITEM_REACTIVE;
}
} else {
each_type |= EACH_ITEM_REACTIVE;
flags |= EACH_ITEM_REACTIVE;
}
// Since `animate:` can only appear on elements that are the sole child of a keyed each block,
@ -2404,15 +2396,15 @@ export const template_visitors = {
return child.attributes.some((attr) => attr.type === 'AnimateDirective');
})
) {
each_type |= EACH_IS_ANIMATED;
flags |= EACH_IS_ANIMATED;
}
if (each_node_meta.is_controlled) {
each_type |= EACH_IS_CONTROLLED;
flags |= EACH_IS_CONTROLLED;
}
if (context.state.analysis.runes) {
each_type |= EACH_IS_STRICT_EQUALS;
flags |= EACH_IS_STRICT_EQUALS;
}
// If the array is a store expression, we need to invalidate it when the array is changed.
@ -2437,10 +2429,12 @@ export const template_visitors = {
);
return [array, ...transitive_dependencies];
});
if (each_node_meta.array_name) {
indirect_dependencies.push(b.call(each_node_meta.array_name));
} else {
indirect_dependencies.push(collection);
const transitive_dependencies = serialize_transitive_dependencies(
each_node_meta.references,
context
@ -2459,6 +2453,7 @@ export const template_visitors = {
// into separate expressions, at which point this is called again with an identifier or member expression
return serialize_set_binding(assignment, context, () => assignment);
}
const left = object(assignment.left);
const value = get_assignment_value(assignment, context);
const invalidate = b.call(
@ -2499,10 +2494,12 @@ export const template_visitors = {
const item_with_loc = with_loc(item, id);
return b.call('$.unwrap', item_with_loc);
};
if (node.index) {
const index_binding = /** @type {import('#compiler').Binding} */ (
context.state.scope.get(node.index)
);
index_binding.expression = (id) => {
const index_with_loc = with_loc(index, id);
return b.call('$.unwrap', index_with_loc);
@ -2565,7 +2562,7 @@ export const template_visitors = {
declarations.push(b.let(node.index, index));
}
if (context.state.options.dev && (each_type & EACH_KEYED) !== 0) {
if (context.state.options.dev && (flags & EACH_KEYED) !== 0) {
context.state.init.push(
b.stmt(b.call('$.validate_each_keys', b.thunk(collection), key_function))
);
@ -2574,7 +2571,7 @@ export const template_visitors = {
/** @type {Expression[]} */
const args = [
context.state.node,
b.literal(each_type),
b.literal(flags),
each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection),
key_function,
b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(block.body)))

@ -10,6 +10,8 @@ import {
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { create_fragment_from_html } from '../reconciler.js';
import { assign_nodes } from '../template.js';
import * as w from '../../warnings.js';
import { DEV } from 'esm-env';
/**
* @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn
@ -88,6 +90,11 @@ export function createRawSnippet(fn) {
var html = snippet.render().trim();
var fragment = create_fragment_from_html(html);
element = /** @type {Element} */ (fragment.firstChild);
if (DEV && (element.nextSibling !== null || element.nodeType !== 3)) {
w.invalid_raw_snippet_render();
}
/** @type {TemplateNode} */ (/** @type {unknown} */ (anchor)).before(element);
}

@ -60,6 +60,18 @@ export function hydration_mismatch(location) {
}
}
/**
* The `render` function passed to `createRawSnippet` should return HTML for a single element
*/
export function invalid_raw_snippet_render() {
if (DEV) {
console.warn(`%c[svelte] invalid_raw_snippet_render\n%cThe \`render\` function passed to \`createRawSnippet\` should return HTML for a single element`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn("invalid_raw_snippet_render");
}
}
/**
* Tried to unmount a component that was not mounted
*/

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '5.0.0-next.192';
export const VERSION = '5.0.0-next.193';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,13 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
skip_mode: ['hydrate'],
warnings: [
'The `render` function passed to `createRawSnippet` should return HTML for a single element'
]
});

@ -0,0 +1,11 @@
<script>
import { createRawSnippet } from 'svelte';
const snippet = createRawSnippet(() => ({
render: () => `
<!-- --><div>123</div>
`
}));
</script>
{@render snippet()}

@ -18,7 +18,7 @@
<meta name="twitter:title" content="{data.page.title} • Docs • Svelte 5 preview" />
<meta name="twitter:description" content="{data.page.title} • Svelte 5 preview documentation" />
<meta name="Description" content="{data.page.title} • Svelte 5 preview documentation" />
<meta name="description" content="{data.page.title} • Svelte 5 preview documentation" />
</svelte:head>
<div class="text" id="docs-content" use:copy_code_descendants>

@ -556,17 +556,22 @@ let props = $props();
If you're using TypeScript, you can declare the prop types:
<!-- prettier-ignore -->
```ts
type MyProps = any;
// ---cut---
let { a, b, c, ...everythingElse }: MyProps = $props();
interface MyProps {
required: string;
optional?: number;
partOfEverythingElse?: boolean;
};
let { required, optional, ...everythingElse }: MyProps = $props();
```
> In an earlier preview, `$props()` took a type argument. This caused bugs, since in a case like this...
>
> ```ts
> // @errors: 2558
> let { x = 42 } = $props<{ x: string }>();
> let { x = 42 } = $props<{ x?: string }>();
> ```
>
> ...TypeScript [widens the type](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwBIAHGHIgZwB4AVeAXnilQE8A+ACgEoAueagbgBQgiCAzwA3vAAe9eABYATPAC+c4qQqUp03uQwwsqAOaqOnIfCsB6a-AB6AfiA) of `x` to be `string | number`, instead of erroring.

@ -70,7 +70,7 @@
<meta name="twitter:title" content="{data.gist.name} • REPL • Svelte" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Interactive Svelte playground" />
<meta name="description" content="Interactive Svelte playground" />
</svelte:head>
<div class="repl-outer {zen_mode ? 'zen-mode' : ''}">

@ -10,7 +10,7 @@
<meta name="twitter:title" content="Svelte REPL" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Interactive Svelte playground" />
<meta name="description" content="Interactive Svelte playground" />
</svelte:head>
<div class="repl-outer">

@ -11,7 +11,7 @@
<meta name="twitter:title" content="Svelte" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Cybernetically enhanced web apps" />
<meta name="description" content="Cybernetically enhanced web apps" />
</svelte:head>
<h1 class="visually-hidden">Svelte</h1>

@ -13,7 +13,7 @@
<meta name="twitter:title" content="Svelte blog" />
<meta name="twitter:description" content="Articles about Svelte and UI development" />
<meta name="Description" content="Articles about Svelte and UI development" />
<meta name="description" content="Articles about Svelte and UI development" />
</svelte:head>
<h1 class="visually-hidden">Blog</h1>

@ -14,7 +14,7 @@
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={data.post.title} />
<meta name="twitter:description" content={data.post.description} />
<meta name="Description" content={data.post.description} />
<meta name="description" content={data.post.description} />
<meta name="twitter:image" content="https://svelte.dev/blog/{$page.params.slug}/card.png" />
<meta name="og:image" content="https://svelte.dev/blog/{$page.params.slug}/card.png" />

@ -19,7 +19,7 @@
<meta name="twitter:title" content="{data.page.title} • Docs • Svelte" />
<meta name="twitter:description" content="{data.page.title} • Svelte documentation" />
<meta name="Description" content="{data.page.title} • Svelte documentation" />
<meta name="description" content="{data.page.title} • Svelte documentation" />
</svelte:head>
<div class="text" id="docs-content" use:copy_code_descendants>

@ -34,7 +34,7 @@
<meta name="twitter:title" content="Svelte examples" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" />
<meta name="Description" content="Interactive example Svelte apps" />
<meta name="description" content="Interactive example Svelte apps" />
</svelte:head>
<h1 class="visually-hidden">Examples</h1>

@ -102,7 +102,7 @@
<meta name="twitter:title" content="Svelte tutorial" />
<meta name="twitter:description" content="{selected.section.title} / {selected.chapter.title}" />
<meta name="Description" content="{selected.section.title} / {selected.chapter.title}" />
<meta name="description" content="{selected.section.title} / {selected.chapter.title}" />
</svelte:head>
<svelte:window bind:innerWidth={width} />

Loading…
Cancel
Save