ripple 0.2.177 → 0.2.179
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/package.json +4 -3
- package/src/compiler/index.d.ts +35 -28
- package/src/compiler/index.js +12 -7
- package/src/compiler/phases/1-parse/index.js +4 -1
- package/src/compiler/phases/1-parse/style.js +78 -2
- package/src/compiler/phases/2-analyze/index.js +19 -9
- package/src/compiler/phases/3-transform/client/index.js +48 -3
- package/src/compiler/phases/3-transform/segments.js +70 -8
- package/src/compiler/phases/3-transform/server/index.js +4 -2
- package/src/compiler/phases/3-transform/stylesheet.js +62 -4
- package/src/compiler/types/index.d.ts +175 -3
- package/src/runtime/internal/client/composite.js +3 -3
- package/src/utils/builders.js +13 -2
- package/tests/client/composite/composite.render.test.ripple +21 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.179",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -80,9 +80,10 @@
|
|
|
80
80
|
"@types/estree-jsx": "^1.0.5",
|
|
81
81
|
"@types/node": "^24.3.0",
|
|
82
82
|
"typescript": "^5.9.2",
|
|
83
|
-
"@volar/language-core": "~2.4.23"
|
|
83
|
+
"@volar/language-core": "~2.4.23",
|
|
84
|
+
"vscode-languageserver-types": "^3.17.5"
|
|
84
85
|
},
|
|
85
86
|
"peerDependencies": {
|
|
86
|
-
"ripple": "0.2.
|
|
87
|
+
"ripple": "0.2.179"
|
|
87
88
|
}
|
|
88
89
|
}
|
package/src/compiler/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
CodeInformation as VolarCodeInformation,
|
|
4
4
|
Mapping as VolarMapping,
|
|
5
5
|
} from '@volar/language-core';
|
|
6
|
+
import type { DocumentHighlightKind } from 'vscode-languageserver-types';
|
|
6
7
|
|
|
7
8
|
// ============================================================================
|
|
8
9
|
// Compiler API Exports
|
|
@@ -22,7 +23,28 @@ export interface CompileResult {
|
|
|
22
23
|
css: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export interface
|
|
26
|
+
export interface PluginActionOverrides {
|
|
27
|
+
/** Whether to enable word document highlighting for this mapping */
|
|
28
|
+
wordHighlight?: {
|
|
29
|
+
kind: DocumentHighlightKind;
|
|
30
|
+
};
|
|
31
|
+
/** TypeScript diagnostic codes to suppress for this mapping */
|
|
32
|
+
suppressedDiagnostics?: number[];
|
|
33
|
+
/** Custom hover documentation for this mapping, false to disable */
|
|
34
|
+
hover?:
|
|
35
|
+
| {
|
|
36
|
+
contents: string;
|
|
37
|
+
}
|
|
38
|
+
| false;
|
|
39
|
+
/** Custom definition info for this mapping, false to disable */
|
|
40
|
+
definition?:
|
|
41
|
+
| {
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
44
|
+
| false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CustomMappingData extends PluginActionOverrides {
|
|
26
48
|
generatedLengths: number[];
|
|
27
49
|
}
|
|
28
50
|
|
|
@@ -34,9 +56,6 @@ export interface CodeMapping extends VolarMapping<MappingData> {
|
|
|
34
56
|
data: MappingData;
|
|
35
57
|
}
|
|
36
58
|
|
|
37
|
-
/**
|
|
38
|
-
* Result of Volar mappings compilation
|
|
39
|
-
*/
|
|
40
59
|
export interface VolarMappingsResult {
|
|
41
60
|
code: string;
|
|
42
61
|
mappings: CodeMapping[];
|
|
@@ -47,42 +66,30 @@ export interface VolarMappingsResult {
|
|
|
47
66
|
/**
|
|
48
67
|
* Compilation options
|
|
49
68
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
|
|
70
|
+
interface SharedCompileOptions {
|
|
71
|
+
minify_css?: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface CompileOptions extends SharedCompileOptions {
|
|
52
74
|
mode?: 'client' | 'server';
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
export interface ParseOptions {
|
|
56
|
-
/** Enable loose mode */
|
|
57
78
|
loose?: boolean;
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
export interface AnalyzeOptions extends ParseOptions, Pick<CompileOptions, 'mode'> {
|
|
82
|
+
to_ts?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface VolarCompileOptions extends ParseOptions, SharedCompileOptions {}
|
|
86
|
+
|
|
66
87
|
export function parse(source: string, options?: ParseOptions): Program;
|
|
67
88
|
|
|
68
|
-
/**
|
|
69
|
-
* Compile Ripple source code to JS/CSS output
|
|
70
|
-
* @param source - The Ripple source code to compile
|
|
71
|
-
* @param filename - The filename for source map generation
|
|
72
|
-
* @param options - Compilation options (mode: 'client' or 'server')
|
|
73
|
-
* @returns The compilation result with AST, JS, and CSS
|
|
74
|
-
*/
|
|
75
89
|
export function compile(source: string, filename: string, options?: CompileOptions): CompileResult;
|
|
76
90
|
|
|
77
|
-
/**
|
|
78
|
-
* Compile Ripple source to Volar mappings for editor integration
|
|
79
|
-
* @param source - The Ripple source code
|
|
80
|
-
* @param filename - The filename for source map generation
|
|
81
|
-
* @param options - Parse options
|
|
82
|
-
* @returns Volar mappings object for editor integration
|
|
83
|
-
*/
|
|
84
91
|
export function compile_to_volar_mappings(
|
|
85
92
|
source: string,
|
|
86
93
|
filename: string,
|
|
87
|
-
options?:
|
|
94
|
+
options?: VolarCompileOptions,
|
|
88
95
|
): VolarMappingsResult;
|
package/src/compiler/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** @import { Program } from 'estree' */
|
|
2
|
-
/** @import { ParseOptions } from 'ripple/compiler' */
|
|
3
2
|
|
|
4
3
|
import { parse as parse_module } from './phases/1-parse/index.js';
|
|
5
4
|
import { analyze } from './phases/2-analyze/index.js';
|
|
@@ -20,7 +19,7 @@ export function parse(source) {
|
|
|
20
19
|
* Compile Ripple source code to JS/CSS output
|
|
21
20
|
* @param {string} source
|
|
22
21
|
* @param {string} filename
|
|
23
|
-
* @param {
|
|
22
|
+
* @param {CompileOptions} [options]
|
|
24
23
|
* @returns {object}
|
|
25
24
|
*/
|
|
26
25
|
export function compile(source, filename, options = {}) {
|
|
@@ -28,26 +27,32 @@ export function compile(source, filename, options = {}) {
|
|
|
28
27
|
const analysis = analyze(ast, filename, options);
|
|
29
28
|
const result =
|
|
30
29
|
options.mode === 'server'
|
|
31
|
-
? transform_server(filename, source, analysis)
|
|
32
|
-
: transform_client(filename, source, analysis, false);
|
|
30
|
+
? transform_server(filename, source, analysis, options?.minify_css ?? false)
|
|
31
|
+
: transform_client(filename, source, analysis, false, options?.minify_css ?? false);
|
|
33
32
|
|
|
34
33
|
return result;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/** @import { PostProcessingChanges, LineOffsets } from './phases/3-transform/client/index.js' */
|
|
38
|
-
/** @import { VolarMappingsResult } from 'ripple/compiler' */
|
|
37
|
+
/** @import { VolarMappingsResult, VolarCompileOptions, CompileOptions } from 'ripple/compiler' */
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* Compile Ripple component to Volar virtual code with TypeScript mappings
|
|
42
41
|
* @param {string} source
|
|
43
42
|
* @param {string} filename
|
|
44
|
-
* @param {
|
|
43
|
+
* @param {VolarCompileOptions} [options] - Compiler options
|
|
45
44
|
* @returns {VolarMappingsResult} Volar mappings object
|
|
46
45
|
*/
|
|
47
46
|
export function compile_to_volar_mappings(source, filename, options) {
|
|
48
47
|
const ast = parse_module(source, options);
|
|
49
48
|
const analysis = analyze(ast, filename, { to_ts: true, loose: !!options?.loose });
|
|
50
|
-
const transformed = transform_client(
|
|
49
|
+
const transformed = transform_client(
|
|
50
|
+
filename,
|
|
51
|
+
source,
|
|
52
|
+
analysis,
|
|
53
|
+
true,
|
|
54
|
+
options?.minify_css ?? false,
|
|
55
|
+
);
|
|
51
56
|
|
|
52
57
|
// Create volar mappings with esrap source map for accurate positioning
|
|
53
58
|
return convert_source_map_to_mappings(
|
|
@@ -2115,9 +2115,12 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2115
2115
|
}
|
|
2116
2116
|
// Handle empty Element nodes the same way as empty BlockStatements
|
|
2117
2117
|
if (node.type === 'Element' && (!node.children || node.children.length === 0)) {
|
|
2118
|
-
|
|
2118
|
+
// Collect all comments that fall within this empty element
|
|
2119
|
+
while (comments[0] && comments[0].start < node.end && comments[0].end < node.end) {
|
|
2119
2120
|
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
2120
2121
|
(node.innerComments ||= []).push(comment);
|
|
2122
|
+
}
|
|
2123
|
+
if (node.innerComments && node.innerComments.length > 0) {
|
|
2121
2124
|
return;
|
|
2122
2125
|
}
|
|
2123
2126
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import * as AST from 'estree' */
|
|
2
|
+
|
|
1
3
|
import { hash } from '../../utils.js';
|
|
2
4
|
|
|
3
5
|
const REGEX_MATCHER = /^[~^$*|]?=/;
|
|
@@ -17,6 +19,10 @@ const regex_whitespace = /\s/;
|
|
|
17
19
|
class Parser {
|
|
18
20
|
index = 0;
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} template
|
|
24
|
+
* @param {boolean} loose
|
|
25
|
+
*/
|
|
20
26
|
constructor(template, loose) {
|
|
21
27
|
if (typeof template !== 'string') {
|
|
22
28
|
throw new TypeError('Template must be a string');
|
|
@@ -27,6 +33,7 @@ class Parser {
|
|
|
27
33
|
this.template = template.trimEnd();
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
/** @param {string} str */
|
|
30
37
|
match(str) {
|
|
31
38
|
const length = str.length;
|
|
32
39
|
if (length === 1) {
|
|
@@ -37,6 +44,11 @@ class Parser {
|
|
|
37
44
|
return this.template.slice(this.index, this.index + length) === str;
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} str
|
|
49
|
+
* @param {boolean} required
|
|
50
|
+
* @param {boolean} required_in_loose
|
|
51
|
+
*/
|
|
40
52
|
eat(str, required = false, required_in_loose = true) {
|
|
41
53
|
if (this.match(str)) {
|
|
42
54
|
this.index += str.length;
|
|
@@ -50,6 +62,10 @@ class Parser {
|
|
|
50
62
|
return false;
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Match a regex at the current index
|
|
67
|
+
* @param {RegExp} pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
|
|
68
|
+
*/
|
|
53
69
|
match_regex(pattern) {
|
|
54
70
|
const match = pattern.exec(this.template.slice(this.index));
|
|
55
71
|
if (!match || match.index !== 0) return null;
|
|
@@ -57,6 +73,10 @@ class Parser {
|
|
|
57
73
|
return match[0];
|
|
58
74
|
}
|
|
59
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Search for a regex starting at the current index and return the result if it matches
|
|
78
|
+
* @param {RegExp} pattern Should have a ^ anchor at the start so the regex doesn't search past the beginning, resulting in worse performance
|
|
79
|
+
*/
|
|
60
80
|
read(pattern) {
|
|
61
81
|
const result = this.match_regex(pattern);
|
|
62
82
|
if (result) this.index += result.length;
|
|
@@ -69,6 +89,7 @@ class Parser {
|
|
|
69
89
|
}
|
|
70
90
|
}
|
|
71
91
|
|
|
92
|
+
/** @param {RegExp} pattern */
|
|
72
93
|
read_until(pattern) {
|
|
73
94
|
if (this.index >= this.template.length) {
|
|
74
95
|
if (this.loose) return '';
|
|
@@ -88,6 +109,11 @@ class Parser {
|
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} content
|
|
114
|
+
* @param {{ loose?: boolean }} options
|
|
115
|
+
* @returns {Partial<AST.CSS.StyleSheet>}
|
|
116
|
+
*/
|
|
91
117
|
export function parse_style(content, options) {
|
|
92
118
|
const parser = new Parser(content, options.loose || false);
|
|
93
119
|
|
|
@@ -95,10 +121,11 @@ export function parse_style(content, options) {
|
|
|
95
121
|
source: content,
|
|
96
122
|
hash: `ripple-${hash(content)}`,
|
|
97
123
|
type: 'StyleSheet',
|
|
98
|
-
|
|
124
|
+
children: read_body(parser),
|
|
99
125
|
};
|
|
100
126
|
}
|
|
101
127
|
|
|
128
|
+
/** @param {Parser} parser */
|
|
102
129
|
function allow_comment_or_whitespace(parser) {
|
|
103
130
|
parser.allow_whitespace();
|
|
104
131
|
while (parser.match('/*') || parser.match('<!--')) {
|
|
@@ -116,7 +143,12 @@ function allow_comment_or_whitespace(parser) {
|
|
|
116
143
|
}
|
|
117
144
|
}
|
|
118
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @param {Parser} parser
|
|
148
|
+
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule>}
|
|
149
|
+
*/
|
|
119
150
|
function read_body(parser) {
|
|
151
|
+
/** @type {Array<AST.CSS.Rule | AST.CSS.Atrule>} */
|
|
120
152
|
const children = [];
|
|
121
153
|
|
|
122
154
|
while (parser.index < parser.template.length) {
|
|
@@ -132,6 +164,10 @@ function read_body(parser) {
|
|
|
132
164
|
return children;
|
|
133
165
|
}
|
|
134
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @param {Parser} parser
|
|
169
|
+
* @returns {AST.CSS.Atrule}
|
|
170
|
+
*/
|
|
135
171
|
function read_at_rule(parser) {
|
|
136
172
|
const start = parser.index;
|
|
137
173
|
parser.eat('@', true);
|
|
@@ -161,6 +197,10 @@ function read_at_rule(parser) {
|
|
|
161
197
|
};
|
|
162
198
|
}
|
|
163
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @param {Parser} parser
|
|
202
|
+
* @returns {AST.CSS.Rule}
|
|
203
|
+
*/
|
|
164
204
|
function read_rule(parser) {
|
|
165
205
|
const start = parser.index;
|
|
166
206
|
|
|
@@ -178,6 +218,10 @@ function read_rule(parser) {
|
|
|
178
218
|
};
|
|
179
219
|
}
|
|
180
220
|
|
|
221
|
+
/**
|
|
222
|
+
* @param {Parser} parser
|
|
223
|
+
* @returns {AST.CSS.Block}
|
|
224
|
+
*/
|
|
181
225
|
function read_block(parser) {
|
|
182
226
|
const start = parser.index;
|
|
183
227
|
|
|
@@ -206,6 +250,12 @@ function read_block(parser) {
|
|
|
206
250
|
};
|
|
207
251
|
}
|
|
208
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Reads a declaration, rule or at-rule
|
|
255
|
+
*
|
|
256
|
+
* @param {Parser} parser
|
|
257
|
+
* @returns {AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule}
|
|
258
|
+
*/
|
|
209
259
|
function read_block_item(parser) {
|
|
210
260
|
if (parser.match('@')) {
|
|
211
261
|
return read_at_rule(parser);
|
|
@@ -221,6 +271,10 @@ function read_block_item(parser) {
|
|
|
221
271
|
return char === '{' ? read_rule(parser) : read_declaration(parser);
|
|
222
272
|
}
|
|
223
273
|
|
|
274
|
+
/**
|
|
275
|
+
* @param {Parser} parser
|
|
276
|
+
* @returns {AST.CSS.Declaration}
|
|
277
|
+
*/
|
|
224
278
|
function read_declaration(parser) {
|
|
225
279
|
const start = parser.index;
|
|
226
280
|
|
|
@@ -251,6 +305,10 @@ function read_declaration(parser) {
|
|
|
251
305
|
};
|
|
252
306
|
}
|
|
253
307
|
|
|
308
|
+
/**
|
|
309
|
+
* @param {Parser} parser
|
|
310
|
+
* @returns {string}
|
|
311
|
+
*/
|
|
254
312
|
function read_value(parser) {
|
|
255
313
|
let value = '';
|
|
256
314
|
let escaped = false;
|
|
@@ -287,6 +345,11 @@ function read_value(parser) {
|
|
|
287
345
|
throw new Error('Unexpected end of input');
|
|
288
346
|
}
|
|
289
347
|
|
|
348
|
+
/**
|
|
349
|
+
* @param {Parser} parser
|
|
350
|
+
* @param {boolean} [inside_pseudo_class]
|
|
351
|
+
* @returns {AST.CSS.SelectorList}
|
|
352
|
+
*/
|
|
290
353
|
function read_selector_list(parser, inside_pseudo_class = false) {
|
|
291
354
|
/** @type {AST.CSS.ComplexSelector[]} */
|
|
292
355
|
const children = [];
|
|
@@ -318,6 +381,10 @@ function read_selector_list(parser, inside_pseudo_class = false) {
|
|
|
318
381
|
throw new Error('Unexpected end of input');
|
|
319
382
|
}
|
|
320
383
|
|
|
384
|
+
/**
|
|
385
|
+
* @param {Parser} parser
|
|
386
|
+
* @returns {AST.CSS.Combinator | null}
|
|
387
|
+
*/
|
|
321
388
|
function read_combinator(parser) {
|
|
322
389
|
const start = parser.index;
|
|
323
390
|
parser.allow_whitespace();
|
|
@@ -349,6 +416,11 @@ function read_combinator(parser) {
|
|
|
349
416
|
return null;
|
|
350
417
|
}
|
|
351
418
|
|
|
419
|
+
/**
|
|
420
|
+
* @param {Parser} parser
|
|
421
|
+
* @param {boolean} [inside_pseudo_class]
|
|
422
|
+
* @returns {AST.CSS.ComplexSelector}
|
|
423
|
+
*/
|
|
352
424
|
function read_selector(parser, inside_pseudo_class = false) {
|
|
353
425
|
const list_start = parser.index;
|
|
354
426
|
|
|
@@ -547,7 +619,7 @@ function read_selector(parser, inside_pseudo_class = false) {
|
|
|
547
619
|
parser.allow_whitespace();
|
|
548
620
|
|
|
549
621
|
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
|
|
550
|
-
|
|
622
|
+
throw new Error(`Invalid selector at parser.index: ${parser.index}`);
|
|
551
623
|
}
|
|
552
624
|
}
|
|
553
625
|
}
|
|
@@ -588,6 +660,10 @@ function read_attribute_value(parser) {
|
|
|
588
660
|
throw new Error('Unexpected end of input');
|
|
589
661
|
}
|
|
590
662
|
|
|
663
|
+
/**
|
|
664
|
+
* https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
|
|
665
|
+
* @param {Parser} parser
|
|
666
|
+
*/
|
|
591
667
|
function read_identifier(parser) {
|
|
592
668
|
const start = parser.index;
|
|
593
669
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** @import {AnalyzeOptions} from 'ripple/compiler' */
|
|
2
|
+
/** @import * as AST from 'estree' */
|
|
3
|
+
|
|
1
4
|
import * as b from '../../../utils/builders.js';
|
|
2
5
|
import { walk } from 'zimmerframe';
|
|
3
6
|
import { create_scopes, ScopeRoot } from '../../scope.js';
|
|
@@ -564,14 +567,15 @@ const visitors = {
|
|
|
564
567
|
* @returns
|
|
565
568
|
*/
|
|
566
569
|
TryStatement(node, context) {
|
|
570
|
+
const { state } = context;
|
|
567
571
|
if (!is_inside_component(context)) {
|
|
568
572
|
return context.next();
|
|
569
573
|
}
|
|
570
574
|
|
|
571
575
|
if (node.pending) {
|
|
572
576
|
// Try/pending blocks indicate async operations
|
|
573
|
-
if (
|
|
574
|
-
|
|
577
|
+
if (state.metadata?.await === false) {
|
|
578
|
+
state.metadata.await = true;
|
|
575
579
|
}
|
|
576
580
|
|
|
577
581
|
node.metadata = {
|
|
@@ -579,12 +583,12 @@ const visitors = {
|
|
|
579
583
|
has_template: false,
|
|
580
584
|
};
|
|
581
585
|
|
|
582
|
-
context.visit(node.block,
|
|
586
|
+
context.visit(node.block, state);
|
|
583
587
|
|
|
584
|
-
if (!node.metadata.has_template) {
|
|
588
|
+
if (!node.metadata.has_template && !state.loose) {
|
|
585
589
|
error(
|
|
586
590
|
'Component try statements must contain a template in their main body. Move the try statement into an effect if it does not render anything.',
|
|
587
|
-
|
|
591
|
+
state.analysis.module.filename,
|
|
588
592
|
node,
|
|
589
593
|
);
|
|
590
594
|
}
|
|
@@ -594,19 +598,19 @@ const visitors = {
|
|
|
594
598
|
has_template: false,
|
|
595
599
|
};
|
|
596
600
|
|
|
597
|
-
context.visit(node.pending,
|
|
601
|
+
context.visit(node.pending, state);
|
|
598
602
|
|
|
599
|
-
if (!node.metadata.has_template) {
|
|
603
|
+
if (!node.metadata.has_template && !state.loose) {
|
|
600
604
|
error(
|
|
601
605
|
'Component try statements must contain a template in their "pending" body. Rendering a pending fallback is required to have a template.',
|
|
602
|
-
|
|
606
|
+
state.analysis.module.filename,
|
|
603
607
|
node,
|
|
604
608
|
);
|
|
605
609
|
}
|
|
606
610
|
}
|
|
607
611
|
|
|
608
612
|
if (node.finalizer) {
|
|
609
|
-
context.visit(node.finalizer,
|
|
613
|
+
context.visit(node.finalizer, state);
|
|
610
614
|
}
|
|
611
615
|
},
|
|
612
616
|
|
|
@@ -856,6 +860,12 @@ const visitors = {
|
|
|
856
860
|
},
|
|
857
861
|
};
|
|
858
862
|
|
|
863
|
+
/**
|
|
864
|
+
*
|
|
865
|
+
* @param {AST.Program} ast
|
|
866
|
+
* @param {string} filename
|
|
867
|
+
* @param {AnalyzeOptions} options
|
|
868
|
+
*/
|
|
859
869
|
export function analyze(ast, filename, options = {}) {
|
|
860
870
|
const scope_root = new ScopeRoot();
|
|
861
871
|
|
|
@@ -2192,6 +2192,18 @@ function transform_ts_child(node, context) {
|
|
|
2192
2192
|
catch_handler = b.catch_clause(node.handler.param || null, catch_body);
|
|
2193
2193
|
}
|
|
2194
2194
|
|
|
2195
|
+
let pending_block = null;
|
|
2196
|
+
if (node.pending) {
|
|
2197
|
+
const pending_scope = context.state.scopes.get(node.pending);
|
|
2198
|
+
pending_block = b.try_item_block(
|
|
2199
|
+
transform_body(node.pending.body, {
|
|
2200
|
+
...context,
|
|
2201
|
+
state: { ...context.state, scope: pending_scope },
|
|
2202
|
+
}),
|
|
2203
|
+
node.pending.loc,
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2195
2207
|
let finally_block = null;
|
|
2196
2208
|
if (node.finalizer) {
|
|
2197
2209
|
const finally_scope = context.state.scopes.get(node.finalizer);
|
|
@@ -2203,7 +2215,7 @@ function transform_ts_child(node, context) {
|
|
|
2203
2215
|
);
|
|
2204
2216
|
}
|
|
2205
2217
|
|
|
2206
|
-
state.init.push(b.try(try_body, catch_handler, finally_block));
|
|
2218
|
+
state.init.push(b.try(try_body, catch_handler, finally_block, pending_block));
|
|
2207
2219
|
} else if (node.type === 'Component') {
|
|
2208
2220
|
const component = visit(node, state);
|
|
2209
2221
|
|
|
@@ -2809,6 +2821,38 @@ function create_tsx_with_typescript_support() {
|
|
|
2809
2821
|
context.visit(node.body);
|
|
2810
2822
|
}
|
|
2811
2823
|
},
|
|
2824
|
+
// Custom handler for TryStatement to support Ripple's pending block
|
|
2825
|
+
TryStatement(node, context) {
|
|
2826
|
+
context.write('try ');
|
|
2827
|
+
context.visit(node.block);
|
|
2828
|
+
|
|
2829
|
+
if (node.pending) {
|
|
2830
|
+
// Output the pending block with source mapping for the 'pending' keyword
|
|
2831
|
+
context.write(' ');
|
|
2832
|
+
context.location(
|
|
2833
|
+
node.pending.loc.start.line,
|
|
2834
|
+
node.pending.loc.start.column - 'pending '.length,
|
|
2835
|
+
);
|
|
2836
|
+
context.write('pending ');
|
|
2837
|
+
context.visit(node.pending);
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
if (node.handler) {
|
|
2841
|
+
context.write(' catch');
|
|
2842
|
+
if (node.handler.param) {
|
|
2843
|
+
context.write(' (');
|
|
2844
|
+
context.visit(node.handler.param);
|
|
2845
|
+
context.write(')');
|
|
2846
|
+
}
|
|
2847
|
+
context.write(' ');
|
|
2848
|
+
context.visit(node.handler.body);
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
if (node.finalizer) {
|
|
2852
|
+
context.write(' finally ');
|
|
2853
|
+
context.visit(node.finalizer);
|
|
2854
|
+
}
|
|
2855
|
+
},
|
|
2812
2856
|
};
|
|
2813
2857
|
}
|
|
2814
2858
|
|
|
@@ -2818,9 +2862,10 @@ function create_tsx_with_typescript_support() {
|
|
|
2818
2862
|
* @param {string} source - Original source code
|
|
2819
2863
|
* @param {any} analysis - Analysis result
|
|
2820
2864
|
* @param {boolean} to_ts - Whether to generate TypeScript output
|
|
2865
|
+
* @param {boolean} minify_css - Whether to minify CSS output
|
|
2821
2866
|
* @returns {{ ast: any, js: { code: string, map: any, post_processing_changes?: PostProcessingChanges, line_offsets?: LineOffsets }, css: any }}
|
|
2822
2867
|
*/
|
|
2823
|
-
export function transform_client(filename, source, analysis, to_ts) {
|
|
2868
|
+
export function transform_client(filename, source, analysis, to_ts, minify_css) {
|
|
2824
2869
|
/**
|
|
2825
2870
|
* User's named imports from 'ripple' so we can reuse them in TS output
|
|
2826
2871
|
* when transforming shorthand syntax. E.g., if the user has already imported
|
|
@@ -2966,7 +3011,7 @@ export function transform_client(filename, source, analysis, to_ts) {
|
|
|
2966
3011
|
js.line_offsets = line_offsets;
|
|
2967
3012
|
}
|
|
2968
3013
|
|
|
2969
|
-
const css = render_stylesheets(state.stylesheets);
|
|
3014
|
+
const css = render_stylesheets(state.stylesheets, minify_css);
|
|
2970
3015
|
|
|
2971
3016
|
return {
|
|
2972
3017
|
ast: program,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} CustomMappingData
|
|
3
|
-
* @property {number[]} generatedLengths
|
|
4
|
-
*/
|
|
1
|
+
/** @import { CustomMappingData, PluginActionOverrides } from 'ripple/compiler'; */
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* @typedef {import('estree').Position} Position
|
|
@@ -19,6 +16,7 @@
|
|
|
19
16
|
|
|
20
17
|
import { walk } from 'zimmerframe';
|
|
21
18
|
import { build_source_to_generated_map, get_generated_position } from '../../source-map-utils.js';
|
|
19
|
+
import { DocumentHighlightKind } from 'vscode-languageserver-types';
|
|
22
20
|
|
|
23
21
|
/** @type {VolarCodeMapping['data']} */
|
|
24
22
|
export const mapping_data = {
|
|
@@ -152,6 +150,7 @@ export function convert_source_map_to_mappings(
|
|
|
152
150
|
* is_full_import_statement?: boolean,
|
|
153
151
|
* loc: Location,
|
|
154
152
|
* end_loc?: Location,
|
|
153
|
+
* metadata?: PluginActionOverrides
|
|
155
154
|
* }>}
|
|
156
155
|
*/
|
|
157
156
|
const tokens = [];
|
|
@@ -503,10 +502,53 @@ export function convert_source_map_to_mappings(
|
|
|
503
502
|
}
|
|
504
503
|
return;
|
|
505
504
|
} else if (node.type === 'TryStatement') {
|
|
506
|
-
// Visit in source order: block, handler, finalizer
|
|
505
|
+
// Visit in source order: block, pending, handler, finalizer
|
|
507
506
|
if (node.block) {
|
|
508
507
|
visit(node.block);
|
|
509
508
|
}
|
|
509
|
+
if (node.pending) {
|
|
510
|
+
// Add a special token for the 'pending' keyword with customData
|
|
511
|
+
// to suppress TypeScript diagnostics and provide custom hover/definition
|
|
512
|
+
const pendingKeywordLoc = {
|
|
513
|
+
start: {
|
|
514
|
+
line: node.pending.loc.start.line,
|
|
515
|
+
column: node.pending.loc.start.column - 'pending '.length,
|
|
516
|
+
},
|
|
517
|
+
end: {
|
|
518
|
+
line: node.pending.loc.start.line,
|
|
519
|
+
column: node.pending.loc.start.column - 1,
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
tokens.push({
|
|
523
|
+
source: 'pending',
|
|
524
|
+
generated: 'pending',
|
|
525
|
+
loc: pendingKeywordLoc,
|
|
526
|
+
metadata: {
|
|
527
|
+
wordHighlight: {
|
|
528
|
+
kind: DocumentHighlightKind.Text,
|
|
529
|
+
},
|
|
530
|
+
suppressedDiagnostics: [
|
|
531
|
+
1472, // 'catch' or 'finally' expected
|
|
532
|
+
2304, // Cannot find name 'pending'
|
|
533
|
+
],
|
|
534
|
+
// suppress all hovers
|
|
535
|
+
hover: false,
|
|
536
|
+
|
|
537
|
+
// Example of a custom hover contents (uses markdown)
|
|
538
|
+
// hover: {
|
|
539
|
+
// contents:
|
|
540
|
+
// '```ripple\npending\n```\n\nRipple-specific keyword for try/pending blocks.\n\nThe `pending` block executes while async operations inside the `try` block are awaiting. This provides a built-in loading state for async components.',
|
|
541
|
+
// },
|
|
542
|
+
|
|
543
|
+
// TODO: Definition is not implemented yet, leaving for future use
|
|
544
|
+
// definition: {
|
|
545
|
+
// description:
|
|
546
|
+
// 'Ripple pending block - executes during async operations in the try block',
|
|
547
|
+
// },
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
visit(node.pending);
|
|
551
|
+
}
|
|
510
552
|
if (node.handler) {
|
|
511
553
|
visit(node.handler);
|
|
512
554
|
}
|
|
@@ -1333,11 +1375,31 @@ export function convert_source_map_to_mappings(
|
|
|
1333
1375
|
);
|
|
1334
1376
|
gen_start = gen_loc_to_offset(gen_line_col.line, gen_line_col.column);
|
|
1335
1377
|
|
|
1378
|
+
/** @type {CustomMappingData} */
|
|
1379
|
+
const customData = {
|
|
1380
|
+
generatedLengths: [gen_length],
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// Add optional metadata from token if present
|
|
1384
|
+
if (token.metadata) {
|
|
1385
|
+
if ('wordHighlight' in token.metadata) {
|
|
1386
|
+
customData.wordHighlight = token.metadata.wordHighlight;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if ('suppressedDiagnostics' in token.metadata) {
|
|
1390
|
+
customData.suppressedDiagnostics = token.metadata.suppressedDiagnostics;
|
|
1391
|
+
}
|
|
1392
|
+
if ('hover' in token.metadata) {
|
|
1393
|
+
customData.hover = token.metadata.hover;
|
|
1394
|
+
}
|
|
1395
|
+
if ('definition' in token.metadata) {
|
|
1396
|
+
customData.definition = token.metadata.definition;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1336
1400
|
data = {
|
|
1337
1401
|
...mapping_data,
|
|
1338
|
-
customData
|
|
1339
|
-
generatedLengths: [gen_length],
|
|
1340
|
-
},
|
|
1402
|
+
customData,
|
|
1341
1403
|
};
|
|
1342
1404
|
}
|
|
1343
1405
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import * as ESTree from 'estree' */
|
|
2
|
+
|
|
1
3
|
import * as b from '../../../../utils/builders.js';
|
|
2
4
|
import { walk } from 'zimmerframe';
|
|
3
5
|
import ts from 'esrap/languages/ts';
|
|
@@ -804,7 +806,7 @@ const visitors = {
|
|
|
804
806
|
},
|
|
805
807
|
};
|
|
806
808
|
|
|
807
|
-
export function transform_server(filename, source, analysis) {
|
|
809
|
+
export function transform_server(filename, source, analysis, minify_css) {
|
|
808
810
|
// Use component metadata collected during the analyze phase
|
|
809
811
|
const component_metadata = analysis.component_metadata || [];
|
|
810
812
|
|
|
@@ -823,7 +825,7 @@ export function transform_server(filename, source, analysis) {
|
|
|
823
825
|
walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
|
|
824
826
|
);
|
|
825
827
|
|
|
826
|
-
const css = render_stylesheets(state.stylesheets);
|
|
828
|
+
const css = render_stylesheets(state.stylesheets, minify_css);
|
|
827
829
|
|
|
828
830
|
// Add CSS registration if there are stylesheets
|
|
829
831
|
if (state.stylesheets.length > 0 && css) {
|
|
@@ -1,25 +1,58 @@
|
|
|
1
|
+
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import { Visitors } from 'zimmerframe' */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* code: MagicString;
|
|
7
|
+
* hash: string;
|
|
8
|
+
* minify: boolean;
|
|
9
|
+
* selector: string;
|
|
10
|
+
* keyframes: Record<string, {
|
|
11
|
+
* indexes: number[];
|
|
12
|
+
* local: boolean | undefined;
|
|
13
|
+
* }>;
|
|
14
|
+
* specificity: {
|
|
15
|
+
* bumped: boolean
|
|
16
|
+
* }
|
|
17
|
+
* }} State
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
import MagicString from 'magic-string';
|
|
2
21
|
import { walk } from 'zimmerframe';
|
|
3
22
|
|
|
4
23
|
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
|
|
5
24
|
const regex_css_name_boundary = /^[\s,;}]$/;
|
|
6
25
|
|
|
26
|
+
/** @param {AST.CSS.Atrule} node */
|
|
7
27
|
const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes';
|
|
8
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
9
33
|
function remove_css_prefix(name) {
|
|
10
34
|
return name.replace(regex_css_browser_prefix, '');
|
|
11
35
|
}
|
|
12
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Walk backwards until we find a non-whitespace character
|
|
39
|
+
* @param {number} end
|
|
40
|
+
* @param {State} state
|
|
41
|
+
*/
|
|
13
42
|
function remove_preceding_whitespace(end, state) {
|
|
14
43
|
let start = end;
|
|
15
44
|
while (/\s/.test(state.code.original[start - 1])) start--;
|
|
16
45
|
if (start < end) state.code.remove(start, end);
|
|
17
46
|
}
|
|
18
47
|
|
|
48
|
+
/** @param {AST.CSS.Rule} rule */
|
|
19
49
|
function is_used(rule) {
|
|
20
50
|
return rule.prelude.children.some((selector) => selector.metadata.used);
|
|
21
51
|
}
|
|
22
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {Array<AST.CSS.Node>} path
|
|
55
|
+
*/
|
|
23
56
|
function is_in_global_block(path) {
|
|
24
57
|
return path.some((node) => node.type === 'Rule' && node.metadata.is_global_block);
|
|
25
58
|
}
|
|
@@ -27,7 +60,7 @@ function is_in_global_block(path) {
|
|
|
27
60
|
/**
|
|
28
61
|
* Check if we're inside a pseudo-class selector that's INSIDE a :global() wrapper
|
|
29
62
|
* or adjacent to a :global modifier
|
|
30
|
-
* @param {
|
|
63
|
+
* @param {AST.CSS.Node[]} path
|
|
31
64
|
*/
|
|
32
65
|
function is_in_global_pseudo(path) {
|
|
33
66
|
// Walk up the path to find if we're inside a :global() pseudo-class selector with args
|
|
@@ -83,6 +116,11 @@ function has_global_in_middle(rule) {
|
|
|
83
116
|
return false;
|
|
84
117
|
}
|
|
85
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @param {AST.CSS.PseudoClassSelector} selector
|
|
121
|
+
* @param {AST.CSS.Combinator | null} combinator
|
|
122
|
+
* @param {State} state
|
|
123
|
+
*/
|
|
86
124
|
function remove_global_pseudo_class(selector, combinator, state) {
|
|
87
125
|
if (selector.args === null) {
|
|
88
126
|
let start = selector.start;
|
|
@@ -98,6 +136,10 @@ function remove_global_pseudo_class(selector, combinator, state) {
|
|
|
98
136
|
}
|
|
99
137
|
}
|
|
100
138
|
|
|
139
|
+
/**
|
|
140
|
+
* @param {AST.CSS.Rule} node
|
|
141
|
+
* @param {MagicString} code
|
|
142
|
+
*/
|
|
101
143
|
function escape_comment_close(node, code) {
|
|
102
144
|
let escaped = false;
|
|
103
145
|
let in_comment = false;
|
|
@@ -121,12 +163,16 @@ function escape_comment_close(node, code) {
|
|
|
121
163
|
}
|
|
122
164
|
}
|
|
123
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @param {State} state
|
|
168
|
+
* @param {number} index
|
|
169
|
+
*/
|
|
124
170
|
function append_hash(state, index) {
|
|
125
171
|
state.code.prependRight(index, `${state.hash}-`);
|
|
126
172
|
}
|
|
127
173
|
|
|
128
174
|
/**
|
|
129
|
-
*
|
|
175
|
+
* @param {AST.CSS.Rule} rule
|
|
130
176
|
* @param {boolean} is_in_global_block
|
|
131
177
|
*/
|
|
132
178
|
function is_empty(rule, is_in_global_block) {
|
|
@@ -159,6 +205,7 @@ function is_empty(rule, is_in_global_block) {
|
|
|
159
205
|
return true;
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
/** @type {Visitors<AST.CSS.Node, State>} */
|
|
162
209
|
const visitors = {
|
|
163
210
|
_: (node, context) => {
|
|
164
211
|
context.state.code.addSourcemapLocation(node.start);
|
|
@@ -189,6 +236,7 @@ const visitors = {
|
|
|
189
236
|
const property = node.property && remove_css_prefix(node.property.toLowerCase());
|
|
190
237
|
if (property === 'animation' || property === 'animation-name') {
|
|
191
238
|
let index = node.start + node.property.length + 1;
|
|
239
|
+
/** @type {string} */
|
|
192
240
|
let name = '';
|
|
193
241
|
|
|
194
242
|
while (index < state.code.original.length) {
|
|
@@ -368,7 +416,10 @@ const visitors = {
|
|
|
368
416
|
) {
|
|
369
417
|
// Now check if this pseudo-class is part of a global RelativeSelector
|
|
370
418
|
for (let j = i - 2; j >= 0; j--) {
|
|
371
|
-
if (
|
|
419
|
+
if (
|
|
420
|
+
parentPath[j].type === 'RelativeSelector' &&
|
|
421
|
+
/** @type {AST.CSS.RelativeSelector} */ (parentPath[j]).metadata?.is_global
|
|
422
|
+
) {
|
|
372
423
|
insideScopingPseudo = true;
|
|
373
424
|
break;
|
|
374
425
|
}
|
|
@@ -462,7 +513,13 @@ const visitors = {
|
|
|
462
513
|
},
|
|
463
514
|
};
|
|
464
515
|
|
|
465
|
-
|
|
516
|
+
/**
|
|
517
|
+
* Render stylesheets to CSS string
|
|
518
|
+
* @param {AST.CSS.StyleSheet[]} stylesheets
|
|
519
|
+
* @param {boolean} [minify]
|
|
520
|
+
* @returns {string}
|
|
521
|
+
*/
|
|
522
|
+
export function render_stylesheets(stylesheets, minify = false) {
|
|
466
523
|
let css = '';
|
|
467
524
|
|
|
468
525
|
for (const stylesheet of stylesheets) {
|
|
@@ -470,6 +527,7 @@ export function render_stylesheets(stylesheets) {
|
|
|
470
527
|
const state = {
|
|
471
528
|
code,
|
|
472
529
|
hash: stylesheet.hash,
|
|
530
|
+
minify,
|
|
473
531
|
selector: `.${stylesheet.hash}`,
|
|
474
532
|
keyframes: {},
|
|
475
533
|
specificity: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as ESTree from 'estree';
|
|
2
2
|
|
|
3
3
|
// Ripple augmentation for ESTree function nodes
|
|
4
4
|
declare module 'estree' {
|
|
@@ -20,6 +20,178 @@ declare module 'estree' {
|
|
|
20
20
|
params: Pattern[];
|
|
21
21
|
body: BlockStatement;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
interface TryStatement {
|
|
25
|
+
pending?: BlockStatement | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export namespace CSS {
|
|
29
|
+
export interface BaseNode {
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface StyleSheet extends BaseNode {
|
|
35
|
+
type: 'StyleSheet';
|
|
36
|
+
children: Array<Atrule | Rule>;
|
|
37
|
+
source: string;
|
|
38
|
+
hash: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Atrule extends BaseNode {
|
|
42
|
+
type: 'Atrule';
|
|
43
|
+
name: string;
|
|
44
|
+
prelude: string;
|
|
45
|
+
block: Block | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Rule extends BaseNode {
|
|
49
|
+
type: 'Rule';
|
|
50
|
+
prelude: SelectorList;
|
|
51
|
+
block: Block;
|
|
52
|
+
metadata: {
|
|
53
|
+
parent_rule: Rule | null;
|
|
54
|
+
has_local_selectors: boolean;
|
|
55
|
+
is_global_block: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A list of selectors, e.g. `a, b, c {}`
|
|
61
|
+
*/
|
|
62
|
+
export interface SelectorList extends BaseNode {
|
|
63
|
+
type: 'SelectorList';
|
|
64
|
+
/**
|
|
65
|
+
* The `a`, `b` and `c` in `a, b, c {}`
|
|
66
|
+
*/
|
|
67
|
+
children: ComplexSelector[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A complex selector, e.g. `a b c {}`
|
|
72
|
+
*/
|
|
73
|
+
export interface ComplexSelector extends BaseNode {
|
|
74
|
+
type: 'ComplexSelector';
|
|
75
|
+
/**
|
|
76
|
+
* The `a`, `b` and `c` in `a b c {}`
|
|
77
|
+
*/
|
|
78
|
+
children: RelativeSelector[];
|
|
79
|
+
metadata: {
|
|
80
|
+
rule: Rule | null;
|
|
81
|
+
used: boolean;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A relative selector, e.g the `a` and `> b` in `a > b {}`
|
|
87
|
+
*/
|
|
88
|
+
export interface RelativeSelector extends BaseNode {
|
|
89
|
+
type: 'RelativeSelector';
|
|
90
|
+
/**
|
|
91
|
+
* In `a > b`, `> b` forms one relative selector, and `>` is the combinator. `null` for the first selector.
|
|
92
|
+
*/
|
|
93
|
+
combinator: null | Combinator;
|
|
94
|
+
/**
|
|
95
|
+
* The `b:is(...)` in `> b:is(...)`
|
|
96
|
+
*/
|
|
97
|
+
selectors: SimpleSelector[];
|
|
98
|
+
|
|
99
|
+
metadata: {
|
|
100
|
+
is_global: boolean;
|
|
101
|
+
is_global_like: boolean;
|
|
102
|
+
scoped: boolean;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface TypeSelector extends BaseNode {
|
|
107
|
+
type: 'TypeSelector';
|
|
108
|
+
name: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface IdSelector extends BaseNode {
|
|
112
|
+
type: 'IdSelector';
|
|
113
|
+
name: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface ClassSelector extends BaseNode {
|
|
117
|
+
type: 'ClassSelector';
|
|
118
|
+
name: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AttributeSelector extends BaseNode {
|
|
122
|
+
type: 'AttributeSelector';
|
|
123
|
+
name: string;
|
|
124
|
+
matcher: string | null;
|
|
125
|
+
value: string | null;
|
|
126
|
+
flags: string | null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface PseudoElementSelector extends BaseNode {
|
|
130
|
+
type: 'PseudoElementSelector';
|
|
131
|
+
name: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface PseudoClassSelector extends BaseNode {
|
|
135
|
+
type: 'PseudoClassSelector';
|
|
136
|
+
name: string;
|
|
137
|
+
args: SelectorList | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface Percentage extends BaseNode {
|
|
141
|
+
type: 'Percentage';
|
|
142
|
+
value: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface NestingSelector extends BaseNode {
|
|
146
|
+
type: 'NestingSelector';
|
|
147
|
+
name: '&';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface Nth extends BaseNode {
|
|
151
|
+
type: 'Nth';
|
|
152
|
+
value: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type SimpleSelector =
|
|
156
|
+
| TypeSelector
|
|
157
|
+
| IdSelector
|
|
158
|
+
| ClassSelector
|
|
159
|
+
| AttributeSelector
|
|
160
|
+
| PseudoElementSelector
|
|
161
|
+
| PseudoClassSelector
|
|
162
|
+
| Percentage
|
|
163
|
+
| Nth
|
|
164
|
+
| NestingSelector;
|
|
165
|
+
|
|
166
|
+
export interface Combinator extends BaseNode {
|
|
167
|
+
type: 'Combinator';
|
|
168
|
+
name: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface Block extends BaseNode {
|
|
172
|
+
type: 'Block';
|
|
173
|
+
children: Array<Declaration | Rule | Atrule>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface Declaration extends BaseNode {
|
|
177
|
+
type: 'Declaration';
|
|
178
|
+
property: string;
|
|
179
|
+
value: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// for zimmerframe
|
|
183
|
+
export type Node =
|
|
184
|
+
| StyleSheet
|
|
185
|
+
| Rule
|
|
186
|
+
| Atrule
|
|
187
|
+
| SelectorList
|
|
188
|
+
| Block
|
|
189
|
+
| ComplexSelector
|
|
190
|
+
| RelativeSelector
|
|
191
|
+
| Combinator
|
|
192
|
+
| SimpleSelector
|
|
193
|
+
| Declaration;
|
|
194
|
+
}
|
|
23
195
|
}
|
|
24
196
|
|
|
25
197
|
declare module 'estree-jsx' {
|
|
@@ -28,11 +200,11 @@ declare module 'estree-jsx' {
|
|
|
28
200
|
}
|
|
29
201
|
|
|
30
202
|
interface JSXOpeningElement {
|
|
31
|
-
loc: SourceLocation;
|
|
203
|
+
loc: ESTree.SourceLocation;
|
|
32
204
|
}
|
|
33
205
|
|
|
34
206
|
interface JSXClosingElement {
|
|
35
|
-
loc: SourceLocation;
|
|
207
|
+
loc: ESTree.SourceLocation;
|
|
36
208
|
}
|
|
37
209
|
}
|
|
38
210
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { Block } from '#client' */
|
|
2
2
|
|
|
3
3
|
import { branch, destroy_block, render, render_spread } from './blocks.js';
|
|
4
|
-
import { COMPOSITE_BLOCK,
|
|
4
|
+
import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
|
|
5
5
|
import { active_block, active_namespace, with_ns } from './runtime.js';
|
|
6
6
|
import { top_element_to_ns } from './utils.js';
|
|
7
7
|
|
|
@@ -32,8 +32,8 @@ export function composite(get_component, node, props) {
|
|
|
32
32
|
var block = active_block;
|
|
33
33
|
/** @type {ComponentFunction} */ (component)(anchor, props, block);
|
|
34
34
|
});
|
|
35
|
-
} else {
|
|
36
|
-
// Custom element
|
|
35
|
+
} else if (component != null) {
|
|
36
|
+
// Custom element - only create if component is not null/undefined
|
|
37
37
|
var run = () => {
|
|
38
38
|
var block = /** @type {Block} */ (active_block);
|
|
39
39
|
|
package/src/utils/builders.js
CHANGED
|
@@ -106,6 +106,15 @@ export function block(body) {
|
|
|
106
106
|
return { type: 'BlockStatement', body };
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* @param {ESTree.Statement[]} body
|
|
111
|
+
* @param {ESTree.SourceLocation} loc
|
|
112
|
+
* @returns {ESTree.BlockStatement}
|
|
113
|
+
*/
|
|
114
|
+
export function try_item_block(body, loc) {
|
|
115
|
+
return { type: 'BlockStatement', body, loc };
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
/**
|
|
110
119
|
* @param {string} name
|
|
111
120
|
* @param {ESTree.Statement} body
|
|
@@ -681,21 +690,23 @@ export function throw_error(str) {
|
|
|
681
690
|
* @param {ESTree.BlockStatement} block
|
|
682
691
|
* @param {ESTree.CatchClause | null} handler
|
|
683
692
|
* @param {ESTree.BlockStatement | null} finalizer
|
|
693
|
+
* @param {ESTree.BlockStatement | null} pending
|
|
684
694
|
* @returns {ESTree.TryStatement}
|
|
685
695
|
*/
|
|
686
|
-
export function try_builder(block, handler = null, finalizer = null) {
|
|
696
|
+
export function try_builder(block, handler = null, finalizer = null, pending = null) {
|
|
687
697
|
return {
|
|
688
698
|
type: 'TryStatement',
|
|
689
699
|
block,
|
|
690
700
|
handler,
|
|
691
701
|
finalizer,
|
|
702
|
+
pending,
|
|
692
703
|
};
|
|
693
704
|
}
|
|
694
705
|
|
|
695
706
|
/**
|
|
696
707
|
* @param {ESTree.Pattern | null} param
|
|
697
708
|
* @param {ESTree.BlockStatement} body
|
|
698
|
-
* @
|
|
709
|
+
* @return {ESTree.CatchClause}
|
|
699
710
|
*/
|
|
700
711
|
export function catch_clause_builder(param, body) {
|
|
701
712
|
return {
|
|
@@ -59,7 +59,6 @@ describe('composite > render', () => {
|
|
|
59
59
|
let name = 'Click Me';
|
|
60
60
|
|
|
61
61
|
<Child class="my-button">{name}</Child>
|
|
62
|
-
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
component Child({ children, ...rest }: { children: string; class: string }) {
|
|
@@ -82,4 +81,25 @@ describe('composite > render', () => {
|
|
|
82
81
|
|
|
83
82
|
render(ArrayTest);
|
|
84
83
|
});
|
|
84
|
+
|
|
85
|
+
it('should not render <undefined> tag when a passed in component is undefined', () => {
|
|
86
|
+
component Child({ children, NonExistent, ...props }) {
|
|
87
|
+
<div {...props}>
|
|
88
|
+
<children />
|
|
89
|
+
<NonExistent />
|
|
90
|
+
</div>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
component App() {
|
|
94
|
+
<Child />
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render(App);
|
|
98
|
+
|
|
99
|
+
const div = container.querySelector('div');
|
|
100
|
+
const undefinedTag = container.querySelector('undefined');
|
|
101
|
+
|
|
102
|
+
expect(undefinedTag).toBeNull();
|
|
103
|
+
expect(div.innerHTML).not.toContain('<undefined');
|
|
104
|
+
});
|
|
85
105
|
});
|