svelte 5.55.10 → 5.56.1
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 +31 -1
- package/src/compiler/phases/1-parse/index.js +4 -1
- package/src/compiler/phases/1-parse/state/tag.js +104 -3
- package/src/compiler/phases/1-parse/utils/bracket.js +14 -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 +71 -0
- package/src/compiler/phases/2-analyze/visitors/Identifier.js +1 -1
- package/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +15 -10
- 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 +89 -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/server/visitors/shared/utils.js +1 -1
- 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/blocks/each.js +3 -1
- package/src/internal/client/dom/elements/attributes.js +9 -0
- package/src/internal/client/dom/elements/bindings/input.js +0 -1
- package/src/internal/client/dom/operations.js +12 -2
- package/src/internal/client/reactivity/async.js +23 -10
- package/src/internal/client/reactivity/batch.js +0 -21
- package/src/internal/client/reactivity/effects.js +4 -2
- 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/reactivity/url-search-params.js +3 -2
- 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.1",
|
|
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,4 +1,4 @@
|
|
|
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';
|
|
@@ -98,6 +98,36 @@ export function parse_expression_at(parser, source, index) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @param {Parser} parser
|
|
103
|
+
* @param {string} source
|
|
104
|
+
* @param {number} index
|
|
105
|
+
* @returns {Statement}
|
|
106
|
+
*/
|
|
107
|
+
export function parse_statement_at(parser, source, index) {
|
|
108
|
+
// cast to `any`: acorn's Parser constructor and parseStatement/nextToken aren't in its public types
|
|
109
|
+
const acorn = /** @type {any} */ (parser.ts ? TSParser : JSParser);
|
|
110
|
+
const { onComment, add_comments } = get_comment_handlers(source, parser.root.comments, index);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// This is like parseExpressionAt but for statements
|
|
114
|
+
const p = new acorn(
|
|
115
|
+
{ onComment, sourceType: 'module', ecmaVersion: 16, locations: true },
|
|
116
|
+
source,
|
|
117
|
+
index
|
|
118
|
+
);
|
|
119
|
+
p.nextToken();
|
|
120
|
+
const statement = /** @type {Statement} */ (p.parseStatement(null, true, Object.create(null)));
|
|
121
|
+
add_comments(/** @type {acorn.Node} */ (statement));
|
|
122
|
+
return statement;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// A statement that runs to the end of the source (e.g. an unterminated declaration tag)
|
|
125
|
+
// is an EOF, not a stray token; preserve the friendlier `unexpected_eof` diagnostic.
|
|
126
|
+
if (/** @type {any} */ (err).pos === source.length) e.unexpected_eof(source.length);
|
|
127
|
+
handle_parse_error(err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
const regex_position_indicator = / \(\d+:\d+\)$/;
|
|
102
132
|
|
|
103
133
|
/**
|
|
@@ -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
|
+
const regex_unsupported_declaration = /(?:var|interface|enum)\b/y;
|
|
16
|
+
// `type` is a contextual keyword; this is just a shape hint, confirmed by parsing.
|
|
17
|
+
const regex_maybe_type_declaration = /type\b/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,89 @@ 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 (
|
|
81
|
+
!parser.match_regex(regex_supported_declaration) &&
|
|
82
|
+
// `type` is special, since it is not a reserved keyword and can be used
|
|
83
|
+
// as part of a valid expression. We gotta parse first and then see what it is.
|
|
84
|
+
!parser.match_regex(regex_maybe_type_declaration)
|
|
85
|
+
) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const initial_comment_count = parser.root.comments.length;
|
|
90
|
+
|
|
91
|
+
/** @type {import('estree').Statement | import('estree').VariableDeclaration} */
|
|
92
|
+
let declaration;
|
|
93
|
+
try {
|
|
94
|
+
declaration = parse_statement_at(parser, parser.template, start);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (!parser.loose) throw error;
|
|
97
|
+
|
|
98
|
+
const end = find_matching_bracket(parser.template, start, '{');
|
|
99
|
+
if (end === undefined) throw error;
|
|
100
|
+
|
|
101
|
+
parser.index = end;
|
|
102
|
+
const kind = parser.template.startsWith('const', start) ? 'const' : 'let';
|
|
103
|
+
|
|
104
|
+
declaration = {
|
|
105
|
+
type: 'VariableDeclaration',
|
|
106
|
+
kind,
|
|
107
|
+
declarations: [
|
|
108
|
+
{
|
|
109
|
+
type: 'VariableDeclarator',
|
|
110
|
+
id: {
|
|
111
|
+
type: 'Identifier',
|
|
112
|
+
name: '',
|
|
113
|
+
start: parser.index,
|
|
114
|
+
end: parser.index
|
|
115
|
+
},
|
|
116
|
+
init: null,
|
|
117
|
+
start: parser.index,
|
|
118
|
+
end: parser.index
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
start,
|
|
122
|
+
end
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (declaration.type !== 'VariableDeclaration') {
|
|
127
|
+
if (declaration.type === 'ExpressionStatement') {
|
|
128
|
+
parser.root.comments.length = initial_comment_count; // Else they show up duplicated
|
|
129
|
+
return null;
|
|
130
|
+
} else {
|
|
131
|
+
// This is a TSTypeAliasDeclaration
|
|
132
|
+
e.declaration_tag_invalid_type({
|
|
133
|
+
start: declaration.start ?? start,
|
|
134
|
+
end: declaration.end ?? parser.index
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// TODO support using
|
|
140
|
+
if (declaration.kind !== 'let' && declaration.kind !== 'const') {
|
|
141
|
+
e.declaration_tag_invalid_type(declaration);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
parser.index = /** @type {number} */ (declaration.end);
|
|
145
|
+
parser.allow_whitespace();
|
|
146
|
+
parser.eat('}', true);
|
|
147
|
+
|
|
148
|
+
return declaration;
|
|
149
|
+
}
|
|
150
|
+
|
|
50
151
|
/** @param {Parser} parser */
|
|
51
152
|
function open(parser) {
|
|
52
153
|
let start = parser.index - 2;
|
|
@@ -39,7 +39,9 @@ function find_string_end(string, search_start_index, string_start_char) {
|
|
|
39
39
|
* @returns {number} The index of the end of this regex expression, or `Infinity` if not found.
|
|
40
40
|
*/
|
|
41
41
|
function find_regex_end(string, search_start_index) {
|
|
42
|
-
|
|
42
|
+
const slash = find_unescaped_char(string, search_start_index, '/');
|
|
43
|
+
const eol = find_unescaped_char(string, search_start_index, '\n');
|
|
44
|
+
return slash < eol ? slash : Infinity;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -105,7 +107,11 @@ export function find_matching_bracket(template, index, open) {
|
|
|
105
107
|
continue;
|
|
106
108
|
case '/': {
|
|
107
109
|
const next_char = template[i + 1];
|
|
108
|
-
if (!next_char)
|
|
110
|
+
if (!next_char) {
|
|
111
|
+
// `/` is the last character; advance past it so we don't loop forever
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
109
115
|
if (next_char === '/') {
|
|
110
116
|
i = infinity_if_negative(template.indexOf('\n', i + 1)) + '\n'.length;
|
|
111
117
|
continue;
|
|
@@ -114,7 +120,12 @@ export function find_matching_bracket(template, index, open) {
|
|
|
114
120
|
i = infinity_if_negative(template.indexOf('*/', i + 1)) + '*/'.length;
|
|
115
121
|
continue;
|
|
116
122
|
}
|
|
117
|
-
|
|
123
|
+
const end = find_regex_end(template, i + 1) + '/'.length;
|
|
124
|
+
if (end === Infinity) {
|
|
125
|
+
i++;
|
|
126
|
+
} else {
|
|
127
|
+
i = end;
|
|
128
|
+
}
|
|
118
129
|
continue;
|
|
119
130
|
}
|
|
120
131
|
default: {
|
|
@@ -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,71 @@
|
|
|
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 { extract_identifiers } from '../../../utils/ast.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {AST.DeclarationTag} node
|
|
9
|
+
* @param {Context} context
|
|
10
|
+
*/
|
|
11
|
+
export function DeclarationTag(node, context) {
|
|
12
|
+
if (!context.state.analysis.runes && !context.state.analysis.maybe_runes) {
|
|
13
|
+
e.declaration_tag_no_legacy_mode(node);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
|
|
17
|
+
if (is_top_level) {
|
|
18
|
+
const duplicate = node.declaration.declarations
|
|
19
|
+
.flatMap((declaration) => extract_identifiers(declaration.id))
|
|
20
|
+
.find((id) => context.state.analysis.instance.scope.declarations.has(id.name));
|
|
21
|
+
if (duplicate) {
|
|
22
|
+
e.declaration_duplicate(duplicate, duplicate.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
context.visit(node.declaration, {
|
|
27
|
+
...context.state,
|
|
28
|
+
in_declaration_tag: true,
|
|
29
|
+
// the declaration lives in the fragment scope, which is one level deeper than the
|
|
30
|
+
// `function_depth` we're tracking here (`set_scope` doesn't update `function_depth`).
|
|
31
|
+
// align them so that `state_referenced_locally` warnings are calculated correctly
|
|
32
|
+
function_depth: context.state.scope.function_depth,
|
|
33
|
+
expression: node.metadata.expression
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
mark_async_declaration(context, node.metadata, node.declaration.declarations);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {Context} context
|
|
41
|
+
* @param {AST.ConstTag['metadata'] | AST.DeclarationTag['metadata']} metadata
|
|
42
|
+
* @param {import('estree').VariableDeclarator[]} declarations
|
|
43
|
+
*/
|
|
44
|
+
export function mark_async_declaration(context, metadata, declarations) {
|
|
45
|
+
const has_await = metadata.expression.has_await;
|
|
46
|
+
const blockers = [...metadata.expression.dependencies]
|
|
47
|
+
.map((dep) => dep.blocker)
|
|
48
|
+
.filter((b) => b !== null && b.object !== context.state.async_consts?.id);
|
|
49
|
+
|
|
50
|
+
if (has_await || context.state.async_consts || blockers.length > 0) {
|
|
51
|
+
const run = (context.state.async_consts ??= {
|
|
52
|
+
id: context.state.analysis.root.unique('promises'),
|
|
53
|
+
declaration_count: 0
|
|
54
|
+
});
|
|
55
|
+
metadata.promises_id = run.id;
|
|
56
|
+
|
|
57
|
+
const bindings = declarations.flatMap((declaration) =>
|
|
58
|
+
context.state.scope.get_bindings(declaration)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// keep the counter in sync with the number of thunks pushed 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
|
+
}
|
|
71
|
+
}
|
|
@@ -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];
|
|
@@ -25,19 +25,24 @@ export function SnippetBlock(node, context) {
|
|
|
25
25
|
|
|
26
26
|
context.next({ ...context.state, parent_element: null });
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
context.path.length === 1 &&
|
|
30
|
-
context.path[0].type === 'Fragment' &&
|
|
31
|
-
can_hoist_snippet(context.state.scope, context.state.scopes);
|
|
28
|
+
const is_top_level = context.path.length === 1 && context.path[0].type === 'Fragment';
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
if (is_top_level) {
|
|
31
|
+
const name = node.expression.name;
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
if (context.state.analysis.instance.scope.declarations.has(name)) {
|
|
34
|
+
e.declaration_duplicate(node.expression, name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
node.metadata.can_hoist =
|
|
38
|
+
is_top_level && can_hoist_snippet(context.state.scope, context.state.scopes);
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
if (node.metadata.can_hoist) {
|
|
41
|
+
const name = node.expression.name;
|
|
42
|
+
const binding = /** @type {Binding} */ (context.state.scope.get(name));
|
|
43
|
+
context.state.analysis.module.scope.declarations.set(name, binding);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
const { path } = context;
|
|
43
48
|
const parent = path.at(-2);
|
|
@@ -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
|
+
}
|