Merge remote-tracking branch 'upstream/main' into structuredclone-patch

pull/14599/head
ComputerGuy 2 years ago
commit 2a45896310

@ -59,7 +59,7 @@ jobs:
run: pnpm lint run: pnpm lint
- name: build and check generated types - name: build and check generated types
if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
Benchmarks: Benchmarks:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15

@ -33,7 +33,7 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build - name: Build
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
- name: Create Release Pull Request or Publish to npm - name: Create Release Pull Request or Publish to npm
id: changesets id: changesets

@ -630,6 +630,32 @@ In some situations a selector may target an element that is not 'visible' to the
Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />` Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />`
``` ```
In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it...
```html
<div>
<span class="icon" /> some text!
</div>
```
...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon:
```html
<div>
<span class="icon"> some text! </span>
</div>
```
Some templating languages (including Svelte) will 'fix' HTML by turning `<span />` into `<span></span>`. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag.
To automate this, run the dedicated migration:
```bash
npx sv migrate self-closing-tags
```
In a future version of Svelte, self-closing tags may be upgraded from a warning to an error.
### event_directive_deprecated ### event_directive_deprecated
``` ```

@ -2,7 +2,7 @@
title: <svelte:component> title: <svelte:component>
--- ---
In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes. In runes mode, `<MyComponent>` will re-render if the value of `MyComponent` changes. See the [Svelte 5 migration guide](/docs/svelte/v5-migration-guide#Breaking-changes-in-runes-mode-svelte:component-is-no-longer-necessary) for an example.
In legacy mode, it won't — we must use `<svelte:component>`, which destroys and recreates the component instance when the value of its `this` expression changes: In legacy mode, it won't — we must use `<svelte:component>`, which destroys and recreates the component instance when the value of its `this` expression changes:

@ -1,5 +1,59 @@
# svelte # svelte
## 5.15.0
### Minor Changes
- feat: add "worker" exports condition to better support bundling for worker-based environments ([#14779](https://github.com/sveltejs/svelte/pull/14779))
## 5.14.6
### Patch Changes
- fix: treeshake `$inspect.trace` code if unused in modules ([#14774](https://github.com/sveltejs/svelte/pull/14774))
- fix: Improve typescript DX for $inspect, $props, $bindable, and $host ([#14777](https://github.com/sveltejs/svelte/pull/14777))
## 5.14.5
### Patch Changes
- fix: bump esrap dependency ([#14765](https://github.com/sveltejs/svelte/pull/14765))
- fix: ensure svg namespace for `<a>` elements is correct ([#14756](https://github.com/sveltejs/svelte/pull/14756))
- fix: treeshake `$inspect.trace` code if unused ([#14770](https://github.com/sveltejs/svelte/pull/14770))
## 5.14.4
### Patch Changes
- fix: remove implements from class declarations ([#14749](https://github.com/sveltejs/svelte/pull/14749))
- fix: remove unwanted properties from both replaced and unreplaced nodes ([#14744](https://github.com/sveltejs/svelte/pull/14744))
## 5.14.3
### Patch Changes
- fix: bump esrap, prevent malformed AST ([#14742](https://github.com/sveltejs/svelte/pull/14742))
- fix: compare array contents for equality mismatch detections, not the arrays themselves ([#14738](https://github.com/sveltejs/svelte/pull/14738))
## 5.14.2
### Patch Changes
- fix: correctly highlight first rerun of `$inspect.trace` ([#14734](https://github.com/sveltejs/svelte/pull/14734))
- chore: more loose parser improvements ([#14733](https://github.com/sveltejs/svelte/pull/14733))
## 5.14.1
### Patch Changes
- fix: improve unowned derived performance ([#14724](https://github.com/sveltejs/svelte/pull/14724))
## 5.14.0 ## 5.14.0
### Minor Changes ### Minor Changes

@ -34,6 +34,32 @@
> Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />` > Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...></%name%>` rather than `<%name% ... />`
In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it...
```html
<div>
<span class="icon" /> some text!
</div>
```
...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon:
```html
<div>
<span class="icon"> some text! </span>
</div>
```
Some templating languages (including Svelte) will 'fix' HTML by turning `<span />` into `<span></span>`. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag.
To automate this, run the dedicated migration:
```bash
npx sv migrate self-closing-tags
```
In a future version of Svelte, self-closing tags may be upgraded from a warning to an error.
## event_directive_deprecated ## event_directive_deprecated
> Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead > Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.14.0", "version": "5.15.0",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {
@ -21,6 +21,7 @@
"exports": { "exports": {
".": { ".": {
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"worker": "./src/index-server.js",
"browser": "./src/index-client.js", "browser": "./src/index-client.js",
"default": "./src/index-server.js" "default": "./src/index-server.js"
}, },
@ -56,11 +57,15 @@
"./internal/flags/legacy": { "./internal/flags/legacy": {
"default": "./src/internal/flags/legacy.js" "default": "./src/internal/flags/legacy.js"
}, },
"./internal/flags/tracing": {
"default": "./src/internal/flags/tracing.js"
},
"./internal/server": { "./internal/server": {
"default": "./src/internal/server/index.js" "default": "./src/internal/server/index.js"
}, },
"./legacy": { "./legacy": {
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"worker": "./src/legacy/legacy-server.js",
"browser": "./src/legacy/legacy-client.js", "browser": "./src/legacy/legacy-client.js",
"default": "./src/legacy/legacy-server.js" "default": "./src/legacy/legacy-server.js"
}, },
@ -70,6 +75,7 @@
}, },
"./reactivity": { "./reactivity": {
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"worker": "./src/reactivity/index-server.js",
"browser": "./src/reactivity/index-client.js", "browser": "./src/reactivity/index-client.js",
"default": "./src/reactivity/index-server.js" "default": "./src/reactivity/index-server.js"
}, },
@ -83,6 +89,7 @@
}, },
"./store": { "./store": {
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"worker": "./src/store/index-server.js",
"browser": "./src/store/index-client.js", "browser": "./src/store/index-client.js",
"default": "./src/store/index-server.js" "default": "./src/store/index-server.js"
}, },
@ -147,7 +154,7 @@
"aria-query": "^5.3.1", "aria-query": "^5.3.1",
"axobject-query": "^4.1.0", "axobject-query": "^4.1.0",
"esm-env": "^1.2.1", "esm-env": "^1.2.1",
"esrap": "^1.2.3", "esrap": "^1.3.2",
"is-reference": "^3.0.3", "is-reference": "^3.0.3",
"locate-character": "^3.0.0", "locate-character": "^3.0.0",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",

@ -58,6 +58,7 @@ for (const key in pkg.exports) {
if (key === './internal') continue; if (key === './internal') continue;
if (key === './internal/disclose-version') continue; if (key === './internal/disclose-version') continue;
if (key === './internal/flags/legacy') continue; if (key === './internal/flags/legacy') continue;
if (key === './internal/flags/tracing') continue;
for (const type of ['browser', 'default']) { for (const type of ['browser', 'default']) {
if (!pkg.exports[key][type]) continue; if (!pkg.exports[key][type]) continue;
@ -91,6 +92,7 @@ const bundle = await bundle_code(
</script> </script>
<svelte:head><title>hi</title></svelte:head> <svelte:head><title>hi</title></svelte:head>
<input bind:value={foo} />
<a href={foo} class={foo}>a</a> <a href={foo} class={foo}>a</a>
<a {...foo}>a</a> <a {...foo}>a</a>
@ -134,6 +136,15 @@ if (!bundle.includes('component_context.l')) {
console.error(`❌ Legacy code not treeshakeable`); console.error(`❌ Legacy code not treeshakeable`);
} }
if (!bundle.includes(`'CreatedAt'`)) {
// eslint-disable-next-line no-console
console.error(`$inspect.trace code treeshakeable`);
} else {
failed = true;
// eslint-disable-next-line no-console
console.error(`$inspect.trace code not treeshakeable`);
}
if (failed) { if (failed) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(bundle); console.error(bundle);

@ -46,6 +46,8 @@ await createBundle({
} }
}); });
fs.appendFileSync(`${dir}/types/index.d.ts`, '\n');
const types = fs.readFileSync(`${dir}/types/index.d.ts`, 'utf-8'); const types = fs.readFileSync(`${dir}/types/index.d.ts`, 'utf-8');
const bad_links = [...types.matchAll(/\]\((\/[^)]+)\)/g)]; const bad_links = [...types.matchAll(/\]\((\/[^)]+)\)/g)];

@ -389,7 +389,6 @@ function transform(name, dest) {
ast.body.push(clone); ast.body.push(clone);
} }
// @ts-expect-error
const module = esrap.print(ast); const module = esrap.print(ast);
fs.writeFileSync( fs.writeFileSync(

@ -338,6 +338,29 @@ declare namespace $effect {
*/ */
declare function $props(): any; declare function $props(): any;
declare namespace $props {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/** /**
* Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it. * Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it.
* *
@ -349,6 +372,29 @@ declare function $props(): any;
*/ */
declare function $bindable<T>(fallback?: T): T; declare function $bindable<T>(fallback?: T): T;
declare namespace $bindable {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/** /**
* Inspects one or more values whenever they, or the properties they contain, change. Example: * Inspects one or more values whenever they, or the properties they contain, change. Example:
* *
@ -388,6 +434,27 @@ declare namespace $inspect {
* </script> * </script>
*/ */
export function trace(name: string): void; export function trace(name: string): void;
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
} }
/** /**
@ -410,3 +477,26 @@ declare namespace $inspect {
* https://svelte.dev/docs/svelte/$host * https://svelte.dev/docs/svelte/$host
*/ */
declare function $host<El extends HTMLElement = HTMLElement>(): El; declare function $host<El extends HTMLElement = HTMLElement>(): El;
declare namespace $host {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}

@ -7,9 +7,10 @@ import { find_matching_bracket } from '../utils/bracket.js';
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression} * @returns {Expression}
*/ */
export default function read_expression(parser) { export default function read_expression(parser, opening_token) {
try { try {
const node = parse_expression_at(parser.template, parser.ts, parser.index); const node = parse_expression_at(parser.template, parser.ts, parser.index);
@ -42,7 +43,7 @@ export default function read_expression(parser) {
} catch (err) { } catch (err) {
if (parser.loose) { if (parser.loose) {
// Find the next } and treat it as the end of the expression // Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, '{'); const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) { if (end) {
const start = parser.index; const start = parser.index;
parser.index = end; parser.index = end;

@ -17,6 +17,16 @@ function remove_this_param(node, context) {
/** @type {Visitors<any, null>} */ /** @type {Visitors<any, null>} */
const visitors = { const visitors = {
_(node, context) {
const n = context.next() ?? node;
// TODO there may come a time when we decide to preserve type annotations.
// until that day comes, we just delete them so they don't confuse esrap
delete n.typeAnnotation;
delete n.typeParameters;
delete n.returnType;
delete n.accessibility;
},
Decorator(node) { Decorator(node) {
e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)'); e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)');
}, },
@ -78,23 +88,12 @@ const visitors = {
TSNonNullExpression(node, context) { TSNonNullExpression(node, context) {
return context.visit(node.expression); return context.visit(node.expression);
}, },
TSTypeAnnotation() {
// This isn't correct, strictly speaking, and could result in invalid ASTs (like an empty statement within function parameters),
// but esrap, our printing tool, just ignores these AST nodes at invalid positions, so it's fine
return b.empty;
},
TSInterfaceDeclaration() { TSInterfaceDeclaration() {
return b.empty; return b.empty;
}, },
TSTypeAliasDeclaration() { TSTypeAliasDeclaration() {
return b.empty; return b.empty;
}, },
TSTypeParameterDeclaration() {
return b.empty;
},
TSTypeParameterInstantiation() {
return b.empty;
},
TSEnumDeclaration(node) { TSEnumDeclaration(node) {
e.typescript_invalid_feature(node, 'enums'); e.typescript_invalid_feature(node, 'enums');
}, },
@ -116,6 +115,7 @@ const visitors = {
if (node.declare) { if (node.declare) {
return b.empty; return b.empty;
} }
delete node.implements;
return context.next(); return context.next();
}, },
VariableDeclaration(node, context) { VariableDeclaration(node, context) {

@ -123,8 +123,11 @@ export default function element(parser) {
} }
if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) { if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
const bounds = { start: start + 1, end: start + 1 + name.length }; // <div. -> in the middle of typing -> allow in loose mode
e.tag_invalid_name(bounds); if (!parser.loose || !name.endsWith('.')) {
const bounds = { start: start + 1, end: start + 1 + name.length };
e.tag_invalid_name(bounds);
}
} }
if (root_only_meta_tags.has(name)) { if (root_only_meta_tags.has(name)) {
@ -141,7 +144,7 @@ export default function element(parser) {
const type = meta_tags.has(name) const type = meta_tags.has(name)
? meta_tags.get(name) ? meta_tags.get(name)
: regex_valid_component_name.test(name) : regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.'))
? 'Component' ? 'Component'
: name === 'title' && parent_is_head(parser.stack) : name === 'title' && parent_is_head(parser.stack)
? 'TitleElement' ? 'TitleElement'

@ -174,13 +174,30 @@ function open(parser) {
if (parser.eat('(')) { if (parser.eat('(')) {
parser.allow_whitespace(); parser.allow_whitespace();
key = read_expression(parser); key = read_expression(parser, '(');
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat(')', true); parser.eat(')', true);
parser.allow_whitespace(); parser.allow_whitespace();
} }
parser.eat('}', true); const matches = parser.eat('}', true, false);
if (!matches) {
// Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
const prev_index = parser.index;
context = read_pattern(parser);
parser.eat('}', true);
expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 4
};
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}
/** @type {AST.EachBlock} */ /** @type {AST.EachBlock} */
const block = parser.append({ const block = parser.append({
@ -246,7 +263,39 @@ function open(parser) {
parser.fragments.push(block.pending); parser.fragments.push(block.pending);
} }
parser.eat('}', true); const matches = parser.eat('}', true, false);
// Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
if (!matches) {
if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
const prev_index = parser.index;
block.value = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 6
};
block.then = block.pending;
block.pending = null;
} else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
const prev_index = parser.index;
block.error = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 7
};
block.catch = block.pending;
block.pending = null;
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}
parser.stack.push(block); parser.stack.push(block);
return; return;

@ -4,6 +4,8 @@ const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0); const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0); const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0); const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0);
const PARENTHESES_OPEN = '('.charCodeAt(0);
const PARENTHESES_CLOSE = ')'.charCodeAt(0);
/** @param {number} code */ /** @param {number} code */
export function is_bracket_open(code) { export function is_bracket_open(code) {
@ -34,6 +36,9 @@ export function get_bracket_close(open) {
if (open === CURLY_BRACKET_OPEN) { if (open === CURLY_BRACKET_OPEN) {
return CURLY_BRACKET_CLOSE; return CURLY_BRACKET_CLOSE;
} }
if (open === PARENTHESES_OPEN) {
return PARENTHESES_CLOSE;
}
} }
/** /**

@ -243,13 +243,15 @@ export function analyze_module(ast, options) {
} }
} }
const analysis = { runes: true, tracing: false };
walk( walk(
/** @type {Node} */ (ast), /** @type {Node} */ (ast),
{ {
scope, scope,
scopes, scopes,
// @ts-expect-error TODO // @ts-expect-error TODO
analysis: { runes: true } analysis
}, },
visitors visitors
); );
@ -259,7 +261,8 @@ export function analyze_module(ast, options) {
name: options.filename, name: options.filename,
accessors: false, accessors: false,
runes: true, runes: true,
immutable: true immutable: true,
tracing: analysis.tracing
}; };
} }
@ -408,6 +411,7 @@ export function analyze_component(root, source, options) {
template, template,
elements: [], elements: [],
runes, runes,
tracing: false,
immutable: runes || options.immutable, immutable: runes || options.immutable,
exports: [], exports: [],
uses_props: false, uses_props: false,

@ -171,6 +171,8 @@ export function CallExpression(node, context) {
context.state.scope.tracing = b.thunk(b.literal(label + ' ' + loc)); context.state.scope.tracing = b.thunk(b.literal(label + ' ' + loc));
} }
context.state.analysis.tracing = true;
} }
break; break;

@ -86,7 +86,23 @@ export function RegularElement(node, context) {
(attribute) => attribute.type === 'SpreadAttribute' (attribute) => attribute.type === 'SpreadAttribute'
); );
node.metadata.svg = is_svg(node.name); const is_svg_element = () => {
if (is_svg(node.name)) {
return true;
}
if (node.name === 'a') {
for (let i = context.path.length - 1; i >= 0; i--) {
const ancestor = context.path[i];
if (ancestor.type === 'RegularElement') {
return ancestor.metadata.svg;
}
}
}
return false;
};
node.metadata.svg = is_svg_element();
node.metadata.mathml = is_mathml(node.name); node.metadata.mathml = is_mathml(node.name);
if (is_custom_element_node(node) && node.attributes.length > 0) { if (is_custom_element_node(node) && node.attributes.length > 0) {

@ -539,6 +539,10 @@ export function client_component(analysis, options) {
body.unshift(b.imports([], 'svelte/internal/flags/legacy')); body.unshift(b.imports([], 'svelte/internal/flags/legacy'));
} }
if (analysis.tracing) {
body.unshift(b.imports([], 'svelte/internal/flags/tracing'));
}
if (options.discloseVersion) { if (options.discloseVersion) {
body.unshift(b.imports([], 'svelte/internal/disclose-version')); body.unshift(b.imports([], 'svelte/internal/disclose-version'));
} }
@ -667,9 +671,15 @@ export function client_module(analysis, options) {
walk(/** @type {AST.SvelteNode} */ (analysis.module.ast), state, visitors) walk(/** @type {AST.SvelteNode} */ (analysis.module.ast), state, visitors)
); );
const body = [b.import_all('$', 'svelte/internal/client')];
if (analysis.tracing) {
body.push(b.imports([], 'svelte/internal/flags/tracing'));
}
return { return {
type: 'Program', type: 'Program',
sourceType: 'module', sourceType: 'module',
body: [b.import_all('$', 'svelte/internal/client'), ...module.body] body: [...body, ...module.body]
}; };
} }

@ -33,12 +33,14 @@ export function transform_component(analysis, source, options) {
: client_component(analysis, options); : client_component(analysis, options);
const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte');
const js = print(program, { const js = print(program, {
// include source content; makes it easier/more robust looking up the source map code // include source content; makes it easier/more robust looking up the source map code
// (else esrap does return null for source and sourceMapContent which may trip up tooling) // (else esrap does return null for source and sourceMapContent which may trip up tooling)
sourceMapContent: source, sourceMapContent: source,
sourceMapSource: js_source_name sourceMapSource: js_source_name
}); });
merge_with_preprocessor_map(js, options, js_source_name); merge_with_preprocessor_map(js, options, js_source_name);
const css = const css =

@ -27,6 +27,7 @@ export interface Analysis {
name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile` name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile`
runes: boolean; runes: boolean;
immutable: boolean; immutable: boolean;
tracing: boolean;
// TODO figure out if we can move this to ComponentAnalysis // TODO figure out if we can move this to ComponentAnalysis
accessors: boolean; accessors: boolean;
@ -39,6 +40,7 @@ export interface ComponentAnalysis extends Analysis {
/** Used for CSS pruning and scoping */ /** Used for CSS pruning and scoping */
elements: Array<AST.RegularElement | AST.SvelteElement>; elements: Array<AST.RegularElement | AST.SvelteElement>;
runes: boolean; runes: boolean;
tracing: boolean;
exports: Array<{ name: string; alias: string | null }>; exports: Array<{ name: string; alias: string | null }>;
/** Whether the component uses `$$props` */ /** Whether the component uses `$$props` */
uses_props: boolean; uses_props: boolean;

@ -17,10 +17,11 @@ export function init_array_prototype_warnings() {
const index = indexOf.call(this, item, from_index); const index = indexOf.call(this, item, from_index);
if (index === -1) { if (index === -1) {
const test = indexOf.call(get_proxied_value(this), get_proxied_value(item), from_index); for (let i = from_index ?? 0; i < this.length; i += 1) {
if (get_proxied_value(this[i]) === item) {
if (test !== -1) { w.state_proxy_equality_mismatch('array.indexOf(...)');
w.state_proxy_equality_mismatch('array.indexOf(...)'); break;
}
} }
} }
@ -33,16 +34,11 @@ export function init_array_prototype_warnings() {
const index = lastIndexOf.call(this, item, from_index ?? this.length - 1); const index = lastIndexOf.call(this, item, from_index ?? this.length - 1);
if (index === -1) { if (index === -1) {
// we need to specify this.length - 1 because it's probably using something like for (let i = 0; i <= (from_index ?? this.length - 1); i += 1) {
// `arguments` inside so passing undefined is different from not passing anything if (get_proxied_value(this[i]) === item) {
const test = lastIndexOf.call( w.state_proxy_equality_mismatch('array.lastIndexOf(...)');
get_proxied_value(this), break;
get_proxied_value(item), }
from_index ?? this.length - 1
);
if (test !== -1) {
w.state_proxy_equality_mismatch('array.lastIndexOf(...)');
} }
} }
@ -53,10 +49,11 @@ export function init_array_prototype_warnings() {
const has = includes.call(this, item, from_index); const has = includes.call(this, item, from_index);
if (!has) { if (!has) {
const test = includes.call(get_proxied_value(this), get_proxied_value(item), from_index); for (let i = 0; i < this.length; i += 1) {
if (get_proxied_value(this[i]) === item) {
if (test) { w.state_proxy_equality_mismatch('array.includes(...)');
w.state_proxy_equality_mismatch('array.includes(...)'); break;
}
} }
} }

@ -14,6 +14,7 @@ import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js';
import { UNINITIALIZED } from '../../constants.js'; import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js'; import * as e from './errors.js';
import { get_stack } from './dev/tracing.js'; import { get_stack } from './dev/tracing.js';
import { tracing_mode_flag } from '../flags/index.js';
/** /**
* @template T * @template T
@ -25,7 +26,7 @@ import { get_stack } from './dev/tracing.js';
export function proxy(value, parent = null, prev) { export function proxy(value, parent = null, prev) {
/** @type {Error | null} */ /** @type {Error | null} */
var stack = null; var stack = null;
if (DEV) { if (DEV && tracing_mode_flag) {
stack = get_stack('CreatedAt'); stack = get_stack('CreatedAt');
} }
// if non-proxyable, or is already a proxy, return `value` // if non-proxyable, or is already a proxy, return `value`

@ -25,6 +25,7 @@ import * as e from '../errors.js';
import { destroy_effect } from './effects.js'; import { destroy_effect } from './effects.js';
import { inspect_effects, set_inspect_effects } from './sources.js'; import { inspect_effects, set_inspect_effects } from './sources.js';
import { get_stack } from '../dev/tracing.js'; import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js';
/** /**
* @template V * @template V
@ -62,7 +63,7 @@ export function derived(fn) {
parent: parent_derived ?? active_effect parent: parent_derived ?? active_effect
}; };
if (DEV) { if (DEV && tracing_mode_flag) {
signal.created = get_stack('CreatedAt'); signal.created = get_stack('CreatedAt');
} }

@ -32,7 +32,7 @@ import {
BLOCK_EFFECT BLOCK_EFFECT
} from '../constants.js'; } from '../constants.js';
import * as e from '../errors.js'; import * as e from '../errors.js';
import { legacy_mode_flag } from '../../flags/index.js'; import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack } from '../dev/tracing.js'; import { get_stack } from '../dev/tracing.js';
export let inspect_effects = new Set(); export let inspect_effects = new Set();
@ -60,7 +60,7 @@ export function source(v, stack) {
version: 0 version: 0
}; };
if (DEV) { if (DEV && tracing_mode_flag) {
signal.created = stack ?? get_stack('CreatedAt'); signal.created = stack ?? get_stack('CreatedAt');
signal.debug = null; signal.debug = null;
} }
@ -170,7 +170,7 @@ export function internal_set(source, value) {
source.v = value; source.v = value;
source.version = increment_version(); source.version = increment_version();
if (DEV) { if (DEV && tracing_mode_flag) {
source.updated = get_stack('UpdatedAt'); source.updated = get_stack('UpdatedAt');
} }

@ -34,7 +34,7 @@ import { destroy_derived, execute_derived, update_derived } from './reactivity/d
import * as e from './errors.js'; import * as e from './errors.js';
import { lifecycle_outside_component } from '../shared/errors.js'; import { lifecycle_outside_component } from '../shared/errors.js';
import { FILENAME } from '../../constants.js'; import { FILENAME } from '../../constants.js';
import { legacy_mode_flag } from '../flags/index.js'; import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js';
import { tracing_expressions, get_stack } from './dev/tracing.js'; import { tracing_expressions, get_stack } from './dev/tracing.js';
const FLUSH_MICROTASK = 0; const FLUSH_MICROTASK = 0;
@ -127,8 +127,8 @@ export function set_untracked_writes(value) {
untracked_writes = value; untracked_writes = value;
} }
/** @type {number} Used by sources and deriveds for handling updates to unowned deriveds */ /** @type {number} Used by sources and deriveds for handling updates to unowned deriveds it starts from 1 to differentiate between a created effect and a run one for tracing */
let current_version = 0; let current_version = 1;
// If we are working with a get() chain that has no active container, // If we are working with a get() chain that has no active container,
// to prevent memory leaks, we skip adding the reaction. // to prevent memory leaks, we skip adding the reaction.
@ -230,8 +230,9 @@ export function check_dirtiness(reaction) {
} }
} }
// Unowned signals should never be marked as clean. // Unowned signals should never be marked as clean unless they
if (!is_unowned) { // are used within an active_effect without skip_reaction
if (!is_unowned || (active_effect !== null && !skip_reaction)) {
set_signal_status(reaction, CLEAN); set_signal_status(reaction, CLEAN);
} }
} }
@ -916,6 +917,7 @@ export function get(signal) {
if ( if (
DEV && DEV &&
tracing_mode_flag &&
tracing_expressions !== null && tracing_expressions !== null &&
active_reaction !== null && active_reaction !== null &&
tracing_expressions.reaction === active_reaction tracing_expressions.reaction === active_reaction

@ -1,5 +1,10 @@
export let legacy_mode_flag = false; export let legacy_mode_flag = false;
export let tracing_mode_flag = false;
export function enable_legacy_mode_flag() { export function enable_legacy_mode_flag() {
legacy_mode_flag = true; legacy_mode_flag = true;
} }
export function enable_tracing_mode_flag() {
tracing_mode_flag = true;
}

@ -0,0 +1,3 @@
import { enable_tracing_mode_flag } from './index.js';
enable_tracing_mode_flag();

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

@ -9,3 +9,15 @@
asd{a.}asd asd{a.}asd
{foo[bar.]} {foo[bar.]}
{#if x.}{/if}
{#each array as item (item.)}{/each}
{#each obj. as item}{/each}
{#await x.}{/await}
{#await x. then y}{/await}
{#await x. catch y}{/await}

@ -2,7 +2,7 @@
"html": { "html": {
"type": "Fragment", "type": "Fragment",
"start": 0, "start": 0,
"end": 164, "end": 324,
"children": [ "children": [
{ {
"type": "Element", "type": "Element",
@ -236,6 +236,272 @@
"end": 163, "end": 163,
"name": "" "name": ""
} }
},
{
"type": "Text",
"start": 164,
"end": 166,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "IfBlock",
"start": 166,
"end": 179,
"expression": {
"type": "Identifier",
"start": 171,
"end": 173,
"name": ""
},
"children": []
},
{
"type": "Text",
"start": 179,
"end": 181,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "EachBlock",
"start": 181,
"end": 217,
"children": [],
"context": {
"type": "Identifier",
"name": "item",
"start": 197,
"loc": {
"start": {
"line": 15,
"column": 16,
"character": 197
},
"end": {
"line": 15,
"column": 20,
"character": 201
}
},
"end": 201
},
"expression": {
"type": "Identifier",
"start": 188,
"end": 193,
"loc": {
"start": {
"line": 15,
"column": 7
},
"end": {
"line": 15,
"column": 12
}
},
"name": "array"
},
"key": {
"type": "Identifier",
"start": 203,
"end": 208,
"name": ""
}
},
{
"type": "Text",
"start": 217,
"end": 219,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "EachBlock",
"start": 219,
"end": 246,
"children": [],
"context": {
"type": "Identifier",
"name": "item",
"start": 234,
"loc": {
"start": {
"line": 17,
"column": 15,
"character": 234
},
"end": {
"line": 17,
"column": 19,
"character": 238
}
},
"end": 238
},
"expression": {
"type": "Identifier",
"name": "",
"start": 226,
"end": 230
}
},
{
"type": "Text",
"start": 246,
"end": 248,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 248,
"end": 267,
"expression": {
"type": "Identifier",
"start": 256,
"end": 258,
"name": ""
},
"value": null,
"error": null,
"pending": {
"type": "PendingBlock",
"start": 259,
"end": 259,
"children": [],
"skip": false
},
"then": {
"type": "ThenBlock",
"start": null,
"end": null,
"children": [],
"skip": true
},
"catch": {
"type": "CatchBlock",
"start": null,
"end": null,
"children": [],
"skip": true
}
},
{
"type": "Text",
"start": 267,
"end": 269,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 269,
"end": 295,
"expression": {
"type": "Identifier",
"name": "",
"start": 277,
"end": 279
},
"value": {
"type": "Identifier",
"name": "y",
"start": 285,
"loc": {
"start": {
"line": 21,
"column": 16,
"character": 285
},
"end": {
"line": 21,
"column": 17,
"character": 286
}
},
"end": 286
},
"error": null,
"pending": {
"type": "PendingBlock",
"start": null,
"end": null,
"children": [],
"skip": true
},
"then": {
"type": "ThenBlock",
"start": 287,
"end": 267,
"children": [],
"skip": false
},
"catch": {
"type": "CatchBlock",
"start": null,
"end": null,
"children": [],
"skip": true
}
},
{
"type": "Text",
"start": 295,
"end": 297,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 297,
"end": 324,
"expression": {
"type": "Identifier",
"name": "",
"start": 305,
"end": 307
},
"value": null,
"error": {
"type": "Identifier",
"name": "y",
"start": 314,
"loc": {
"start": {
"line": 23,
"column": 17,
"character": 314
},
"end": {
"line": 23,
"column": 18,
"character": 315
}
},
"end": 315
},
"pending": {
"type": "PendingBlock",
"start": null,
"end": null,
"children": [],
"skip": true
},
"then": {
"type": "ThenBlock",
"start": null,
"end": null,
"children": [],
"skip": true
},
"catch": {
"type": "CatchBlock",
"start": 316,
"end": 295,
"children": [],
"skip": false
}
} }
] ]
} }

@ -10,6 +10,14 @@
<span <span
</div> </div>
<div>
<Comp.
</div>
<div>
<comp.
</div>
{#if foo} {#if foo}
<div> <div>
{/if} {/if}

@ -2,7 +2,7 @@
"html": { "html": {
"type": "Fragment", "type": "Fragment",
"start": 0, "start": 0,
"end": 160, "end": 204,
"children": [ "children": [
{ {
"type": "Element", "type": "Element",
@ -136,20 +136,82 @@
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "IfBlock", "type": "Element",
"start": 74, "start": 74,
"end": 94,
"name": "div",
"attributes": [],
"children": [
{
"type": "Text",
"start": 79,
"end": 81,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "InlineComponent",
"start": 81,
"end": 88,
"name": "Comp.",
"attributes": [],
"children": []
}
]
},
{
"type": "Text",
"start": 94,
"end": 96, "end": 96,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "Element",
"start": 96,
"end": 116,
"name": "div",
"attributes": [],
"children": [
{
"type": "Text",
"start": 101,
"end": 103,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "InlineComponent",
"start": 103,
"end": 110,
"name": "comp.",
"attributes": [],
"children": []
}
]
},
{
"type": "Text",
"start": 116,
"end": 118,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "IfBlock",
"start": 118,
"end": 140,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 79, "start": 123,
"end": 82, "end": 126,
"loc": { "loc": {
"start": { "start": {
"line": 13, "line": 21,
"column": 5 "column": 5
}, },
"end": { "end": {
"line": 13, "line": 21,
"column": 8 "column": 8
} }
}, },
@ -158,15 +220,15 @@
"children": [ "children": [
{ {
"type": "Element", "type": "Element",
"start": 85, "start": 129,
"end": 91, "end": 135,
"name": "div", "name": "div",
"attributes": [], "attributes": [],
"children": [ "children": [
{ {
"type": "Text", "type": "Text",
"start": 90, "start": 134,
"end": 91, "end": 135,
"raw": "\n", "raw": "\n",
"data": "\n" "data": "\n"
} }
@ -176,26 +238,26 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 96, "start": 140,
"end": 98, "end": 142,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "IfBlock", "type": "IfBlock",
"start": 98, "start": 142,
"end": 130, "end": 174,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 103, "start": 147,
"end": 106, "end": 150,
"loc": { "loc": {
"start": { "start": {
"line": 17, "line": 25,
"column": 5 "column": 5
}, },
"end": { "end": {
"line": 17, "line": 25,
"column": 8 "column": 8
} }
}, },
@ -204,31 +266,31 @@
"children": [ "children": [
{ {
"type": "InlineComponent", "type": "InlineComponent",
"start": 109, "start": 153,
"end": 125, "end": 169,
"name": "Comp", "name": "Comp",
"attributes": [ "attributes": [
{ {
"type": "Attribute", "type": "Attribute",
"start": 115, "start": 159,
"end": 124, "end": 168,
"name": "foo", "name": "foo",
"value": [ "value": [
{ {
"type": "MustacheTag", "type": "MustacheTag",
"start": 119, "start": 163,
"end": 124, "end": 168,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 120, "start": 164,
"end": 123, "end": 167,
"loc": { "loc": {
"start": { "start": {
"line": 18, "line": 26,
"column": 12 "column": 12
}, },
"end": { "end": {
"line": 18, "line": 26,
"column": 15 "column": 15
} }
}, },
@ -244,36 +306,36 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 130, "start": 174,
"end": 132, "end": 176,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "Element", "type": "Element",
"start": 132, "start": 176,
"end": 160, "end": 204,
"name": "div", "name": "div",
"attributes": [], "attributes": [],
"children": [ "children": [
{ {
"type": "Text", "type": "Text",
"start": 137, "start": 181,
"end": 138, "end": 182,
"raw": "\n", "raw": "\n",
"data": "\n" "data": "\n"
}, },
{ {
"type": "Element", "type": "Element",
"start": 138, "start": 182,
"end": 147, "end": 191,
"name": "p", "name": "p",
"attributes": [], "attributes": [],
"children": [ "children": [
{ {
"type": "Text", "type": "Text",
"start": 141, "start": 185,
"end": 143, "end": 187,
"raw": "hi", "raw": "hi",
"data": "hi" "data": "hi"
} }
@ -281,15 +343,15 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 147, "start": 191,
"end": 149, "end": 193,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "Element", "type": "Element",
"start": 149, "start": 193,
"end": 160, "end": 204,
"name": "open-ended", "name": "open-ended",
"attributes": [], "attributes": [],
"children": [] "children": []

@ -9,3 +9,15 @@
asd{a.}asd asd{a.}asd
{foo[bar.]} {foo[bar.]}
{#if x.}{/if}
{#each array as item (item.)}{/each}
{#each obj. as item}{/each}
{#await x.}{/await}
{#await x. then y}{/await}
{#await x. catch y}{/await}

@ -2,7 +2,7 @@
"css": null, "css": null,
"js": [], "js": [],
"start": 0, "start": 0,
"end": 164, "end": 324,
"type": "Root", "type": "Root",
"fragment": { "fragment": {
"type": "Fragment", "type": "Fragment",
@ -247,6 +247,238 @@
"end": 163, "end": 163,
"name": "" "name": ""
} }
},
{
"type": "Text",
"start": 164,
"end": 166,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "IfBlock",
"elseif": false,
"start": 166,
"end": 179,
"test": {
"type": "Identifier",
"start": 171,
"end": 173,
"name": ""
},
"consequent": {
"type": "Fragment",
"nodes": []
},
"alternate": null
},
{
"type": "Text",
"start": 179,
"end": 181,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "EachBlock",
"start": 181,
"end": 217,
"expression": {
"type": "Identifier",
"start": 188,
"end": 193,
"loc": {
"start": {
"line": 15,
"column": 7
},
"end": {
"line": 15,
"column": 12
}
},
"name": "array"
},
"body": {
"type": "Fragment",
"nodes": []
},
"context": {
"type": "Identifier",
"name": "item",
"start": 197,
"loc": {
"start": {
"line": 15,
"column": 16,
"character": 197
},
"end": {
"line": 15,
"column": 20,
"character": 201
}
},
"end": 201
},
"key": {
"type": "Identifier",
"start": 203,
"end": 208,
"name": ""
}
},
{
"type": "Text",
"start": 217,
"end": 219,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "EachBlock",
"start": 219,
"end": 246,
"expression": {
"type": "Identifier",
"name": "",
"start": 226,
"end": 230
},
"body": {
"type": "Fragment",
"nodes": []
},
"context": {
"type": "Identifier",
"name": "item",
"start": 234,
"loc": {
"start": {
"line": 17,
"column": 15,
"character": 234
},
"end": {
"line": 17,
"column": 19,
"character": 238
}
},
"end": 238
}
},
{
"type": "Text",
"start": 246,
"end": 248,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 248,
"end": 267,
"expression": {
"type": "Identifier",
"start": 256,
"end": 258,
"name": ""
},
"value": null,
"error": null,
"pending": {
"type": "Fragment",
"nodes": []
},
"then": null,
"catch": null
},
{
"type": "Text",
"start": 267,
"end": 269,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 269,
"end": 295,
"expression": {
"type": "Identifier",
"name": "",
"start": 277,
"end": 279
},
"value": {
"type": "Identifier",
"name": "y",
"start": 285,
"loc": {
"start": {
"line": 21,
"column": 16,
"character": 285
},
"end": {
"line": 21,
"column": 17,
"character": 286
}
},
"end": 286
},
"error": null,
"pending": null,
"then": {
"type": "Fragment",
"nodes": []
},
"catch": null
},
{
"type": "Text",
"start": 295,
"end": 297,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "AwaitBlock",
"start": 297,
"end": 324,
"expression": {
"type": "Identifier",
"name": "",
"start": 305,
"end": 307
},
"value": null,
"error": {
"type": "Identifier",
"name": "y",
"start": 314,
"loc": {
"start": {
"line": 23,
"column": 17,
"character": 314
},
"end": {
"line": 23,
"column": 18,
"character": 315
}
},
"end": 315
},
"pending": null,
"then": null,
"catch": {
"type": "Fragment",
"nodes": []
}
} }
] ]
}, },

@ -10,6 +10,14 @@
<span <span
</div> </div>
<div>
<Comp.
</div>
<div>
<comp.
</div>
{#if foo} {#if foo}
<div> <div>
{/if} {/if}

@ -2,7 +2,7 @@
"css": null, "css": null,
"js": [], "js": [],
"start": 0, "start": 0,
"end": 160, "end": 204,
"type": "Root", "type": "Root",
"fragment": { "fragment": {
"type": "Fragment", "type": "Fragment",
@ -155,21 +155,95 @@
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "IfBlock", "type": "RegularElement",
"elseif": false,
"start": 74, "start": 74,
"end": 94,
"name": "div",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 79,
"end": 81,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "Component",
"start": 81,
"end": 88,
"name": "Comp.",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": []
}
}
]
}
},
{
"type": "Text",
"start": 94,
"end": 96, "end": 96,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "RegularElement",
"start": 96,
"end": 116,
"name": "div",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 101,
"end": 103,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "Component",
"start": 103,
"end": 110,
"name": "comp.",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": []
}
}
]
}
},
{
"type": "Text",
"start": 116,
"end": 118,
"raw": "\n\n",
"data": "\n\n"
},
{
"type": "IfBlock",
"elseif": false,
"start": 118,
"end": 140,
"test": { "test": {
"type": "Identifier", "type": "Identifier",
"start": 79, "start": 123,
"end": 82, "end": 126,
"loc": { "loc": {
"start": { "start": {
"line": 13, "line": 21,
"column": 5 "column": 5
}, },
"end": { "end": {
"line": 13, "line": 21,
"column": 8 "column": 8
} }
}, },
@ -180,15 +254,15 @@
"nodes": [ "nodes": [
{ {
"type": "Text", "type": "Text",
"start": 83, "start": 127,
"end": 85, "end": 129,
"raw": "\n\t", "raw": "\n\t",
"data": "\n\t" "data": "\n\t"
}, },
{ {
"type": "RegularElement", "type": "RegularElement",
"start": 85, "start": 129,
"end": 91, "end": 135,
"name": "div", "name": "div",
"attributes": [], "attributes": [],
"fragment": { "fragment": {
@ -196,8 +270,8 @@
"nodes": [ "nodes": [
{ {
"type": "Text", "type": "Text",
"start": 90, "start": 134,
"end": 91, "end": 135,
"raw": "\n", "raw": "\n",
"data": "\n" "data": "\n"
} }
@ -210,27 +284,27 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 96, "start": 140,
"end": 98, "end": 142,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "IfBlock", "type": "IfBlock",
"elseif": false, "elseif": false,
"start": 98, "start": 142,
"end": 130, "end": 174,
"test": { "test": {
"type": "Identifier", "type": "Identifier",
"start": 103, "start": 147,
"end": 106, "end": 150,
"loc": { "loc": {
"start": { "start": {
"line": 17, "line": 25,
"column": 5 "column": 5
}, },
"end": { "end": {
"line": 17, "line": 25,
"column": 8 "column": 8
} }
}, },
@ -241,37 +315,37 @@
"nodes": [ "nodes": [
{ {
"type": "Text", "type": "Text",
"start": 107, "start": 151,
"end": 109, "end": 153,
"raw": "\n\t", "raw": "\n\t",
"data": "\n\t" "data": "\n\t"
}, },
{ {
"type": "Component", "type": "Component",
"start": 109, "start": 153,
"end": 125, "end": 169,
"name": "Comp", "name": "Comp",
"attributes": [ "attributes": [
{ {
"type": "Attribute", "type": "Attribute",
"start": 115, "start": 159,
"end": 124, "end": 168,
"name": "foo", "name": "foo",
"value": { "value": {
"type": "ExpressionTag", "type": "ExpressionTag",
"start": 119, "start": 163,
"end": 124, "end": 168,
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 120, "start": 164,
"end": 123, "end": 167,
"loc": { "loc": {
"start": { "start": {
"line": 18, "line": 26,
"column": 12 "column": 12
}, },
"end": { "end": {
"line": 18, "line": 26,
"column": 15 "column": 15
} }
}, },
@ -291,15 +365,15 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 130, "start": 174,
"end": 132, "end": 176,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "RegularElement", "type": "RegularElement",
"start": 132, "start": 176,
"end": 160, "end": 204,
"name": "div", "name": "div",
"attributes": [], "attributes": [],
"fragment": { "fragment": {
@ -307,15 +381,15 @@
"nodes": [ "nodes": [
{ {
"type": "Text", "type": "Text",
"start": 137, "start": 181,
"end": 138, "end": 182,
"raw": "\n", "raw": "\n",
"data": "\n" "data": "\n"
}, },
{ {
"type": "RegularElement", "type": "RegularElement",
"start": 138, "start": 182,
"end": 147, "end": 191,
"name": "p", "name": "p",
"attributes": [], "attributes": [],
"fragment": { "fragment": {
@ -323,8 +397,8 @@
"nodes": [ "nodes": [
{ {
"type": "Text", "type": "Text",
"start": 141, "start": 185,
"end": 143, "end": 187,
"raw": "hi", "raw": "hi",
"data": "hi" "data": "hi"
} }
@ -333,15 +407,15 @@
}, },
{ {
"type": "Text", "type": "Text",
"start": 147, "start": 191,
"end": 149, "end": 193,
"raw": "\n\n", "raw": "\n\n",
"data": "\n\n" "data": "\n\n"
}, },
{ {
"type": "RegularElement", "type": "RegularElement",
"start": 149, "start": 193,
"end": 160, "end": 204,
"name": "open-ended", "name": "open-ended",
"attributes": [], "attributes": [],
"fragment": { "fragment": {

@ -11,12 +11,15 @@ function normalise_trace_logs(logs) {
if (typeof log === 'string' && log.includes('%c')) { if (typeof log === 'string' && log.includes('%c')) {
const split = log.split('%c'); const split = log.split('%c');
normalised.push((split[0].length !== 0 ? split[0] : split[1]).trim()); normalised.push({
log: (split[0].length !== 0 ? split[0] : split[1]).trim(),
highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold'
});
i++; i++;
} else if (log instanceof Error) { } else if (log instanceof Error) {
continue; continue;
} else { } else {
normalised.push(log); normalised.push({ log });
} }
} }
return normalised; return normalised;
@ -28,7 +31,17 @@ export default test({
}, },
test({ assert, target, logs }) { test({ assert, target, logs }) {
assert.deepEqual(normalise_trace_logs(logs), ['effect', '$derived', 0, '$state', 0]); // initial log, everything is highlighted
assert.deepEqual(normalise_trace_logs(logs), [
{ log: 'effect', highlighted: false },
{ log: '$derived', highlighted: true },
{ log: 0 },
{ log: '$state', highlighted: true },
{ log: 0 },
{ log: '$state', highlighted: true },
{ log: false }
]);
logs.length = 0; logs.length = 0;
@ -36,6 +49,49 @@ export default test({
button?.click(); button?.click();
flushSync(); flushSync();
assert.deepEqual(normalise_trace_logs(logs), ['effect', '$derived', 2, '$state', 1]); // count changed, derived and state are highlighted, last state is not
assert.deepEqual(normalise_trace_logs(logs), [
{ log: 'effect', highlighted: false },
{ log: '$derived', highlighted: true },
{ log: 2 },
{ log: '$state', highlighted: true },
{ log: 1 },
{ log: '$state', highlighted: false },
{ log: false }
]);
logs.length = 0;
const input = target.querySelector('input');
input?.click();
flushSync();
// checked changed, last state is highlighted, first two are not
assert.deepEqual(normalise_trace_logs(logs), [
{ log: 'effect', highlighted: false },
{ log: '$derived', highlighted: false },
{ log: 2 },
{ log: '$state', highlighted: false },
{ log: 1 },
{ log: '$state', highlighted: true },
{ log: true }
]);
logs.length = 0;
button?.click();
flushSync();
// count change and derived it's >=4, checked is not in the dependencies anymore
assert.deepEqual(normalise_trace_logs(logs), [
{ log: 'effect', highlighted: false },
{ log: '$derived', highlighted: true },
{ log: 4 },
{ log: '$state', highlighted: true },
{ log: 2 }
]);
} }
}); });

@ -2,11 +2,15 @@
let count = $state(0); let count = $state(0);
let double = $derived(count * 2); let double = $derived(count * 2);
let checked = $state(false);
$effect(() => { $effect(() => {
$inspect.trace('effect'); $inspect.trace('effect');
double; double;
}) double >= 4 || checked;
});
</script> </script>
<button onclick={() => count++}>{double}</button> <button onclick={() => count++}>{double}</button>
<input type="checkbox" bind:checked />

@ -0,0 +1,41 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, target, warnings }) {
const [btn1, btn2, btn3, btn4, btn5, btn6, clear] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
btn2.click();
btn3.click();
btn4.click();
btn5.click();
btn6.click();
});
assert.deepEqual(warnings, [
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.includes(...)` will produce unexpected results',
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.indexOf(...)` will produce unexpected results',
'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.lastIndexOf(...)` will produce unexpected results'
]);
flushSync(() => clear.click());
warnings.length = 0;
flushSync(() => {
btn1.click();
btn2.click();
btn3.click();
btn4.click();
btn5.click();
btn6.click();
});
assert.deepEqual(warnings, []);
}
});

@ -0,0 +1,23 @@
<script>
let primitive = 'foo';
let object = {};
let array = $state([primitive, object]);
</script>
<button onclick={() => array.includes(primitive)}>array.includes(primitive)</button>
<button onclick={() => array.includes(object)}>array.includes(object)</button>
<hr />
<button onclick={() => array.indexOf(primitive)}>array.indexOf(primitive)</button>
<button onclick={() => array.indexOf(object)}>array.indexOf(object)</button>
<hr />
<button onclick={() => array.lastIndexOf(primitive)}>array.lastIndexOf(primitive)</button>
<button onclick={() => array.lastIndexOf(object)}>array.lastIndexOf(object)</button>
<hr />
<button onclick={() => (array.length = 0)}>clear</button>

@ -0,0 +1,11 @@
import { test, ok } from '../../test';
export default test({
html: `<svg><a href="/docs"><text class="small" x="20" y="40"></text></a></svg>`,
test({ assert, target }) {
const a = target.querySelector('a');
ok(a);
assert.equal(a.namespaceURI, 'http://www.w3.org/2000/svg');
}
});

@ -0,0 +1,7 @@
<svg>
{#if true}
<a href="/docs">
<text x="20" y="40" class="small">{name}</text>
</a>
{/if}
</svg>

After

Width:  |  Height:  |  Size: 109 B

@ -8,6 +8,10 @@
console.log(this); console.log(this);
} }
function foo(): string {
return ""!;
}
class Foo<T> { class Foo<T> {
public name: string; public name: string;
x = 'x' as const; x = 'x' as const;
@ -16,6 +20,8 @@
} }
} }
class MyClass implements Hello {}
declare const declared_const: number; declare const declared_const: number;
declare function declared_fn(): void; declare function declared_fn(): void;
declare class declared_class { declare class declared_class {
@ -24,7 +30,7 @@
declare module 'foobar' {} declare module 'foobar' {}
namespace SomeNamespace { namespace SomeNamespace {
export type Foo = true export type Foo = true;
} }
export function overload(a: boolean): boolean; export function overload(a: boolean): boolean;

@ -40,5 +40,9 @@
"./tests/runtime-browser/test-ssr.ts", "./tests/runtime-browser/test-ssr.ts",
"./tests/*/samples/*/_config.js" "./tests/*/samples/*/_config.js"
], ],
"exclude": ["./scripts/process-messages/templates/", "./src/compiler/optimizer/"] "exclude": [
"./scripts/process-messages/templates/",
"./scripts/_bundle.js",
"./src/compiler/optimizer/"
]
} }

@ -2996,6 +2996,29 @@ declare namespace $effect {
*/ */
declare function $props(): any; declare function $props(): any;
declare namespace $props {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/** /**
* Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it. * Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it.
* *
@ -3007,6 +3030,29 @@ declare function $props(): any;
*/ */
declare function $bindable<T>(fallback?: T): T; declare function $bindable<T>(fallback?: T): T;
declare namespace $bindable {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
/** /**
* Inspects one or more values whenever they, or the properties they contain, change. Example: * Inspects one or more values whenever they, or the properties they contain, change. Example:
* *
@ -3046,6 +3092,27 @@ declare namespace $inspect {
* </script> * </script>
*/ */
export function trace(name: string): void; export function trace(name: string): void;
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
} }
/** /**
@ -3069,4 +3136,27 @@ declare namespace $inspect {
*/ */
declare function $host<El extends HTMLElement = HTMLElement>(): El; declare function $host<El extends HTMLElement = HTMLElement>(): El;
//# sourceMappingURL=index.d.ts.map declare namespace $host {
// prevent intellisense from being unhelpful
/** @deprecated */
export const apply: never;
/** @deprecated */
// @ts-ignore
export const arguments: never;
/** @deprecated */
export const bind: never;
/** @deprecated */
export const call: never;
/** @deprecated */
export const caller: never;
/** @deprecated */
export const length: never;
/** @deprecated */
export const name: never;
/** @deprecated */
export const prototype: never;
/** @deprecated */
export const toString: never;
}
//# sourceMappingURL=index.d.ts.map

@ -84,8 +84,8 @@ importers:
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
esrap: esrap:
specifier: ^1.2.3 specifier: ^1.3.2
version: 1.2.3 version: 1.3.2
is-reference: is-reference:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
@ -908,7 +908,7 @@ packages:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cross-spawn@5.1.0: cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@ -1111,8 +1111,8 @@ packages:
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
esrap@1.2.3: esrap@1.3.2:
resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==} resolution: {integrity: sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==}
esrecurse@4.3.0: esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
@ -3315,10 +3315,9 @@ snapshots:
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
esrap@1.2.3: esrap@1.3.2:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
'@types/estree': 1.0.6
esrecurse@4.3.0: esrecurse@4.3.0:
dependencies: dependencies:

Loading…
Cancel
Save