@tsrx/core 0.0.26 → 0.0.27
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 +1 -1
- package/src/analyze/validation.js +59 -0
- package/src/index.js +9 -1
- package/src/plugin.js +2 -61
- package/src/transform/jsx/ast-builders.js +51 -0
- package/src/transform/jsx/index.js +139 -43
- package/src/transform/segments.js +2 -10
- package/src/transform/stylesheet.js +19 -0
- package/types/index.d.ts +50 -12
- package/types/jsx-platform.d.ts +33 -2
package/package.json
CHANGED
|
@@ -19,6 +19,10 @@ export const COMPONENT_WHILE_STATEMENT_ERROR =
|
|
|
19
19
|
'While loops are not supported in components. Move the while loop into a function.';
|
|
20
20
|
export const COMPONENT_DO_WHILE_STATEMENT_ERROR =
|
|
21
21
|
'Do...while loops are not supported in components. Move the do...while loop into a function.';
|
|
22
|
+
export const CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR =
|
|
23
|
+
'Components declared inside a class must be defined as an arrow function class property (e.g. `Foo = component() => { ... }`). Non-arrow component property values are not allowed.';
|
|
24
|
+
export const COMPONENT_MULTIPLE_PARAMS_ERROR =
|
|
25
|
+
'Components accept a single props parameter. Move additional inputs into the props object instead.';
|
|
22
26
|
|
|
23
27
|
const invalid_nestings = {
|
|
24
28
|
// <p> cannot contain block-level elements
|
|
@@ -247,6 +251,61 @@ export function validate_component_unsupported_loop_statement(node, filename, er
|
|
|
247
251
|
error(message, filename ?? null, node, errors, comments);
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Validates that a component declares at most a single (props) parameter.
|
|
256
|
+
* Components have one slot for props; additional positional parameters are
|
|
257
|
+
* silently dropped or naively passed through depending on the target, so
|
|
258
|
+
* reject them at analysis time. Reports one error per extra parameter so
|
|
259
|
+
* every offending input gets its own TS diagnostic squiggle. In throwing
|
|
260
|
+
* mode the first call raises and aborts before the loop continues.
|
|
261
|
+
*
|
|
262
|
+
* @param {AST.Component} component
|
|
263
|
+
* @param {string | null | undefined} filename
|
|
264
|
+
* @param {CompileError[]} [errors]
|
|
265
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
266
|
+
*/
|
|
267
|
+
export function validate_component_params(component, filename, errors, comments) {
|
|
268
|
+
const params = /** @type {AST.Pattern[] | undefined} */ (component.params);
|
|
269
|
+
if (!params || params.length <= 1) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (let i = 1; i < params.length; i++) {
|
|
274
|
+
error(COMPONENT_MULTIPLE_PARAMS_ERROR, filename ?? null, params[i], errors, comments);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validates that components declared at the top level of a class body use the
|
|
280
|
+
* only allowed form: an arrow function class property (regular or static).
|
|
281
|
+
* Reports an error for non-arrow component property values such as
|
|
282
|
+
* `Foo = component() { ... }`. The method form (`component foo() {}` inside
|
|
283
|
+
* a class body) is rejected at parse time and never reaches this check.
|
|
284
|
+
*
|
|
285
|
+
* @param {AST.ClassBody} class_body
|
|
286
|
+
* @param {string | null | undefined} filename
|
|
287
|
+
* @param {CompileError[]} [errors]
|
|
288
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
289
|
+
*/
|
|
290
|
+
export function validate_class_component_declarations(class_body, filename, errors, comments) {
|
|
291
|
+
for (const member of class_body.body) {
|
|
292
|
+
if (member.type !== 'PropertyDefinition') {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const value = /** @type {any} */ (member).value;
|
|
297
|
+
if (value && value.type === 'Component' && !value.metadata?.arrow) {
|
|
298
|
+
error(
|
|
299
|
+
CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
|
|
300
|
+
filename ?? null,
|
|
301
|
+
member,
|
|
302
|
+
errors,
|
|
303
|
+
comments,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
250
309
|
/**
|
|
251
310
|
* @param {AST.Element} element
|
|
252
311
|
* @param {AnalysisContext} context
|
package/src/index.js
CHANGED
|
@@ -164,12 +164,16 @@ export {
|
|
|
164
164
|
flatten_switch_consequent,
|
|
165
165
|
get_for_of_iteration_params,
|
|
166
166
|
identifier_to_jsx_name,
|
|
167
|
+
is_bare_render_expression,
|
|
167
168
|
is_dynamic_element_id,
|
|
168
169
|
is_jsx_child,
|
|
169
170
|
set_loc,
|
|
170
171
|
to_text_expression,
|
|
171
172
|
} from './transform/jsx/ast-builders.js';
|
|
172
|
-
export {
|
|
173
|
+
export {
|
|
174
|
+
render_stylesheets as renderStylesheets,
|
|
175
|
+
render_css_result as renderCssResult,
|
|
176
|
+
} from './transform/stylesheet.js';
|
|
173
177
|
export {
|
|
174
178
|
prepare_stylesheet_for_render as prepareStylesheetForRender,
|
|
175
179
|
is_style_element as isStyleElement,
|
|
@@ -213,17 +217,21 @@ export {
|
|
|
213
217
|
// Analyze
|
|
214
218
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
|
215
219
|
export {
|
|
220
|
+
CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
|
|
216
221
|
COMPONENT_DO_WHILE_STATEMENT_ERROR,
|
|
217
222
|
COMPONENT_FOR_IN_STATEMENT_ERROR,
|
|
218
223
|
COMPONENT_FOR_STATEMENT_ERROR,
|
|
219
224
|
COMPONENT_LOOP_BREAK_ERROR,
|
|
220
225
|
COMPONENT_LOOP_RETURN_ERROR,
|
|
226
|
+
COMPONENT_MULTIPLE_PARAMS_ERROR,
|
|
221
227
|
COMPONENT_RETURN_VALUE_ERROR,
|
|
222
228
|
COMPONENT_WHILE_STATEMENT_ERROR,
|
|
223
229
|
get_return_keyword_node as getReturnKeywordNode,
|
|
224
230
|
get_statement_keyword_node as getStatementKeywordNode,
|
|
231
|
+
validate_class_component_declarations as validateClassComponentDeclarations,
|
|
225
232
|
validate_component_loop_break_statement as validateComponentLoopBreakStatement,
|
|
226
233
|
validate_component_loop_return_statement as validateComponentLoopReturnStatement,
|
|
234
|
+
validate_component_params as validateComponentParams,
|
|
227
235
|
validate_component_return_statement as validateComponentReturnStatement,
|
|
228
236
|
validate_component_unsupported_loop_statement as validateComponentUnsupportedLoopStatement,
|
|
229
237
|
validate_nesting as validateNesting,
|
package/src/plugin.js
CHANGED
|
@@ -559,65 +559,6 @@ export function TSRXPlugin(config) {
|
|
|
559
559
|
return super.parseProperty(isPattern, refDestructuringErrors);
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
/**
|
|
563
|
-
* Override parseClassElement to support component methods in classes.
|
|
564
|
-
* Handles syntax like `class Foo { component something() { <div /> } }`
|
|
565
|
-
* Also supports computed names: `class Foo { component ['something']() { <div /> } }`
|
|
566
|
-
* @type {Parse.Parser['parseClassElement']}
|
|
567
|
-
*/
|
|
568
|
-
parseClassElement(constructorAllowsSuper) {
|
|
569
|
-
// Check if this is a component method: component name( ... ) { ... }
|
|
570
|
-
if (this.type === tt.name && this.value === 'component') {
|
|
571
|
-
// Look ahead to see if this is "component identifier(",
|
|
572
|
-
// "component identifier<", "component [", or "component 'string'"
|
|
573
|
-
const lookahead = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
574
|
-
if (lookahead) {
|
|
575
|
-
// This is a component method definition
|
|
576
|
-
const node = /** @type {AST.MethodDefinition} */ (this.startNode());
|
|
577
|
-
const isComputed = lookahead[0].trim().startsWith('[');
|
|
578
|
-
const isStringLiteral = /^['"]/.test(lookahead[0].trim());
|
|
579
|
-
|
|
580
|
-
if (isComputed) {
|
|
581
|
-
// For computed names, consume 'component'
|
|
582
|
-
// parse the key, then parse component without name
|
|
583
|
-
this.next(); // consume 'component'
|
|
584
|
-
this.next(); // consume '['
|
|
585
|
-
node.key = this.parseExpression();
|
|
586
|
-
this.expect(tt.bracketR);
|
|
587
|
-
node.computed = true;
|
|
588
|
-
|
|
589
|
-
// Parse component without name (skipName: true)
|
|
590
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
591
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
592
|
-
} else if (isStringLiteral) {
|
|
593
|
-
// For string literal names, consume 'component'
|
|
594
|
-
// parse the string key, then parse component without name
|
|
595
|
-
this.next(); // consume 'component'
|
|
596
|
-
node.key = /** @type {AST.Literal} */ (this.parseExprAtom());
|
|
597
|
-
node.computed = false;
|
|
598
|
-
|
|
599
|
-
// Parse component without name (skipName: true)
|
|
600
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
601
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
602
|
-
} else {
|
|
603
|
-
// Use parseComponent which handles consuming 'component', parsing name, params, and body
|
|
604
|
-
const component_node = this.parseComponent({ requireName: true });
|
|
605
|
-
|
|
606
|
-
node.key = /** @type {AST.Identifier} */ (component_node.id);
|
|
607
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
608
|
-
node.computed = false;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
node.static = false;
|
|
612
|
-
node.kind = 'method';
|
|
613
|
-
|
|
614
|
-
return this.finishNode(node, 'MethodDefinition');
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
return super.parseClassElement(constructorAllowsSuper);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
562
|
/**
|
|
622
563
|
* Override parsePropertyValue to support TypeScript generic methods in object literals.
|
|
623
564
|
* By default, acorn-typescript doesn't handle `{ method<T>() {} }` syntax.
|
|
@@ -1371,8 +1312,8 @@ export function TSRXPlugin(config) {
|
|
|
1371
1312
|
*/
|
|
1372
1313
|
checkUnreserved(ref) {
|
|
1373
1314
|
if (ref.name === 'component') {
|
|
1374
|
-
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal
|
|
1375
|
-
// e.g., { component something() { ... } }
|
|
1315
|
+
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal)
|
|
1316
|
+
// e.g., { component something() { ... } }
|
|
1376
1317
|
// Also allow computed names: { component ['name']() { ... } }
|
|
1377
1318
|
// Also allow string literal names: { component 'name'() { ... } }
|
|
1378
1319
|
const nextChars = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
@@ -187,6 +187,57 @@ export function is_jsx_child(node) {
|
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The parser represents `<>{expr}</>` / `<tsx>{expr}</tsx>` as a Tsx node,
|
|
192
|
+
* and expression-position lowering unwraps that to the inner expression.
|
|
193
|
+
* When such a node appears directly in a component or statement render body,
|
|
194
|
+
* the unwrapped expression is still render output rather than an executable
|
|
195
|
+
* statement.
|
|
196
|
+
*
|
|
197
|
+
* @param {any} node
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
export function is_bare_render_expression(node) {
|
|
201
|
+
if (!node || typeof node !== 'object') {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
switch (node.type) {
|
|
206
|
+
case 'ArrayExpression':
|
|
207
|
+
case 'ArrowFunctionExpression':
|
|
208
|
+
case 'AssignmentExpression':
|
|
209
|
+
case 'AwaitExpression':
|
|
210
|
+
case 'BinaryExpression':
|
|
211
|
+
case 'CallExpression':
|
|
212
|
+
case 'ChainExpression':
|
|
213
|
+
case 'ClassExpression':
|
|
214
|
+
case 'ConditionalExpression':
|
|
215
|
+
case 'FunctionExpression':
|
|
216
|
+
case 'Identifier':
|
|
217
|
+
case 'ImportExpression':
|
|
218
|
+
case 'Literal':
|
|
219
|
+
case 'LogicalExpression':
|
|
220
|
+
case 'MemberExpression':
|
|
221
|
+
case 'MetaProperty':
|
|
222
|
+
case 'NewExpression':
|
|
223
|
+
case 'ObjectExpression':
|
|
224
|
+
case 'ParenthesizedExpression':
|
|
225
|
+
case 'SequenceExpression':
|
|
226
|
+
case 'TaggedTemplateExpression':
|
|
227
|
+
case 'TemplateLiteral':
|
|
228
|
+
case 'ThisExpression':
|
|
229
|
+
case 'TSAsExpression':
|
|
230
|
+
case 'TSSatisfiesExpression':
|
|
231
|
+
case 'TSNonNullExpression':
|
|
232
|
+
case 'UnaryExpression':
|
|
233
|
+
case 'UpdateExpression':
|
|
234
|
+
case 'YieldExpression':
|
|
235
|
+
return true;
|
|
236
|
+
default:
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
190
241
|
/**
|
|
191
242
|
* A dynamic element id is one whose identifier is `tracked` — i.e. it was
|
|
192
243
|
* introduced by reactive destructuring so its value can change at runtime.
|
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
flatten_switch_consequent,
|
|
21
21
|
get_for_of_iteration_params,
|
|
22
22
|
identifier_to_jsx_name,
|
|
23
|
+
is_bare_render_expression,
|
|
23
24
|
is_dynamic_element_id,
|
|
24
25
|
is_jsx_child,
|
|
25
26
|
set_loc,
|
|
26
27
|
to_text_expression,
|
|
27
28
|
} from './ast-builders.js';
|
|
28
|
-
import {
|
|
29
|
+
import { render_css_result } from '../stylesheet.js';
|
|
29
30
|
import {
|
|
30
31
|
set_location as setLocation,
|
|
31
32
|
jsx_attribute as build_jsx_attribute,
|
|
@@ -41,8 +42,10 @@ import {
|
|
|
41
42
|
import { find_first_top_level_await_in_component_body } from '../await.js';
|
|
42
43
|
import { prepare_stylesheet_for_render, annotate_component_with_hash } from '../scoping.js';
|
|
43
44
|
import {
|
|
45
|
+
validate_class_component_declarations,
|
|
44
46
|
validate_component_loop_break_statement,
|
|
45
47
|
validate_component_loop_return_statement,
|
|
48
|
+
validate_component_params,
|
|
46
49
|
validate_component_return_statement,
|
|
47
50
|
validate_component_unsupported_loop_statement,
|
|
48
51
|
} from '../../analyze/validation.js';
|
|
@@ -167,6 +170,8 @@ export function createJsxTransform(platform) {
|
|
|
167
170
|
needs_suspense: false,
|
|
168
171
|
needs_merge_refs: false,
|
|
169
172
|
needs_fragment: false,
|
|
173
|
+
module_scoped_hook_components:
|
|
174
|
+
options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
|
|
170
175
|
helper_state: null,
|
|
171
176
|
available_bindings: new Map(),
|
|
172
177
|
lazy_next_id: 0,
|
|
@@ -175,12 +180,15 @@ export function createJsxTransform(platform) {
|
|
|
175
180
|
collect,
|
|
176
181
|
errors: collect ? options?.errors : undefined,
|
|
177
182
|
comments: options?.comments,
|
|
183
|
+
typeOnly: !!options?.typeOnly,
|
|
178
184
|
// Platforms can seed their own tracking state (e.g. solid's
|
|
179
185
|
// needs_show / needs_for flags) via `hooks.initialState`.
|
|
180
186
|
...(platform.hooks?.initialState?.() ?? {}),
|
|
181
187
|
};
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
if (!transform_context.typeOnly) {
|
|
190
|
+
preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
|
|
191
|
+
}
|
|
184
192
|
|
|
185
193
|
walk(/** @type {any} */ (ast), transform_context, {
|
|
186
194
|
ReturnStatement(node, { next, path }) {
|
|
@@ -270,9 +278,26 @@ export function createJsxTransform(platform) {
|
|
|
270
278
|
return next();
|
|
271
279
|
},
|
|
272
280
|
|
|
281
|
+
ClassBody(node, { next }) {
|
|
282
|
+
validate_class_component_declarations(
|
|
283
|
+
/** @type {any} */ (node),
|
|
284
|
+
filename,
|
|
285
|
+
transform_context.errors,
|
|
286
|
+
transform_context.comments,
|
|
287
|
+
);
|
|
288
|
+
return next();
|
|
289
|
+
},
|
|
290
|
+
|
|
273
291
|
Component(node, { next, state }) {
|
|
274
292
|
const as_any = /** @type {any} */ (node);
|
|
275
293
|
|
|
294
|
+
validate_component_params(
|
|
295
|
+
as_any,
|
|
296
|
+
filename,
|
|
297
|
+
transform_context.errors,
|
|
298
|
+
transform_context.comments,
|
|
299
|
+
);
|
|
300
|
+
|
|
276
301
|
const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
|
|
277
302
|
|
|
278
303
|
if (await_expression) {
|
|
@@ -420,8 +445,15 @@ export function createJsxTransform(platform) {
|
|
|
420
445
|
// declarations, arrow functions, etc.). Component bodies have already been
|
|
421
446
|
// transformed inside component_to_function_declaration; this catches plain
|
|
422
447
|
// functions outside components and any lazy patterns in module scope.
|
|
448
|
+
// In type-only mode, the lazy patterns survive untouched: esrap ignores the
|
|
449
|
+
// non-standard `lazy` flag, so `&{ a, b }` prints as `{ a, b }`, `let &[a]
|
|
450
|
+
// = expr` prints as `let [a] = expr`, and the bare statement-level form
|
|
451
|
+
// `&[x] = expr;` (used when `x` is already declared) prints as `[x] =
|
|
452
|
+
// expr;` — a valid destructuring assignment to the existing binding.
|
|
423
453
|
const final_program = /** @type {any} */ (
|
|
424
|
-
|
|
454
|
+
transform_context.typeOnly
|
|
455
|
+
? expanded
|
|
456
|
+
: apply_lazy_transforms(/** @type {any} */ (expanded), new Map())
|
|
425
457
|
);
|
|
426
458
|
|
|
427
459
|
const result = print(/** @type {any} */ (final_program), tsx_with_ts_locations(), {
|
|
@@ -429,17 +461,11 @@ export function createJsxTransform(platform) {
|
|
|
429
461
|
sourceMapContent: source,
|
|
430
462
|
});
|
|
431
463
|
|
|
432
|
-
const css =
|
|
433
|
-
stylesheets.
|
|
434
|
-
|
|
435
|
-
code: renderStylesheets(
|
|
436
|
-
/** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
|
|
437
|
-
),
|
|
438
|
-
hash: stylesheets.map((s) => s.hash).join(' '),
|
|
439
|
-
}
|
|
440
|
-
: null;
|
|
464
|
+
const { css, cssHash } = render_css_result(
|
|
465
|
+
/** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
|
|
466
|
+
);
|
|
441
467
|
|
|
442
|
-
return { ast: final_program, code: result.code, map: result.map, css };
|
|
468
|
+
return { ast: final_program, code: result.code, map: result.map, css, cssHash };
|
|
443
469
|
}
|
|
444
470
|
|
|
445
471
|
return transform;
|
|
@@ -503,7 +529,11 @@ export function component_to_function_declaration(component, transform_context,
|
|
|
503
529
|
// Collect lazy binding info WITHOUT mutating patterns. Stores lazy_id on metadata
|
|
504
530
|
// for later replacement. Body bindings (count, setCount, etc.) are still in the
|
|
505
531
|
// original patterns, so collect_statement_bindings during build will find them.
|
|
506
|
-
|
|
532
|
+
// In type-only mode the lazy rewrite is skipped entirely so destructuring
|
|
533
|
+
// patterns survive into the virtual TSX and TypeScript can flow real types.
|
|
534
|
+
const lazy_bindings = transform_context.typeOnly
|
|
535
|
+
? new Map()
|
|
536
|
+
: collect_lazy_bindings_from_component(params, body, transform_context);
|
|
507
537
|
|
|
508
538
|
// Save and set context for this component scope
|
|
509
539
|
const saved_helper_state = transform_context.helper_state;
|
|
@@ -883,6 +913,8 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
883
913
|
} else {
|
|
884
914
|
render_nodes.push(jsx);
|
|
885
915
|
}
|
|
916
|
+
} else if (is_bare_render_expression(child)) {
|
|
917
|
+
render_nodes.push(to_jsx_expression_container(child, child));
|
|
886
918
|
} else {
|
|
887
919
|
statements.push(child);
|
|
888
920
|
collect_statement_bindings(child, transform_context.available_bindings);
|
|
@@ -1170,6 +1202,25 @@ function create_helper_state(base_name) {
|
|
|
1170
1202
|
};
|
|
1171
1203
|
}
|
|
1172
1204
|
|
|
1205
|
+
/**
|
|
1206
|
+
* @param {TransformContext} transform_context
|
|
1207
|
+
* @returns {boolean}
|
|
1208
|
+
*/
|
|
1209
|
+
function should_use_module_scoped_hook_components(transform_context) {
|
|
1210
|
+
return !!(transform_context.helper_state && transform_context.module_scoped_hook_components);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* @param {AST.Identifier} helper_id
|
|
1215
|
+
* @param {TransformContext} transform_context
|
|
1216
|
+
* @returns {AST.Identifier}
|
|
1217
|
+
*/
|
|
1218
|
+
function create_module_scoped_hook_component_id(helper_id, transform_context) {
|
|
1219
|
+
return create_generated_identifier(
|
|
1220
|
+
`${transform_context.helper_state?.base_name || 'Component'}__${helper_id.name}`,
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1173
1224
|
/**
|
|
1174
1225
|
* @param {any[]} params
|
|
1175
1226
|
* @returns {Map<string, AST.Identifier>}
|
|
@@ -2149,25 +2200,33 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2149
2200
|
const helper_id = create_generated_identifier(
|
|
2150
2201
|
create_local_statement_component_name(transform_context),
|
|
2151
2202
|
);
|
|
2152
|
-
|
|
2153
|
-
const
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2203
|
+
const use_module_scoped_component = should_use_module_scoped_hook_components(transform_context);
|
|
2204
|
+
const component_id = use_module_scoped_component
|
|
2205
|
+
? create_module_scoped_hook_component_id(helper_id, transform_context)
|
|
2206
|
+
: helper_id;
|
|
2207
|
+
|
|
2208
|
+
const outer_aliases = use_module_scoped_component
|
|
2209
|
+
? []
|
|
2210
|
+
: outer_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
|
|
2211
|
+
const loop_aliases = use_module_scoped_component
|
|
2212
|
+
? []
|
|
2213
|
+
: loop_bindings.map((binding) =>
|
|
2214
|
+
create_loop_scoped_type_alias_declaration(helper_id, binding, source_id, loop_params),
|
|
2215
|
+
);
|
|
2159
2216
|
|
|
2160
2217
|
// Synthetic `isLast` prop on the loop helper when there's a tail. It's
|
|
2161
2218
|
// passed from the .map callback as `i === source.length - 1` so every
|
|
2162
2219
|
// loop-helper return can append the tail helper on the last iteration.
|
|
2163
2220
|
const tail_isLast_alias = has_tail
|
|
2164
|
-
?
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2168
|
-
b.
|
|
2169
|
-
|
|
2170
|
-
|
|
2221
|
+
? use_module_scoped_component
|
|
2222
|
+
? null
|
|
2223
|
+
: {
|
|
2224
|
+
id: create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2225
|
+
declaration: b.ts_type_alias(
|
|
2226
|
+
create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2227
|
+
b.ts_keyword_type('boolean'),
|
|
2228
|
+
),
|
|
2229
|
+
}
|
|
2171
2230
|
: null;
|
|
2172
2231
|
|
|
2173
2232
|
const ordered_bindings = [...outer_bindings, ...loop_bindings];
|
|
@@ -2181,7 +2240,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2181
2240
|
const signature_use_typeof = has_tail ? [...ordered_use_typeof, false] : ordered_use_typeof;
|
|
2182
2241
|
|
|
2183
2242
|
const props_type =
|
|
2184
|
-
signature_bindings.length > 0
|
|
2243
|
+
signature_bindings.length > 0 && !use_module_scoped_component
|
|
2185
2244
|
? create_helper_props_type_literal_with_typeof_flags(
|
|
2186
2245
|
signature_bindings,
|
|
2187
2246
|
signature_aliases,
|
|
@@ -2189,7 +2248,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2189
2248
|
)
|
|
2190
2249
|
: null;
|
|
2191
2250
|
const params =
|
|
2192
|
-
|
|
2251
|
+
signature_bindings.length > 0
|
|
2252
|
+
? [
|
|
2253
|
+
props_type !== null
|
|
2254
|
+
? create_typed_helper_props_pattern(signature_bindings, props_type)
|
|
2255
|
+
: create_helper_props_pattern(signature_bindings),
|
|
2256
|
+
]
|
|
2257
|
+
: [];
|
|
2193
2258
|
|
|
2194
2259
|
const fn_saved_bindings = transform_context.available_bindings;
|
|
2195
2260
|
transform_context.available_bindings = new Map(fn_saved_bindings);
|
|
@@ -2207,12 +2272,17 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2207
2272
|
transform_context.available_bindings = fn_saved_bindings;
|
|
2208
2273
|
|
|
2209
2274
|
const helper_fn = /** @type {any} */ (
|
|
2210
|
-
b.function(clone_identifier(
|
|
2275
|
+
b.function(clone_identifier(component_id), params, b.block(fn_body_statements))
|
|
2211
2276
|
);
|
|
2212
2277
|
helper_fn.metadata = { path: [], is_component: true, is_method: false };
|
|
2213
2278
|
|
|
2214
2279
|
let helper_decl;
|
|
2215
|
-
if (transform_context.helper_state) {
|
|
2280
|
+
if (transform_context.helper_state && use_module_scoped_component) {
|
|
2281
|
+
transform_context.helper_state.helpers.push(
|
|
2282
|
+
create_helper_declaration(component_id, helper_fn, node, transform_context),
|
|
2283
|
+
);
|
|
2284
|
+
helper_decl = null;
|
|
2285
|
+
} else if (transform_context.helper_state) {
|
|
2216
2286
|
const cache_id = create_generated_identifier(
|
|
2217
2287
|
`${transform_context.helper_state.base_name}__${helper_id.name}`,
|
|
2218
2288
|
);
|
|
@@ -2229,7 +2299,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2229
2299
|
transform_context.available_bindings = saved_bindings;
|
|
2230
2300
|
|
|
2231
2301
|
const callback_invocation_element = create_helper_component_element(
|
|
2232
|
-
|
|
2302
|
+
component_id,
|
|
2233
2303
|
ordered_bindings,
|
|
2234
2304
|
node,
|
|
2235
2305
|
{ mapWrapper: false, mapBindingNames: false, mapBindingValues: false },
|
|
@@ -2310,7 +2380,9 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2310
2380
|
if (has_tail && tail_isLast_alias) {
|
|
2311
2381
|
hoist_statements.push(tail_isLast_alias.declaration);
|
|
2312
2382
|
}
|
|
2313
|
-
|
|
2383
|
+
if (helper_decl) {
|
|
2384
|
+
hoist_statements.push(helper_decl);
|
|
2385
|
+
}
|
|
2314
2386
|
|
|
2315
2387
|
return {
|
|
2316
2388
|
hoist_statements,
|
|
@@ -2770,8 +2842,8 @@ function create_local_statement_component_name(transform_context) {
|
|
|
2770
2842
|
/**
|
|
2771
2843
|
* Wraps a list of body nodes into a component and returns
|
|
2772
2844
|
* statements that return `<ComponentName prop1={prop1} ... />`.
|
|
2773
|
-
*
|
|
2774
|
-
*
|
|
2845
|
+
* Targets can either emit the helper component at module scope or cache the
|
|
2846
|
+
* component identity in module state while initializing it from the parent.
|
|
2775
2847
|
* Used when a control flow branch contains hook calls that must be moved
|
|
2776
2848
|
* into their own component boundary to satisfy the Rules of Hooks.
|
|
2777
2849
|
*
|
|
@@ -2844,24 +2916,36 @@ function create_hook_safe_helper(
|
|
|
2844
2916
|
const helper_id =
|
|
2845
2917
|
preallocated_helper_id ??
|
|
2846
2918
|
create_generated_identifier(create_local_statement_component_name(transform_context));
|
|
2919
|
+
const use_module_scoped_component = should_use_module_scoped_hook_components(transform_context);
|
|
2920
|
+
const component_id = use_module_scoped_component
|
|
2921
|
+
? create_module_scoped_hook_component_id(helper_id, transform_context)
|
|
2922
|
+
: helper_id;
|
|
2847
2923
|
const helper_bindings = get_referenced_helper_bindings(
|
|
2848
2924
|
body_nodes,
|
|
2849
2925
|
transform_context.available_bindings,
|
|
2850
2926
|
);
|
|
2851
|
-
const aliases =
|
|
2852
|
-
|
|
2853
|
-
|
|
2927
|
+
const aliases = use_module_scoped_component
|
|
2928
|
+
? []
|
|
2929
|
+
: helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
|
|
2854
2930
|
const props_type =
|
|
2855
|
-
helper_bindings.length > 0
|
|
2931
|
+
helper_bindings.length > 0 && !use_module_scoped_component
|
|
2932
|
+
? create_helper_props_type_literal(helper_bindings, aliases)
|
|
2933
|
+
: null;
|
|
2856
2934
|
const params =
|
|
2857
|
-
|
|
2935
|
+
helper_bindings.length > 0
|
|
2936
|
+
? [
|
|
2937
|
+
props_type !== null
|
|
2938
|
+
? create_typed_helper_props_pattern(helper_bindings, props_type)
|
|
2939
|
+
: create_helper_props_pattern(helper_bindings),
|
|
2940
|
+
]
|
|
2941
|
+
: [];
|
|
2858
2942
|
|
|
2859
2943
|
const saved_bindings = transform_context.available_bindings;
|
|
2860
2944
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
2861
2945
|
|
|
2862
2946
|
const helper_fn = /** @type {any} */ ({
|
|
2863
2947
|
type: 'FunctionExpression',
|
|
2864
|
-
id: clone_identifier(
|
|
2948
|
+
id: clone_identifier(component_id),
|
|
2865
2949
|
params,
|
|
2866
2950
|
body: {
|
|
2867
2951
|
type: 'BlockStatement',
|
|
@@ -2880,7 +2964,7 @@ function create_hook_safe_helper(
|
|
|
2880
2964
|
transform_context.available_bindings = saved_bindings;
|
|
2881
2965
|
|
|
2882
2966
|
const component_element = create_helper_component_element(
|
|
2883
|
-
|
|
2967
|
+
component_id,
|
|
2884
2968
|
helper_bindings,
|
|
2885
2969
|
source_node,
|
|
2886
2970
|
{
|
|
@@ -2911,6 +2995,16 @@ function create_hook_safe_helper(
|
|
|
2911
2995
|
};
|
|
2912
2996
|
}
|
|
2913
2997
|
|
|
2998
|
+
if (use_module_scoped_component) {
|
|
2999
|
+
transform_context.helper_state.helpers.push(
|
|
3000
|
+
create_helper_declaration(component_id, helper_fn, source_node, transform_context),
|
|
3001
|
+
);
|
|
3002
|
+
return {
|
|
3003
|
+
setup_statements: [],
|
|
3004
|
+
component_element,
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2914
3008
|
const cache_id = create_generated_identifier(
|
|
2915
3009
|
`${transform_context.helper_state.base_name}__${helper_id.name}`,
|
|
2916
3010
|
);
|
|
@@ -4262,6 +4356,8 @@ function create_render_switch_case(switch_case, transform_context) {
|
|
|
4262
4356
|
|
|
4263
4357
|
if (is_jsx_child(child)) {
|
|
4264
4358
|
render_nodes.push(to_jsx_child(child, transform_context));
|
|
4359
|
+
} else if (is_bare_render_expression(child)) {
|
|
4360
|
+
render_nodes.push(to_jsx_expression_container(child, child));
|
|
4265
4361
|
} else {
|
|
4266
4362
|
case_body.push(child);
|
|
4267
4363
|
}
|
|
@@ -439,7 +439,6 @@ export function convert_source_map_to_mappings(
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
/**
|
|
442
|
-
* @typedef {AST.MethodDefinition & {value: {metadata: {is_component: true}}}} MethodIsComponent
|
|
443
442
|
* @typedef {AST.Property & {value: AST.FunctionExpression, method: true} & {value: {metadata: {is_component: true}}}} PropertyIsComponent
|
|
444
443
|
*/
|
|
445
444
|
|
|
@@ -447,7 +446,7 @@ export function convert_source_map_to_mappings(
|
|
|
447
446
|
* Maps `component` to the identifier's location
|
|
448
447
|
* e.g. const obj = { component something() { } }
|
|
449
448
|
* since there is no function keyword in source maps
|
|
450
|
-
* @param {
|
|
449
|
+
* @param {PropertyIsComponent} node
|
|
451
450
|
* @returns {void}
|
|
452
451
|
*/
|
|
453
452
|
function set_component_mapping_to_name(node) {
|
|
@@ -1530,15 +1529,8 @@ export function convert_source_map_to_mappings(
|
|
|
1530
1529
|
set_bracket_computed_mapping(node, mappings);
|
|
1531
1530
|
}
|
|
1532
1531
|
|
|
1533
|
-
if (node.value.metadata.is_component) {
|
|
1534
|
-
set_component_mapping_to_name(/** @type {MethodIsComponent} */ (node));
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
1532
|
if (node.key.type === 'Literal') {
|
|
1538
|
-
handle_literal(
|
|
1539
|
-
node.key,
|
|
1540
|
-
/** @type {AST.FunctionExpression} */ (node.value).metadata.is_component,
|
|
1541
|
-
);
|
|
1533
|
+
handle_literal(node.key);
|
|
1542
1534
|
} else {
|
|
1543
1535
|
visit(node.key);
|
|
1544
1536
|
}
|
|
@@ -543,3 +543,22 @@ export function render_stylesheets(stylesheets, minify = false) {
|
|
|
543
543
|
|
|
544
544
|
return css;
|
|
545
545
|
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Render the `{ css, cssHash }` slice of a `CompileResult` from a list of
|
|
549
|
+
* stylesheets. Returns `{ css: '', cssHash: null }` when the list is empty
|
|
550
|
+
* so consumers can pass the result straight into a flat compile result.
|
|
551
|
+
*
|
|
552
|
+
* @param {AST.CSS.StyleSheet[]} stylesheets
|
|
553
|
+
* @param {boolean} [minify]
|
|
554
|
+
* @returns {{ css: string, cssHash: string | null }}
|
|
555
|
+
*/
|
|
556
|
+
export function render_css_result(stylesheets, minify = false) {
|
|
557
|
+
if (stylesheets.length === 0) {
|
|
558
|
+
return { css: '', cssHash: null };
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
css: render_stylesheets(stylesheets, minify),
|
|
562
|
+
cssHash: stylesheets.map((s) => s.hash).join(' '),
|
|
563
|
+
};
|
|
564
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -463,10 +463,6 @@ declare module 'estree' {
|
|
|
463
463
|
body: (Program['body'][number] | Component | FunctionExpression)[];
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
interface TSRXMethodDefinition extends Omit<AST.MethodDefinition, 'value'> {
|
|
467
|
-
value: AST.MethodDefinition['value'] | Component;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
466
|
interface TSRXProperty extends Omit<AST.Property, 'value'> {
|
|
471
467
|
value: AST.Property['value'] | Component;
|
|
472
468
|
}
|
|
@@ -1575,15 +1571,17 @@ export interface VolarMappingsResult {
|
|
|
1575
1571
|
* Result of compilation operation
|
|
1576
1572
|
*/
|
|
1577
1573
|
export interface CompileResult {
|
|
1578
|
-
/** The
|
|
1579
|
-
|
|
1580
|
-
/**
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
map: import('source-map').RawSourceMap;
|
|
1584
|
-
};
|
|
1585
|
-
/** The generated CSS */
|
|
1574
|
+
/** The generated JavaScript code */
|
|
1575
|
+
code: string;
|
|
1576
|
+
/** Source map for the generated code */
|
|
1577
|
+
map: import('source-map').RawSourceMap;
|
|
1578
|
+
/** Rendered CSS for the module, or `''` when the module emits no styles. */
|
|
1586
1579
|
css: string;
|
|
1580
|
+
/**
|
|
1581
|
+
* Space-separated scope hashes for the rendered CSS, or `null` when the
|
|
1582
|
+
* module emits no styles.
|
|
1583
|
+
*/
|
|
1584
|
+
cssHash: string | null;
|
|
1587
1585
|
/**
|
|
1588
1586
|
* Non-fatal errors collected during compilation. Populated only when the
|
|
1589
1587
|
* caller passes `collect: true` or `loose: true`; empty otherwise.
|
|
@@ -1599,6 +1597,46 @@ export interface VolarCompileOptions extends Omit<ParseOptions, 'errors' | 'comm
|
|
|
1599
1597
|
dev?: boolean;
|
|
1600
1598
|
}
|
|
1601
1599
|
|
|
1600
|
+
/**
|
|
1601
|
+
* Common base options accepted by every TSRX target's `compile` entry point.
|
|
1602
|
+
* Targets that need extra knobs (e.g. ripple's `mode`/`dev`/`hmr`, preact's
|
|
1603
|
+
* `suspenseSource`) intersect their own option type with this base when
|
|
1604
|
+
* declaring their `compile` export.
|
|
1605
|
+
*/
|
|
1606
|
+
export interface BaseCompileOptions {
|
|
1607
|
+
collect?: boolean;
|
|
1608
|
+
loose?: boolean;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/**
|
|
1612
|
+
* Shared `compile` signature for every TSRX target package. Per-target
|
|
1613
|
+
* `compile` declarations should be `CompileFn<TOptions, TResult>` so any
|
|
1614
|
+
* drift in the shared contract becomes a typecheck error in every package.
|
|
1615
|
+
*
|
|
1616
|
+
* @template TOptions Per-target options accepted as the third argument.
|
|
1617
|
+
* Defaults to {@link BaseCompileOptions}.
|
|
1618
|
+
* @template TResult Per-target result type. Must extend {@link CompileResult};
|
|
1619
|
+
* targets may add fields (e.g. ripple's deprecated `js` back-compat field)
|
|
1620
|
+
* via intersection.
|
|
1621
|
+
*/
|
|
1622
|
+
export type CompileFn<
|
|
1623
|
+
TOptions = BaseCompileOptions,
|
|
1624
|
+
TResult extends CompileResult = CompileResult,
|
|
1625
|
+
> = (source: string, filename?: string, options?: TOptions) => TResult;
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Shared `compile_to_volar_mappings` signature for every TSRX target package.
|
|
1629
|
+
*
|
|
1630
|
+
* @template TOptions Per-target options accepted as the third argument.
|
|
1631
|
+
* Defaults to {@link ParseOptions}; targets may intersect their own option
|
|
1632
|
+
* type to add e.g. `suspenseSource`.
|
|
1633
|
+
*/
|
|
1634
|
+
export type VolarCompileFn<TOptions = ParseOptions> = (
|
|
1635
|
+
source: string,
|
|
1636
|
+
filename?: string,
|
|
1637
|
+
options?: TOptions,
|
|
1638
|
+
) => VolarMappingsResult;
|
|
1639
|
+
|
|
1602
1640
|
/**
|
|
1603
1641
|
* Source map transformation types
|
|
1604
1642
|
*/
|
package/types/jsx-platform.d.ts
CHANGED
|
@@ -14,7 +14,14 @@ export interface JsxTransformResult {
|
|
|
14
14
|
* downstream Vite / Rollup plugins to chain source maps.
|
|
15
15
|
*/
|
|
16
16
|
map: RawSourceMap;
|
|
17
|
-
|
|
17
|
+
/** Rendered CSS for the module, or `''` when the module emits no styles. */
|
|
18
|
+
css: string;
|
|
19
|
+
/**
|
|
20
|
+
* Space-separated scope hashes for the rendered CSS, or `null` when the
|
|
21
|
+
* module emits no styles. When multiple `<style>` blocks contribute, the
|
|
22
|
+
* hashes appear in source order.
|
|
23
|
+
*/
|
|
24
|
+
cssHash: string | null;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/**
|
|
@@ -30,6 +37,7 @@ export interface JsxTransformContext {
|
|
|
30
37
|
needs_suspense: boolean;
|
|
31
38
|
needs_merge_refs: boolean;
|
|
32
39
|
needs_fragment: boolean;
|
|
40
|
+
module_scoped_hook_components: boolean;
|
|
33
41
|
helper_state: {
|
|
34
42
|
base_name: string;
|
|
35
43
|
next_id: number;
|
|
@@ -48,6 +56,8 @@ export interface JsxTransformContext {
|
|
|
48
56
|
errors: CompileError[] | undefined;
|
|
49
57
|
/** Module-level comments used to honor `@tsrx-ignore` / `@tsrx-expect-error`. */
|
|
50
58
|
comments: AST.CommentWithLocation[] | undefined;
|
|
59
|
+
/** True when emitting a type-only virtual TSX module; preserves lazy destructuring patterns. */
|
|
60
|
+
typeOnly: boolean;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
|
@@ -80,6 +90,20 @@ export interface JsxTransformOptions {
|
|
|
80
90
|
* `@tsrx-expect-error` line comments.
|
|
81
91
|
*/
|
|
82
92
|
comments?: AST.CommentWithLocation[];
|
|
93
|
+
/**
|
|
94
|
+
* Override whether hook-isolation helper components are emitted directly at
|
|
95
|
+
* module scope. React runtime compilation enables this, while editor tooling
|
|
96
|
+
* can disable it to preserve lexical `typeof` helper prop types.
|
|
97
|
+
*/
|
|
98
|
+
moduleScopedHookComponents?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Emit a type-only virtual TSX module — output is fed to TypeScript for
|
|
101
|
+
* editor diagnostics / completions and never executed. Skips the lazy
|
|
102
|
+
* destructuring rewrite (`&{ a, b }` → `__lazy0: { a: any; b: any }`) so
|
|
103
|
+
* destructuring patterns survive and TypeScript can flow real types to the
|
|
104
|
+
* bindings.
|
|
105
|
+
*/
|
|
106
|
+
typeOnly?: boolean;
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
/**
|
|
@@ -135,6 +159,13 @@ export interface JsxPlatformHooks {
|
|
|
135
159
|
* state behaves like normal component state.
|
|
136
160
|
*/
|
|
137
161
|
wrapHelperComponent?: (helperFn: any, helperId: any, ctx: any, sourceNode: any) => any;
|
|
162
|
+
/**
|
|
163
|
+
* Emit hook-isolation helper components as unique module-scope declarations
|
|
164
|
+
* instead of lazily creating and caching them from the parent component body.
|
|
165
|
+
* React enables this so generated branches stay compatible with the React
|
|
166
|
+
* Compiler's Rules of Hooks validation.
|
|
167
|
+
*/
|
|
168
|
+
moduleScopedHookComponents?: boolean;
|
|
138
169
|
/**
|
|
139
170
|
* Inject module-level imports after the main walk. Default: import
|
|
140
171
|
* `Suspense` from `platform.imports.suspense` and `TsrxErrorBoundary`
|
|
@@ -162,7 +193,7 @@ export interface JsxPlatformHooks {
|
|
|
162
193
|
* Optionally replace the default React-style `.map(...)` lowering for a
|
|
163
194
|
* `for...of` body after the shared transform has already produced its render
|
|
164
195
|
* statements and applied any explicit or implicit keys. Vue uses this to hand
|
|
165
|
-
* the loop to the downstream Vapor JSX compiler as a
|
|
196
|
+
* the loop to the downstream Vapor JSX compiler as a typed `VaporFor` component.
|
|
166
197
|
*/
|
|
167
198
|
renderForOf?: (node: any, loopParams: any[], bodyStatements: any[], ctx: any) => any | null;
|
|
168
199
|
/**
|