@tsrx/core 0.0.25 → 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 +205 -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;
|
|
@@ -624,6 +654,10 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
624
654
|
// any JSX is constructed, and every JSX child would observe the final
|
|
625
655
|
// state of mutable variables.
|
|
626
656
|
const interleaved = is_interleaved_body(body_nodes);
|
|
657
|
+
const capture_static_early_return_nodes =
|
|
658
|
+
!interleaved &&
|
|
659
|
+
!transform_context.platform.hooks?.isTopLevelSetupCall &&
|
|
660
|
+
body_nodes.filter(is_returning_if_statement).length > 1;
|
|
627
661
|
let capture_index = 0;
|
|
628
662
|
|
|
629
663
|
for (let i = 0; i < body_nodes.length; i += 1) {
|
|
@@ -648,6 +682,15 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
648
682
|
true,
|
|
649
683
|
);
|
|
650
684
|
|
|
685
|
+
if (capture_static_early_return_nodes) {
|
|
686
|
+
capture_index = capture_static_early_return_render_nodes(
|
|
687
|
+
render_nodes,
|
|
688
|
+
statements,
|
|
689
|
+
capture_index,
|
|
690
|
+
transform_context,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
651
694
|
if (branch_has_hooks || continuation_has_hooks) {
|
|
652
695
|
if (transform_context.platform.hooks?.isTopLevelSetupCall) {
|
|
653
696
|
statements.push(
|
|
@@ -870,6 +913,8 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
870
913
|
} else {
|
|
871
914
|
render_nodes.push(jsx);
|
|
872
915
|
}
|
|
916
|
+
} else if (is_bare_render_expression(child)) {
|
|
917
|
+
render_nodes.push(to_jsx_expression_container(child, child));
|
|
873
918
|
} else {
|
|
874
919
|
statements.push(child);
|
|
875
920
|
collect_statement_bindings(child, transform_context.available_bindings);
|
|
@@ -1157,6 +1202,25 @@ function create_helper_state(base_name) {
|
|
|
1157
1202
|
};
|
|
1158
1203
|
}
|
|
1159
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
|
+
|
|
1160
1224
|
/**
|
|
1161
1225
|
* @param {any[]} params
|
|
1162
1226
|
* @returns {Map<string, AST.Identifier>}
|
|
@@ -1325,6 +1389,59 @@ function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
|
1325
1389
|
}
|
|
1326
1390
|
}
|
|
1327
1391
|
|
|
1392
|
+
/**
|
|
1393
|
+
* Static JSX that appears before multiple early-return guards is otherwise
|
|
1394
|
+
* cloned into every generated return. Capture it once at its source position
|
|
1395
|
+
* and reuse the reference, matching the interleaved-statement capture path
|
|
1396
|
+
* without moving dynamic render-time expressions across guards.
|
|
1397
|
+
*
|
|
1398
|
+
* @param {any[]} render_nodes
|
|
1399
|
+
* @param {any[]} statements
|
|
1400
|
+
* @param {number} capture_index
|
|
1401
|
+
* @param {TransformContext} transform_context
|
|
1402
|
+
* @returns {number}
|
|
1403
|
+
*/
|
|
1404
|
+
function capture_static_early_return_render_nodes(
|
|
1405
|
+
render_nodes,
|
|
1406
|
+
statements,
|
|
1407
|
+
capture_index,
|
|
1408
|
+
transform_context,
|
|
1409
|
+
) {
|
|
1410
|
+
for (let i = 0; i < render_nodes.length; i += 1) {
|
|
1411
|
+
const node = render_nodes[i];
|
|
1412
|
+
if (!is_static_early_return_capture_node(node, transform_context)) {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const { declaration, reference } = captureJsxChild(node, capture_index++);
|
|
1417
|
+
statements.push(declaration);
|
|
1418
|
+
render_nodes[i] = reference;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return capture_index;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* @param {any} node
|
|
1426
|
+
* @param {TransformContext} transform_context
|
|
1427
|
+
* @returns {boolean}
|
|
1428
|
+
*/
|
|
1429
|
+
function is_static_early_return_capture_node(node, transform_context) {
|
|
1430
|
+
if (node?.type !== 'JSXElement' && node?.type !== 'JSXFragment') {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
if (!is_hoist_safe_jsx_node(node)) {
|
|
1434
|
+
return false;
|
|
1435
|
+
}
|
|
1436
|
+
if (
|
|
1437
|
+
transform_context.platform.hooks?.canHoistStaticNode &&
|
|
1438
|
+
!transform_context.platform.hooks.canHoistStaticNode(node, transform_context)
|
|
1439
|
+
) {
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
return !references_scope_bindings(node, transform_context.available_bindings);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1328
1445
|
/**
|
|
1329
1446
|
* @param {AST.Program} program
|
|
1330
1447
|
* @returns {AST.Program}
|
|
@@ -2083,25 +2200,33 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2083
2200
|
const helper_id = create_generated_identifier(
|
|
2084
2201
|
create_local_statement_component_name(transform_context),
|
|
2085
2202
|
);
|
|
2086
|
-
|
|
2087
|
-
const
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
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
|
+
);
|
|
2093
2216
|
|
|
2094
2217
|
// Synthetic `isLast` prop on the loop helper when there's a tail. It's
|
|
2095
2218
|
// passed from the .map callback as `i === source.length - 1` so every
|
|
2096
2219
|
// loop-helper return can append the tail helper on the last iteration.
|
|
2097
2220
|
const tail_isLast_alias = has_tail
|
|
2098
|
-
?
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
create_generated_identifier(`_tsrx_${helper_id.name}_isLast`),
|
|
2102
|
-
b.
|
|
2103
|
-
|
|
2104
|
-
|
|
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
|
+
}
|
|
2105
2230
|
: null;
|
|
2106
2231
|
|
|
2107
2232
|
const ordered_bindings = [...outer_bindings, ...loop_bindings];
|
|
@@ -2115,7 +2240,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2115
2240
|
const signature_use_typeof = has_tail ? [...ordered_use_typeof, false] : ordered_use_typeof;
|
|
2116
2241
|
|
|
2117
2242
|
const props_type =
|
|
2118
|
-
signature_bindings.length > 0
|
|
2243
|
+
signature_bindings.length > 0 && !use_module_scoped_component
|
|
2119
2244
|
? create_helper_props_type_literal_with_typeof_flags(
|
|
2120
2245
|
signature_bindings,
|
|
2121
2246
|
signature_aliases,
|
|
@@ -2123,7 +2248,13 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2123
2248
|
)
|
|
2124
2249
|
: null;
|
|
2125
2250
|
const params =
|
|
2126
|
-
|
|
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
|
+
: [];
|
|
2127
2258
|
|
|
2128
2259
|
const fn_saved_bindings = transform_context.available_bindings;
|
|
2129
2260
|
transform_context.available_bindings = new Map(fn_saved_bindings);
|
|
@@ -2141,12 +2272,17 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2141
2272
|
transform_context.available_bindings = fn_saved_bindings;
|
|
2142
2273
|
|
|
2143
2274
|
const helper_fn = /** @type {any} */ (
|
|
2144
|
-
b.function(clone_identifier(
|
|
2275
|
+
b.function(clone_identifier(component_id), params, b.block(fn_body_statements))
|
|
2145
2276
|
);
|
|
2146
2277
|
helper_fn.metadata = { path: [], is_component: true, is_method: false };
|
|
2147
2278
|
|
|
2148
2279
|
let helper_decl;
|
|
2149
|
-
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) {
|
|
2150
2286
|
const cache_id = create_generated_identifier(
|
|
2151
2287
|
`${transform_context.helper_state.base_name}__${helper_id.name}`,
|
|
2152
2288
|
);
|
|
@@ -2163,7 +2299,7 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2163
2299
|
transform_context.available_bindings = saved_bindings;
|
|
2164
2300
|
|
|
2165
2301
|
const callback_invocation_element = create_helper_component_element(
|
|
2166
|
-
|
|
2302
|
+
component_id,
|
|
2167
2303
|
ordered_bindings,
|
|
2168
2304
|
node,
|
|
2169
2305
|
{ mapWrapper: false, mapBindingNames: false, mapBindingValues: false },
|
|
@@ -2244,7 +2380,9 @@ function build_hoisted_for_of_with_hooks(node, continuation_body, transform_cont
|
|
|
2244
2380
|
if (has_tail && tail_isLast_alias) {
|
|
2245
2381
|
hoist_statements.push(tail_isLast_alias.declaration);
|
|
2246
2382
|
}
|
|
2247
|
-
|
|
2383
|
+
if (helper_decl) {
|
|
2384
|
+
hoist_statements.push(helper_decl);
|
|
2385
|
+
}
|
|
2248
2386
|
|
|
2249
2387
|
return {
|
|
2250
2388
|
hoist_statements,
|
|
@@ -2704,8 +2842,8 @@ function create_local_statement_component_name(transform_context) {
|
|
|
2704
2842
|
/**
|
|
2705
2843
|
* Wraps a list of body nodes into a component and returns
|
|
2706
2844
|
* statements that return `<ComponentName prop1={prop1} ... />`.
|
|
2707
|
-
*
|
|
2708
|
-
*
|
|
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.
|
|
2709
2847
|
* Used when a control flow branch contains hook calls that must be moved
|
|
2710
2848
|
* into their own component boundary to satisfy the Rules of Hooks.
|
|
2711
2849
|
*
|
|
@@ -2778,24 +2916,36 @@ function create_hook_safe_helper(
|
|
|
2778
2916
|
const helper_id =
|
|
2779
2917
|
preallocated_helper_id ??
|
|
2780
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;
|
|
2781
2923
|
const helper_bindings = get_referenced_helper_bindings(
|
|
2782
2924
|
body_nodes,
|
|
2783
2925
|
transform_context.available_bindings,
|
|
2784
2926
|
);
|
|
2785
|
-
const aliases =
|
|
2786
|
-
|
|
2787
|
-
|
|
2927
|
+
const aliases = use_module_scoped_component
|
|
2928
|
+
? []
|
|
2929
|
+
: helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
|
|
2788
2930
|
const props_type =
|
|
2789
|
-
helper_bindings.length > 0
|
|
2931
|
+
helper_bindings.length > 0 && !use_module_scoped_component
|
|
2932
|
+
? create_helper_props_type_literal(helper_bindings, aliases)
|
|
2933
|
+
: null;
|
|
2790
2934
|
const params =
|
|
2791
|
-
|
|
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
|
+
: [];
|
|
2792
2942
|
|
|
2793
2943
|
const saved_bindings = transform_context.available_bindings;
|
|
2794
2944
|
transform_context.available_bindings = new Map(saved_bindings);
|
|
2795
2945
|
|
|
2796
2946
|
const helper_fn = /** @type {any} */ ({
|
|
2797
2947
|
type: 'FunctionExpression',
|
|
2798
|
-
id: clone_identifier(
|
|
2948
|
+
id: clone_identifier(component_id),
|
|
2799
2949
|
params,
|
|
2800
2950
|
body: {
|
|
2801
2951
|
type: 'BlockStatement',
|
|
@@ -2814,7 +2964,7 @@ function create_hook_safe_helper(
|
|
|
2814
2964
|
transform_context.available_bindings = saved_bindings;
|
|
2815
2965
|
|
|
2816
2966
|
const component_element = create_helper_component_element(
|
|
2817
|
-
|
|
2967
|
+
component_id,
|
|
2818
2968
|
helper_bindings,
|
|
2819
2969
|
source_node,
|
|
2820
2970
|
{
|
|
@@ -2845,6 +2995,16 @@ function create_hook_safe_helper(
|
|
|
2845
2995
|
};
|
|
2846
2996
|
}
|
|
2847
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
|
+
|
|
2848
3008
|
const cache_id = create_generated_identifier(
|
|
2849
3009
|
`${transform_context.helper_state.base_name}__${helper_id.name}`,
|
|
2850
3010
|
);
|
|
@@ -4196,6 +4356,8 @@ function create_render_switch_case(switch_case, transform_context) {
|
|
|
4196
4356
|
|
|
4197
4357
|
if (is_jsx_child(child)) {
|
|
4198
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));
|
|
4199
4361
|
} else {
|
|
4200
4362
|
case_body.push(child);
|
|
4201
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
|
/**
|