svelte 5.55.10 → 5.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/index.js +1 -1
- package/package.json +7 -4
- package/src/compiler/errors.js +18 -0
- package/src/compiler/legacy.js +4 -0
- package/src/compiler/phases/1-parse/acorn.js +44 -1
- package/src/compiler/phases/1-parse/index.js +4 -1
- package/src/compiler/phases/1-parse/state/tag.js +91 -3
- package/src/compiler/phases/2-analyze/index.js +5 -0
- package/src/compiler/phases/2-analyze/visitors/CallExpression.js +3 -0
- package/src/compiler/phases/2-analyze/visitors/ConstTag.js +2 -25
- package/src/compiler/phases/2-analyze/visitors/DeclarationTag.js +58 -0
- package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -1
- package/src/compiler/phases/3-transform/client/transform-client.js +5 -15
- package/src/compiler/phases/3-transform/client/transform-template/index.js +40 -3
- package/src/compiler/phases/3-transform/client/utils.js +21 -0
- package/src/compiler/phases/3-transform/client/visitors/ConstTag.js +13 -24
- package/src/compiler/phases/3-transform/client/visitors/DeclarationTag.js +87 -0
- package/src/compiler/phases/3-transform/client/visitors/Fragment.js +2 -5
- package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +30 -8
- package/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +7 -2
- package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +12 -2
- package/src/compiler/phases/3-transform/server/transform-server.js +2 -0
- package/src/compiler/phases/3-transform/server/visitors/ConstTag.js +9 -24
- package/src/compiler/phases/3-transform/server/visitors/DeclarationTag.js +85 -0
- package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +24 -7
- package/src/compiler/phases/3-transform/utils.js +1 -0
- package/src/compiler/phases/nodes.js +1 -0
- package/src/compiler/print/index.js +42 -0
- package/src/compiler/utils/builders.js +2 -1
- package/src/internal/client/dom/blocks/boundary.js +1 -1
- package/src/internal/client/dom/operations.js +12 -2
- package/src/internal/client/reactivity/batch.js +0 -21
- package/src/internal/client/reactivity/props.js +6 -6
- package/src/internal/client/reactivity/sources.js +1 -2
- package/src/internal/client/runtime.js +4 -8
- package/src/utils.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +7 -0
- package/types/index.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "svelte",
|
|
3
3
|
"description": "Cybernetically enhanced web apps",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.56.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
8
8
|
"engines": {
|
|
@@ -139,20 +139,22 @@
|
|
|
139
139
|
],
|
|
140
140
|
"devDependencies": {
|
|
141
141
|
"@jridgewell/trace-mapping": "^0.3.25",
|
|
142
|
-
"@playwright/test": "^1.
|
|
142
|
+
"@playwright/test": "^1.60.0",
|
|
143
143
|
"@rollup/plugin-commonjs": "^28.0.1",
|
|
144
144
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
145
145
|
"@rollup/plugin-terser": "^0.4.4",
|
|
146
146
|
"@rollup/plugin-virtual": "^3.0.2",
|
|
147
147
|
"@types/aria-query": "^5.0.4",
|
|
148
148
|
"@types/node": "^20.11.5",
|
|
149
|
+
"baseline-browser-mapping": "^2.10.32",
|
|
149
150
|
"dts-buddy": "^0.5.5",
|
|
150
151
|
"esbuild": "^0.25.10",
|
|
151
152
|
"rollup": "^4.59.0",
|
|
152
153
|
"source-map": "^0.7.4",
|
|
153
154
|
"tinyglobby": "^0.2.12",
|
|
154
155
|
"typescript": "^5.5.4",
|
|
155
|
-
"vitest": "^2.1.9"
|
|
156
|
+
"vitest": "^2.1.9",
|
|
157
|
+
"web-features": "^3.29.0"
|
|
156
158
|
},
|
|
157
159
|
"dependencies": {
|
|
158
160
|
"@jridgewell/remapping": "^2.3.4",
|
|
@@ -178,9 +180,10 @@
|
|
|
178
180
|
"check": "tsc --project tsconfig.runtime.json && tsc && cd ./tests/types && tsc",
|
|
179
181
|
"check:tsgo": "tsgo --project tsconfig.runtime.json --skipLibCheck && tsgo --skipLibCheck",
|
|
180
182
|
"check:watch": "tsc --watch",
|
|
181
|
-
"generate": "node scripts/process-messages && node ./scripts/generate-types.js",
|
|
183
|
+
"generate": "node scripts/process-messages && node ./scripts/generate-types.js && pnpm generate:browser-support",
|
|
182
184
|
"generate:version": "node ./scripts/generate-version.js",
|
|
183
185
|
"generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json",
|
|
186
|
+
"generate:browser-support": "node ./scripts/generate-browser-support.ts",
|
|
184
187
|
"knip": "pnpm dlx knip"
|
|
185
188
|
}
|
|
186
189
|
}
|
package/src/compiler/errors.js
CHANGED
|
@@ -1004,6 +1004,24 @@ export function debug_tag_invalid_arguments(node) {
|
|
|
1004
1004
|
e(node, 'debug_tag_invalid_arguments', `{@debug ...} arguments must be identifiers, not arbitrary expressions\nhttps://svelte.dev/e/debug_tag_invalid_arguments`);
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Declaration tags must be `let` or `const` declarations
|
|
1009
|
+
* @param {null | number | NodeLike} node
|
|
1010
|
+
* @returns {never}
|
|
1011
|
+
*/
|
|
1012
|
+
export function declaration_tag_invalid_type(node) {
|
|
1013
|
+
e(node, 'declaration_tag_invalid_type', `Declaration tags must be \`let\` or \`const\` declarations\nhttps://svelte.dev/e/declaration_tag_invalid_type`);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Declaration tags cannot be used in legacy mode
|
|
1018
|
+
* @param {null | number | NodeLike} node
|
|
1019
|
+
* @returns {never}
|
|
1020
|
+
*/
|
|
1021
|
+
export function declaration_tag_no_legacy_mode(node) {
|
|
1022
|
+
e(node, 'declaration_tag_no_legacy_mode', `Declaration tags cannot be used in legacy mode\nhttps://svelte.dev/e/declaration_tag_no_legacy_mode`);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1007
1025
|
/**
|
|
1008
1026
|
* Directive value must be a JavaScript expression enclosed in curly braces
|
|
1009
1027
|
* @param {null | number | NodeLike} node
|
package/src/compiler/legacy.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/** @import { Comment, Program } from 'estree' */
|
|
1
|
+
/** @import { Comment, Program, Statement } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { Parser } from './index.js' */
|
|
4
4
|
import * as acorn from 'acorn';
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
7
7
|
import * as e from '../../errors.js';
|
|
8
|
+
import { find_matching_bracket } from './utils/bracket.js';
|
|
8
9
|
|
|
9
10
|
const JSParser = acorn.Parser;
|
|
10
11
|
const TSParser = JSParser.extend(tsPlugin());
|
|
@@ -98,6 +99,48 @@ export function parse_expression_at(parser, source, index) {
|
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @param {Parser} parser
|
|
104
|
+
* @param {string} source
|
|
105
|
+
* @param {number} index
|
|
106
|
+
* @returns {Statement}
|
|
107
|
+
*/
|
|
108
|
+
export function parse_statement_at(parser, source, index) {
|
|
109
|
+
const acorn = parser.ts ? TSParser : JSParser;
|
|
110
|
+
let end = find_matching_bracket(source, index, '{');
|
|
111
|
+
if (end === undefined) e.unexpected_eof(source.length);
|
|
112
|
+
|
|
113
|
+
while (source[end - 1] === ';') {
|
|
114
|
+
end -= 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const padded_source = `${' '.repeat(index)}${source.slice(index, end)}`;
|
|
118
|
+
const { onComment, add_comments } = get_comment_handlers(
|
|
119
|
+
padded_source,
|
|
120
|
+
parser.root.comments,
|
|
121
|
+
index
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const ast = acorn.parse(padded_source, {
|
|
126
|
+
onComment,
|
|
127
|
+
sourceType: 'module',
|
|
128
|
+
ecmaVersion: 16,
|
|
129
|
+
locations: true
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
add_comments(ast);
|
|
133
|
+
|
|
134
|
+
const statement = /** @type {Statement} */ (
|
|
135
|
+
/** @type {unknown} */ (/** @type {Program} */ (ast).body[0])
|
|
136
|
+
);
|
|
137
|
+
statement.end = Math.min(/** @type {number} */ (statement.end), end);
|
|
138
|
+
return statement;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
handle_parse_error(e);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
101
144
|
const regex_position_indicator = / \(\d+:\d+\)$/;
|
|
102
145
|
|
|
103
146
|
/**
|
|
@@ -302,7 +302,10 @@ export class Parser {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
pop() {
|
|
305
|
-
this.fragments.pop();
|
|
305
|
+
const fragment = this.fragments.pop();
|
|
306
|
+
if (fragment?.metadata.transparent && fragment.nodes.some((n) => n.type === 'DeclarationTag')) {
|
|
307
|
+
fragment.metadata.transparent = false;
|
|
308
|
+
}
|
|
306
309
|
return this.stack.pop();
|
|
307
310
|
}
|
|
308
311
|
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
/** @import { ArrowFunctionExpression, Expression, Identifier, Pattern } from 'estree' */
|
|
1
|
+
/** @import { ArrowFunctionExpression, Expression, Identifier, Pattern, VariableDeclaration } from 'estree' */
|
|
2
2
|
/** @import { AST } from '#compiler' */
|
|
3
3
|
/** @import { Parser } from '../index.js' */
|
|
4
4
|
import { walk } from 'zimmerframe';
|
|
5
5
|
import * as e from '../../../errors.js';
|
|
6
6
|
import { ExpressionMetadata } from '../../nodes.js';
|
|
7
|
-
import { parse_expression_at } from '../acorn.js';
|
|
7
|
+
import { parse_expression_at, parse_statement_at } from '../acorn.js';
|
|
8
8
|
import read_pattern from '../read/context.js';
|
|
9
9
|
import read_expression, { get_loose_identifier } from '../read/expression.js';
|
|
10
10
|
import { create_fragment } from '../utils/create.js';
|
|
11
|
-
import { match_bracket } from '../utils/bracket.js';
|
|
11
|
+
import { find_matching_bracket, match_bracket } from '../utils/bracket.js';
|
|
12
12
|
|
|
13
13
|
const regex_whitespace_with_closing_curly_brace = /\s*}/y;
|
|
14
|
+
const regex_supported_declaration = /(?:let|const)\b/y;
|
|
15
|
+
// All except `type` are reserved keywords and cannot be used as variable names.
|
|
16
|
+
// For type we check if it's not something like `type .x` / `type ()` / `type % 2` / ...
|
|
17
|
+
const regex_unsupported_declaration = /(?:(?:var|interface|enum)\b)|(?:type\s+[^?.(`<[&|%^}])/y;
|
|
14
18
|
|
|
15
19
|
const pointy_bois = { '<': '>' };
|
|
16
20
|
|
|
@@ -31,6 +35,20 @@ export default function tag(parser) {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
const declaration = read_declaration(parser);
|
|
39
|
+
if (declaration) {
|
|
40
|
+
parser.append({
|
|
41
|
+
type: 'DeclarationTag',
|
|
42
|
+
start,
|
|
43
|
+
end: parser.index,
|
|
44
|
+
declaration: /** @type {VariableDeclaration} */ (declaration),
|
|
45
|
+
metadata: {
|
|
46
|
+
expression: new ExpressionMetadata()
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
const expression = read_expression(parser);
|
|
35
53
|
|
|
36
54
|
parser.allow_whitespace();
|
|
@@ -47,6 +65,76 @@ export default function tag(parser) {
|
|
|
47
65
|
});
|
|
48
66
|
}
|
|
49
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @param {Parser} parser
|
|
70
|
+
* @returns {null | import('estree').VariableDeclaration}
|
|
71
|
+
*/
|
|
72
|
+
function read_declaration(parser) {
|
|
73
|
+
const start = parser.index;
|
|
74
|
+
|
|
75
|
+
const unsupported = parser.match_regex(regex_unsupported_declaration);
|
|
76
|
+
if (unsupported) {
|
|
77
|
+
e.declaration_tag_invalid_type({ start, end: start + unsupported.length });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!parser.match_regex(regex_supported_declaration)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @type {import('estree').Statement | import('estree').VariableDeclaration} */
|
|
85
|
+
let declaration;
|
|
86
|
+
try {
|
|
87
|
+
declaration = parse_statement_at(parser, parser.template, start);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (!parser.loose) throw error;
|
|
90
|
+
|
|
91
|
+
const end = find_matching_bracket(parser.template, start, '{');
|
|
92
|
+
if (end === undefined) throw error;
|
|
93
|
+
|
|
94
|
+
parser.index = end;
|
|
95
|
+
const kind = parser.template.startsWith('const', start) ? 'const' : 'let';
|
|
96
|
+
|
|
97
|
+
declaration = {
|
|
98
|
+
type: 'VariableDeclaration',
|
|
99
|
+
kind,
|
|
100
|
+
declarations: [
|
|
101
|
+
{
|
|
102
|
+
type: 'VariableDeclarator',
|
|
103
|
+
id: {
|
|
104
|
+
type: 'Identifier',
|
|
105
|
+
name: '',
|
|
106
|
+
start: parser.index,
|
|
107
|
+
end: parser.index
|
|
108
|
+
},
|
|
109
|
+
init: null,
|
|
110
|
+
start: parser.index,
|
|
111
|
+
end: parser.index
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
start,
|
|
115
|
+
end
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (declaration.type !== 'VariableDeclaration') {
|
|
120
|
+
e.declaration_tag_invalid_type({
|
|
121
|
+
start: declaration.start ?? start,
|
|
122
|
+
end: declaration.end ?? parser.index
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// TODO support using
|
|
127
|
+
if (declaration.kind !== 'let' && declaration.kind !== 'const') {
|
|
128
|
+
e.declaration_tag_invalid_type(declaration);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
parser.index = /** @type {number} */ (declaration.end);
|
|
132
|
+
parser.allow_whitespace();
|
|
133
|
+
parser.eat('}', true);
|
|
134
|
+
|
|
135
|
+
return declaration;
|
|
136
|
+
}
|
|
137
|
+
|
|
50
138
|
/** @param {Parser} parser */
|
|
51
139
|
function open(parser) {
|
|
52
140
|
let start = parser.index - 2;
|
|
@@ -36,6 +36,7 @@ import { ClassDeclaration } from './visitors/ClassDeclaration.js';
|
|
|
36
36
|
import { ClassDirective } from './visitors/ClassDirective.js';
|
|
37
37
|
import { Component } from './visitors/Component.js';
|
|
38
38
|
import { ConstTag } from './visitors/ConstTag.js';
|
|
39
|
+
import { DeclarationTag } from './visitors/DeclarationTag.js';
|
|
39
40
|
import { DebugTag } from './visitors/DebugTag.js';
|
|
40
41
|
import { EachBlock } from './visitors/EachBlock.js';
|
|
41
42
|
import { ExportDefaultDeclaration } from './visitors/ExportDefaultDeclaration.js';
|
|
@@ -157,6 +158,7 @@ const visitors = {
|
|
|
157
158
|
ClassDirective,
|
|
158
159
|
Component,
|
|
159
160
|
ConstTag,
|
|
161
|
+
DeclarationTag,
|
|
160
162
|
DebugTag,
|
|
161
163
|
EachBlock,
|
|
162
164
|
ExportDefaultDeclaration,
|
|
@@ -312,6 +314,7 @@ export function analyze_module(source, options) {
|
|
|
312
314
|
options: /** @type {ValidatedCompileOptions} */ (options),
|
|
313
315
|
fragment: null,
|
|
314
316
|
parent_element: null,
|
|
317
|
+
in_declaration_tag: false,
|
|
315
318
|
reactive_statement: null,
|
|
316
319
|
derived_function_depth: -1
|
|
317
320
|
},
|
|
@@ -718,6 +721,7 @@ export function analyze_component(root, source, options) {
|
|
|
718
721
|
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
|
|
719
722
|
fragment: ast === template.ast ? ast : null,
|
|
720
723
|
parent_element: null,
|
|
724
|
+
in_declaration_tag: false,
|
|
721
725
|
has_props_rune: false,
|
|
722
726
|
component_slots: new Set(),
|
|
723
727
|
expression: null,
|
|
@@ -785,6 +789,7 @@ export function analyze_component(root, source, options) {
|
|
|
785
789
|
options,
|
|
786
790
|
fragment: ast === template.ast ? ast : null,
|
|
787
791
|
parent_element: null,
|
|
792
|
+
in_declaration_tag: false,
|
|
788
793
|
has_props_rune: false,
|
|
789
794
|
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
|
|
790
795
|
reactive_statement: null,
|
|
@@ -255,6 +255,9 @@ export function CallExpression(node, context) {
|
|
|
255
255
|
if (expression.has_await) {
|
|
256
256
|
context.state.analysis.async_deriveds.add(node);
|
|
257
257
|
}
|
|
258
|
+
|
|
259
|
+
// Tell surrounding declaration tag about metadata for correct calculation of blockers etc
|
|
260
|
+
if (context.state.in_declaration_tag) context.state.expression?.merge(expression);
|
|
258
261
|
} else if (rune === '$inspect') {
|
|
259
262
|
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
|
|
260
263
|
} else {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @import { AST } from '#compiler' */
|
|
2
2
|
/** @import { Context } from '../types' */
|
|
3
3
|
import * as e from '../../../errors.js';
|
|
4
|
-
import * as b from '#compiler/builders';
|
|
5
4
|
import { validate_opening_tag } from './shared/utils.js';
|
|
5
|
+
import { mark_async_declaration } from './DeclarationTag.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {AST.ConstTag} node
|
|
@@ -44,28 +44,5 @@ export function ConstTag(node, context) {
|
|
|
44
44
|
derived_function_depth: context.state.function_depth + 1
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
const blockers = [...node.metadata.expression.dependencies]
|
|
49
|
-
.map((dep) => dep.blocker)
|
|
50
|
-
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
51
|
-
|
|
52
|
-
if (has_await || context.state.async_consts || blockers.length > 0) {
|
|
53
|
-
const run = (context.state.async_consts ??= {
|
|
54
|
-
id: context.state.analysis.root.unique('promises'),
|
|
55
|
-
declaration_count: 0
|
|
56
|
-
});
|
|
57
|
-
node.metadata.promises_id = run.id;
|
|
58
|
-
|
|
59
|
-
const bindings = context.state.scope.get_bindings(declaration);
|
|
60
|
-
|
|
61
|
-
// keep the counter in sync with the number of thunks pushed in ConstTag in transform
|
|
62
|
-
// TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
|
|
63
|
-
// via something like the approach in https://github.com/sveltejs/svelte/pull/18032
|
|
64
|
-
const length = run.declaration_count + (blockers.length > 0 ? 1 : 0);
|
|
65
|
-
run.declaration_count += blockers.length > 0 ? 2 : 1;
|
|
66
|
-
const blocker = b.member(run.id, b.literal(length), true);
|
|
67
|
-
for (const binding of bindings) {
|
|
68
|
-
binding.blocker = blocker;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
47
|
+
mark_async_declaration(context, node.metadata, [declaration]);
|
|
71
48
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** @import { AST } from '#compiler' */
|
|
2
|
+
/** @import { Context } from '../types' */
|
|
3
|
+
import * as b from '#compiler/builders';
|
|
4
|
+
import * as e from '../../../errors.js';
|
|
5
|
+
import { validate_opening_tag } from './shared/utils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {AST.DeclarationTag} node
|
|
9
|
+
* @param {Context} context
|
|
10
|
+
*/
|
|
11
|
+
export function DeclarationTag(node, context) {
|
|
12
|
+
validate_opening_tag(node, context.state, node.declaration.kind[0]);
|
|
13
|
+
if (!context.state.analysis.runes && !context.state.analysis.maybe_runes) {
|
|
14
|
+
e.declaration_tag_no_legacy_mode(node);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
context.visit(node.declaration, {
|
|
18
|
+
...context.state,
|
|
19
|
+
in_declaration_tag: true,
|
|
20
|
+
expression: node.metadata.expression
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
mark_async_declaration(context, node.metadata, node.declaration.declarations);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Context} context
|
|
28
|
+
* @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
|
|
29
|
+
* @param {import('estree').VariableDeclarator[]} declarations
|
|
30
|
+
*/
|
|
31
|
+
export function mark_async_declaration(context, metadata, declarations) {
|
|
32
|
+
const has_await = metadata.expression.has_await;
|
|
33
|
+
const blockers = [...metadata.expression.dependencies]
|
|
34
|
+
.map((dep) => dep.blocker)
|
|
35
|
+
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
36
|
+
|
|
37
|
+
if (has_await || context.state.async_consts || blockers.length > 0) {
|
|
38
|
+
const run = (context.state.async_consts ??= {
|
|
39
|
+
id: context.state.analysis.root.unique('promises'),
|
|
40
|
+
declaration_count: 0
|
|
41
|
+
});
|
|
42
|
+
metadata.promises_id = run.id;
|
|
43
|
+
|
|
44
|
+
const bindings = declarations.flatMap((declaration) =>
|
|
45
|
+
context.state.scope.get_bindings(declaration)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// keep the counter in sync with the number of thunks pushed in transform
|
|
49
|
+
// TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
|
|
50
|
+
// via something like the approach in https://github.com/sveltejs/svelte/pull/18032
|
|
51
|
+
const length = run.declaration_count + (blockers.length > 0 ? 1 : 0);
|
|
52
|
+
run.declaration_count += blockers.length > 0 ? 2 : 1;
|
|
53
|
+
const blocker = b.member(run.id, b.literal(length), true);
|
|
54
|
+
for (const binding of bindings) {
|
|
55
|
+
binding.blocker = blocker;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -162,7 +162,7 @@ export function Identifier(node, context) {
|
|
|
162
162
|
if (binding.metadata?.is_template_declaration && context.state.options.experimental.async) {
|
|
163
163
|
let snippet_name;
|
|
164
164
|
|
|
165
|
-
// Find out if this references a {@const ...} declaration of an implicit children snippet
|
|
165
|
+
// Find out if this references a {@const ...}/{let/const ...} declaration of an implicit children snippet
|
|
166
166
|
// when it is itself inside a snippet block at the same level. If so, error.
|
|
167
167
|
for (let i = context.path.length - 1; i >= 0; i--) {
|
|
168
168
|
const parent = context.path[i];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** @import { Visitors, ComponentClientTransformState, ClientTransformState } from './types' */
|
|
5
5
|
import { walk } from 'zimmerframe';
|
|
6
6
|
import * as b from '#compiler/builders';
|
|
7
|
-
import { build_getter,
|
|
7
|
+
import { build_getter, get_transform } from './utils.js';
|
|
8
8
|
import { render_stylesheet } from '../css/index.js';
|
|
9
9
|
import { dev, filename } from '../../../state.js';
|
|
10
10
|
import { AnimateDirective } from './visitors/AnimateDirective.js';
|
|
@@ -22,6 +22,7 @@ import { ClassBody } from './visitors/ClassBody.js';
|
|
|
22
22
|
import { Comment } from './visitors/Comment.js';
|
|
23
23
|
import { Component } from './visitors/Component.js';
|
|
24
24
|
import { ConstTag } from './visitors/ConstTag.js';
|
|
25
|
+
import { DeclarationTag } from './visitors/DeclarationTag.js';
|
|
25
26
|
import { DebugTag } from './visitors/DebugTag.js';
|
|
26
27
|
import { EachBlock } from './visitors/EachBlock.js';
|
|
27
28
|
import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
|
|
@@ -66,20 +67,7 @@ const visitors = {
|
|
|
66
67
|
const scope = state.scopes.get(node);
|
|
67
68
|
|
|
68
69
|
if (scope && scope !== state.scope) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (const [name, binding] of scope.declarations) {
|
|
72
|
-
if (
|
|
73
|
-
binding.kind === 'normal' ||
|
|
74
|
-
// Reads of `$state(...)` declarations are not
|
|
75
|
-
// transformed if they are never reassigned
|
|
76
|
-
(binding.kind === 'state' && !is_state_source(binding, state.analysis))
|
|
77
|
-
) {
|
|
78
|
-
delete transform[name];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
next({ ...state, transform, scope });
|
|
70
|
+
next({ ...state, transform: get_transform(scope, state), scope });
|
|
83
71
|
} else {
|
|
84
72
|
next();
|
|
85
73
|
}
|
|
@@ -99,6 +87,7 @@ const visitors = {
|
|
|
99
87
|
Comment,
|
|
100
88
|
Component,
|
|
101
89
|
ConstTag,
|
|
90
|
+
DeclarationTag,
|
|
102
91
|
DebugTag,
|
|
103
92
|
EachBlock,
|
|
104
93
|
ExportNamedDeclaration,
|
|
@@ -152,6 +141,7 @@ export function client_component(analysis, options) {
|
|
|
152
141
|
scopes: analysis.module.scopes,
|
|
153
142
|
is_instance: false,
|
|
154
143
|
hoisted: [b.import_all('$', 'svelte/internal/client'), ...analysis.instance_body.hoisted],
|
|
144
|
+
templates: new Map(),
|
|
155
145
|
node: /** @type {any} */ (null), // populated by the root node
|
|
156
146
|
legacy_reactive_imports: [],
|
|
157
147
|
legacy_reactive_statements: new Map(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import { TemplateLiteral } from 'estree' */
|
|
1
2
|
/** @import { Namespace } from '#compiler' */
|
|
2
3
|
/** @import { ComponentClientTransformState } from '../types.js' */
|
|
3
4
|
/** @import { Node } from './types.js' */
|
|
@@ -31,14 +32,29 @@ function build_locations(nodes) {
|
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* @param {ComponentClientTransformState} state
|
|
34
|
-
* @param {
|
|
35
|
+
* @param {string} name
|
|
35
36
|
* @param {number} [flags]
|
|
36
37
|
*/
|
|
37
|
-
export function transform_template(state,
|
|
38
|
+
export function transform_template(state, name, flags = 0) {
|
|
39
|
+
const namespace = state.metadata.namespace;
|
|
38
40
|
const tree = state.options.fragments === 'tree';
|
|
39
41
|
|
|
40
42
|
const expression = tree ? state.template.as_tree() : state.template.as_html();
|
|
41
43
|
|
|
44
|
+
const key =
|
|
45
|
+
tree || dev
|
|
46
|
+
? null
|
|
47
|
+
: get_template_key(
|
|
48
|
+
/** @type {TemplateLiteral} */ (expression),
|
|
49
|
+
state.metadata.namespace,
|
|
50
|
+
flags
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (key !== null) {
|
|
54
|
+
const existing = state.templates.get(key);
|
|
55
|
+
if (existing !== undefined) return existing;
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
if (tree) {
|
|
43
59
|
if (namespace === 'svg') flags |= TEMPLATE_USE_SVG;
|
|
44
60
|
if (namespace === 'mathml') flags |= TEMPLATE_USE_MATHML;
|
|
@@ -63,5 +79,26 @@ export function transform_template(state, namespace, flags = 0) {
|
|
|
63
79
|
);
|
|
64
80
|
}
|
|
65
81
|
|
|
66
|
-
|
|
82
|
+
const id = state.scope.root.unique(name);
|
|
83
|
+
state.hoisted.push(b.var(id, call));
|
|
84
|
+
|
|
85
|
+
if (key !== null) {
|
|
86
|
+
state.templates.set(key, id);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns a stable key for templates that are safe to deduplicate - plain
|
|
94
|
+
* `$.from_html`/`from_svg`/`from_mathml` factories with literal arguments - or `null`
|
|
95
|
+
* for anything else. Dev-mode templates are wrapped in `$.add_locations(...)`, which
|
|
96
|
+
* embeds per-call-site locations, so they never produce a key and are never shared.
|
|
97
|
+
* @param {TemplateLiteral} template
|
|
98
|
+
* @param {Namespace} namespace
|
|
99
|
+
* @param {number} flags
|
|
100
|
+
* @returns {string | null}
|
|
101
|
+
*/
|
|
102
|
+
function get_template_key(template, namespace, flags) {
|
|
103
|
+
return `${namespace} ${flags} ${template.quasis[0].value.raw}`;
|
|
67
104
|
}
|
|
@@ -179,3 +179,24 @@ export function create_derived(state, expression, async = false) {
|
|
|
179
179
|
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', thunk);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {Scope} scope
|
|
185
|
+
* @param {ClientTransformState} state
|
|
186
|
+
*/
|
|
187
|
+
export function get_transform(scope, state) {
|
|
188
|
+
const transform = { ...state.transform };
|
|
189
|
+
|
|
190
|
+
for (const [name, binding] of scope.declarations) {
|
|
191
|
+
if (
|
|
192
|
+
binding.kind === 'normal' ||
|
|
193
|
+
// Reads of `$state(...)` declarations are not
|
|
194
|
+
// transformed if they are never reassigned
|
|
195
|
+
(binding.kind === 'state' && !is_state_source(binding, state.analysis))
|
|
196
|
+
) {
|
|
197
|
+
delete transform[name];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return transform;
|
|
202
|
+
}
|
|
@@ -7,6 +7,7 @@ import * as b from '#compiler/builders';
|
|
|
7
7
|
import { create_derived } from '../utils.js';
|
|
8
8
|
import { get_value } from './shared/declarations.js';
|
|
9
9
|
import { build_expression } from './shared/utils.js';
|
|
10
|
+
import { add_async_declaration } from './DeclarationTag.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @param {AST.ConstTag} node
|
|
@@ -26,7 +27,7 @@ export function ConstTag(node, context) {
|
|
|
26
27
|
|
|
27
28
|
context.state.transform[declaration.id.name] = { read: get_value };
|
|
28
29
|
|
|
29
|
-
add_const_declaration(context
|
|
30
|
+
add_const_declaration(context, declaration.id, expression, node.metadata);
|
|
30
31
|
} else {
|
|
31
32
|
const identifiers = extract_identifiers(declaration.id);
|
|
32
33
|
const tmp = b.id(context.state.scope.generate('computed_const'));
|
|
@@ -63,7 +64,7 @@ export function ConstTag(node, context) {
|
|
|
63
64
|
expression = b.call('$.tag', expression, b.literal('[@const]'));
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
add_const_declaration(context
|
|
67
|
+
add_const_declaration(context, tmp, expression, node.metadata);
|
|
67
68
|
|
|
68
69
|
for (const node of identifiers) {
|
|
69
70
|
context.state.transform[node.name] = {
|
|
@@ -74,38 +75,26 @@ export function ConstTag(node, context) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* @param {ComponentContext
|
|
78
|
+
* @param {ComponentContext} context
|
|
78
79
|
* @param {Identifier} id
|
|
79
80
|
* @param {Expression} expression
|
|
80
81
|
* @param {AST.ConstTag['metadata']} metadata
|
|
81
82
|
*/
|
|
82
|
-
function add_const_declaration(
|
|
83
|
+
function add_const_declaration(context, id, expression, metadata) {
|
|
83
84
|
// we need to eagerly evaluate the expression in order to hit any
|
|
84
85
|
// 'Cannot access x before initialization' errors
|
|
85
86
|
const after = dev ? [b.stmt(b.call('$.get', id))] : [];
|
|
86
87
|
|
|
87
|
-
const blockers = [...metadata.expression.dependencies]
|
|
88
|
-
.map((dep) => dep.blocker)
|
|
89
|
-
.filter((b) => b !== null && b.object !== state.async_consts?.id);
|
|
90
|
-
|
|
91
88
|
if (metadata.promises_id) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (blockers.length === 1) {
|
|
100
|
-
run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
|
|
101
|
-
} else if (blockers.length > 0) {
|
|
102
|
-
run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// keep the number of thunks pushed in sync with ConstTag in analysis phase
|
|
106
|
-
const assignment = b.assignment('=', id, expression);
|
|
107
|
-
run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
|
|
89
|
+
add_async_declaration(
|
|
90
|
+
context,
|
|
91
|
+
metadata,
|
|
92
|
+
[id],
|
|
93
|
+
[b.stmt(b.assignment('=', id, expression))],
|
|
94
|
+
'let'
|
|
95
|
+
);
|
|
108
96
|
} else {
|
|
97
|
+
const { state } = context;
|
|
109
98
|
state.consts.push(b.const(id, expression));
|
|
110
99
|
state.consts.push(...after);
|
|
111
100
|
}
|