@tsrx/core 0.1.24 → 0.1.26
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/prune.js +11 -4
- package/src/index.js +1 -0
- package/src/plugin.js +154 -2
- package/src/transform/jsx/ast-builders.js +27 -0
- package/src/transform/jsx/helpers.js +8 -1
- package/src/transform/jsx/index.js +381 -1514
- package/src/transform/jsx-hoist.js +6 -0
- package/src/transform/jsx-interleave.js +4 -0
- package/src/transform/scoping.js +8 -4
- package/src/transform/segments.js +31 -1
- package/types/index.d.ts +4 -4
- package/types/jsx-platform.d.ts +27 -13
- package/types/parse.d.ts +3 -1
|
@@ -112,6 +112,12 @@ export function is_hoist_safe_jsx_node(node) {
|
|
|
112
112
|
return node.children.every(is_hoist_safe_jsx_child);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Lowered dynamic tags reference a component-scoped const and resolve at
|
|
116
|
+
// runtime — never static, never hoistable.
|
|
117
|
+
if (/** @type {any} */ (node).metadata?.dynamicElement === true) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
return (
|
|
116
122
|
node.openingElement.attributes.every(is_hoist_safe_jsx_attribute) &&
|
|
117
123
|
node.children.every(is_hoist_safe_jsx_child)
|
|
@@ -43,6 +43,10 @@ export function is_interleaved_body(body_nodes, is_jsx_child) {
|
|
|
43
43
|
*/
|
|
44
44
|
export function is_capturable_jsx_child(jsx) {
|
|
45
45
|
if (!jsx) return false;
|
|
46
|
+
// Reactive-block containers (dynamic tags) must stay expression children
|
|
47
|
+
// so the host JSX compiler wraps them in a render block; capturing them
|
|
48
|
+
// into a const would evaluate them once.
|
|
49
|
+
if (jsx.metadata?.tsrx_reactive_block === true) return false;
|
|
46
50
|
const t = jsx.type;
|
|
47
51
|
return t === 'JSXElement' || t === 'JSXFragment' || t === 'JSXExpressionContainer';
|
|
48
52
|
}
|
package/src/transform/scoping.js
CHANGED
|
@@ -75,15 +75,19 @@ export function annotate_with_hash(
|
|
|
75
75
|
) {
|
|
76
76
|
if (!node || typeof node !== 'object') return node;
|
|
77
77
|
if (
|
|
78
|
-
node.type === 'FunctionDeclaration' ||
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
(node.type === 'FunctionDeclaration' ||
|
|
79
|
+
node.type === 'FunctionExpression' ||
|
|
80
|
+
node.type === 'ArrowFunctionExpression') &&
|
|
81
|
+
// Generated dynamic-tag wrappers are render-block closures, not user
|
|
82
|
+
// component boundaries — the element inside still belongs to this
|
|
83
|
+
// component's scoped CSS.
|
|
84
|
+
node.metadata?.tsrx_dynamic_wrapper !== true
|
|
81
85
|
) {
|
|
82
86
|
return node;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
if (node.type === 'JSXElement') {
|
|
86
|
-
if (!is_composite_jsx_element(node) || node.metadata?.
|
|
90
|
+
if (!is_composite_jsx_element(node) || node.metadata?.dynamicElement) {
|
|
87
91
|
add_hash_class_to_jsx_element(node, hash, jsx_class_attr_name);
|
|
88
92
|
}
|
|
89
93
|
if (Array.isArray(node.children)) {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
generated: string;
|
|
28
28
|
loc: AST.SourceLocation;
|
|
29
29
|
metadata: PluginActionOverrides;
|
|
30
|
+
generatedLoc?: AST.SourceLocation;
|
|
30
31
|
end_loc?: AST.SourceLocation;
|
|
31
32
|
sourceLength?: number;
|
|
32
33
|
mappingData?: Partial<VolarCodeMapping['data']>;
|
|
@@ -387,7 +388,8 @@ export function convert_source_map_to_mappings(
|
|
|
387
388
|
* @returns {{ line: number; column: number }}
|
|
388
389
|
*/
|
|
389
390
|
function get_generated_position_for_token(token) {
|
|
390
|
-
const
|
|
391
|
+
const generated_loc = token.generatedLoc ?? token.loc;
|
|
392
|
+
const key = `${generated_loc.start.line}:${generated_loc.start.column}`;
|
|
391
393
|
const positions = src_to_gen_map.get(key);
|
|
392
394
|
if (!positions || positions.length === 0) {
|
|
393
395
|
throw new Error(`No source map entry for position "${key}"`);
|
|
@@ -478,6 +480,32 @@ export function convert_source_map_to_mappings(
|
|
|
478
480
|
}
|
|
479
481
|
}
|
|
480
482
|
|
|
483
|
+
/**
|
|
484
|
+
* @param {any} generated_node
|
|
485
|
+
* @returns {void}
|
|
486
|
+
*/
|
|
487
|
+
function add_extra_source_mapping_tokens(generated_node) {
|
|
488
|
+
if (!generated_node?.loc || !Array.isArray(generated_node.metadata?.extra_source_mappings)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
for (const source_node of generated_node.metadata.extra_source_mappings) {
|
|
493
|
+
if (!source_node?.loc) continue;
|
|
494
|
+
|
|
495
|
+
tokens.push({
|
|
496
|
+
source: source_node.name ?? generated_node.name,
|
|
497
|
+
generated: generated_node.name,
|
|
498
|
+
loc: source_node.loc,
|
|
499
|
+
generatedLoc: generated_node.loc,
|
|
500
|
+
metadata: {},
|
|
501
|
+
sourceLength:
|
|
502
|
+
typeof source_node.start === 'number' && typeof source_node.end === 'number'
|
|
503
|
+
? source_node.end - source_node.start
|
|
504
|
+
: undefined,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
481
509
|
// We have to visit everything in generated order to maintain correct indices
|
|
482
510
|
|
|
483
511
|
walk(ast, null, {
|
|
@@ -521,6 +549,7 @@ export function convert_source_map_to_mappings(
|
|
|
521
549
|
token.mappingData = { ...mapping_data, verification: false };
|
|
522
550
|
}
|
|
523
551
|
tokens.push(token);
|
|
552
|
+
add_extra_source_mapping_tokens(node);
|
|
524
553
|
|
|
525
554
|
if (Array.isArray(node.metadata?.lazy_param_binding_mappings)) {
|
|
526
555
|
for (const binding_mapping of node.metadata.lazy_param_binding_mappings) {
|
|
@@ -558,6 +587,7 @@ export function convert_source_map_to_mappings(
|
|
|
558
587
|
token.mappingData = { ...mapping_data, verification: false };
|
|
559
588
|
}
|
|
560
589
|
tokens.push(token);
|
|
590
|
+
add_extra_source_mapping_tokens(node);
|
|
561
591
|
}
|
|
562
592
|
return; // Leaf node, don't traverse further
|
|
563
593
|
} else if (node.type === 'Literal') {
|
package/types/index.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ interface BaseNodeMetaData {
|
|
|
84
84
|
parenthesized?: boolean;
|
|
85
85
|
native_tsrx?: boolean;
|
|
86
86
|
native_tsrx_template_block?: boolean;
|
|
87
|
-
|
|
87
|
+
dynamicElement?: boolean;
|
|
88
88
|
templateMode?: 'script' | 'template';
|
|
89
89
|
script_only?: boolean;
|
|
90
90
|
tsrxDirective?: 'if' | 'for' | 'switch' | 'try';
|
|
@@ -118,7 +118,6 @@ interface BaseNodeMetaData {
|
|
|
118
118
|
interface FunctionMetaData extends BaseNodeMetaData {
|
|
119
119
|
native_tsrx?: boolean;
|
|
120
120
|
native_tsrx_function?: boolean;
|
|
121
|
-
hook_split?: boolean;
|
|
122
121
|
is_method?: boolean;
|
|
123
122
|
tracked?: boolean;
|
|
124
123
|
has_lazy_descendants?: boolean;
|
|
@@ -183,7 +182,6 @@ declare module 'estree' {
|
|
|
183
182
|
|
|
184
183
|
interface BlockStatement {
|
|
185
184
|
metadata: BaseNodeMetaData & {
|
|
186
|
-
hook_split_block?: boolean;
|
|
187
185
|
native_return_block?: boolean;
|
|
188
186
|
native_tsrx_template_block?: boolean;
|
|
189
187
|
allows_native_return?: boolean;
|
|
@@ -301,13 +299,14 @@ declare module 'estree' {
|
|
|
301
299
|
|
|
302
300
|
interface Element extends AST.BaseExpression {
|
|
303
301
|
type: 'Element';
|
|
304
|
-
id: AST.
|
|
302
|
+
id: AST.Expression;
|
|
305
303
|
attributes: Array<Attribute | SpreadAttribute>;
|
|
306
304
|
children: AST.Node[];
|
|
307
305
|
openingElement: ESTreeJSX.JSXOpeningElement;
|
|
308
306
|
closingElement: ESTreeJSX.JSXClosingElement | null;
|
|
309
307
|
selfClosing?: boolean;
|
|
310
308
|
unclosed?: boolean;
|
|
309
|
+
isDynamic?: boolean;
|
|
311
310
|
css?: string;
|
|
312
311
|
metadata: BaseNodeMetaData;
|
|
313
312
|
start: number;
|
|
@@ -729,6 +728,7 @@ declare module 'estree-jsx' {
|
|
|
729
728
|
interface JSXExpressionContainer {
|
|
730
729
|
text?: boolean;
|
|
731
730
|
style?: boolean;
|
|
731
|
+
isDynamic?: boolean;
|
|
732
732
|
}
|
|
733
733
|
|
|
734
734
|
interface JSXMemberExpression {
|
package/types/jsx-platform.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface JsxTransformContext {
|
|
|
39
39
|
needs_normalize_spread_props: boolean;
|
|
40
40
|
needs_normalize_spread_props_for_ref_attr: boolean;
|
|
41
41
|
needs_fragment: boolean;
|
|
42
|
+
needs_dynamic_element: boolean;
|
|
43
|
+
needs_dynamic_factory: boolean;
|
|
42
44
|
needs_for_of_iterable: boolean;
|
|
43
45
|
needs_iteration_value_type: boolean;
|
|
44
46
|
stylesheets: AST.CSS.StyleSheet[];
|
|
@@ -53,8 +55,6 @@ export interface JsxTransformContext {
|
|
|
53
55
|
hook_helpers_enabled: boolean;
|
|
54
56
|
available_bindings: Map<string, AST.Identifier>;
|
|
55
57
|
lazy_next_id: number;
|
|
56
|
-
/** Scope map used to resolve runtime Dynamic imports for scoped CSS pruning. */
|
|
57
|
-
runtime_dynamic_scopes: Map<any, any> | null;
|
|
58
58
|
inside_element_child?: boolean;
|
|
59
59
|
/** Full source text for source-aware diagnostics. */
|
|
60
60
|
source: string;
|
|
@@ -102,7 +102,7 @@ export interface JsxTransformOptions {
|
|
|
102
102
|
comments?: AST.CommentWithLocation[];
|
|
103
103
|
/**
|
|
104
104
|
* Override whether hook-isolation helper components are emitted directly at
|
|
105
|
-
* module scope.
|
|
105
|
+
* module scope. Some runtime targets enable this, while editor tooling
|
|
106
106
|
* can disable it to preserve lexical `typeof` helper prop types.
|
|
107
107
|
*/
|
|
108
108
|
moduleScopedHookComponents?: boolean;
|
|
@@ -160,15 +160,10 @@ export interface JsxPlatformHooks {
|
|
|
160
160
|
/**
|
|
161
161
|
* Emit hook-isolation helper components as unique module-scope declarations
|
|
162
162
|
* instead of lazily creating and caching them from the parent component body.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
163
|
+
* Targets that use compiler-generated branch helpers enable this so helper
|
|
164
|
+
* declarations are shared across renders.
|
|
165
165
|
*/
|
|
166
166
|
moduleScopedHookComponents?: boolean;
|
|
167
|
-
/**
|
|
168
|
-
* Split ordinary uppercase function component bodies when an early
|
|
169
|
-
* conditional return would make later React/Preact hooks conditional.
|
|
170
|
-
*/
|
|
171
|
-
componentBodyHookHelpers?: boolean;
|
|
172
167
|
/**
|
|
173
168
|
* Inject module-level imports after the main walk. Default: import
|
|
174
169
|
* `Suspense` from `platform.imports.suspense` and `TsrxErrorBoundary`
|
|
@@ -321,11 +316,30 @@ export interface JsxPlatform {
|
|
|
321
316
|
*/
|
|
322
317
|
suspense: string;
|
|
323
318
|
/**
|
|
324
|
-
* Module
|
|
325
|
-
*
|
|
326
|
-
*
|
|
319
|
+
* Module the type-only transform imports the `Dynamic` component type
|
|
320
|
+
* from when lowering dynamic tags to `<TsrxDynamic is={expr}>`. The
|
|
321
|
+
* module only needs type declarations; production output never imports
|
|
322
|
+
* it.
|
|
327
323
|
*/
|
|
328
324
|
dynamic?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Scoped-binding lowering for dynamic tags (`<{expr}>`) in production
|
|
327
|
+
* output; each tag binds a scoped component const referenced like an
|
|
328
|
+
* ordinary component. With `name`/`source`, the factory is imported as
|
|
329
|
+
* `_tsrx_dynamic` and wraps the tag expression in a reactive thunk:
|
|
330
|
+
* `const TsrxDynamic_N = _tsrx_dynamic(() => expr);` (Solid:
|
|
331
|
+
* `{ name: 'dynamic', source: '@solidjs/web' }`). With an empty object,
|
|
332
|
+
* the const is a plain import-free alias re-evaluated by the host's
|
|
333
|
+
* render cycle: `const TsrxDynamic_N = expr;` (React/Preact). With
|
|
334
|
+
* `renderBlock: true`, the alias and element move inside an
|
|
335
|
+
* expression-child IIFE,
|
|
336
|
+
* `{(() => { const TsrxDynamic_N = expr; return <TsrxDynamic_N ...>; })()}`,
|
|
337
|
+
* relying on the host JSX compiler's reactive render block for
|
|
338
|
+
* expression children (Vue Vapor, which never re-runs setup). In
|
|
339
|
+
* type-only output, dynamic tags always lower to
|
|
340
|
+
* `<TsrxDynamic is={expr}>` imported from `imports.dynamic`.
|
|
341
|
+
*/
|
|
342
|
+
dynamicFactory?: { name?: string; source?: string; renderBlock?: boolean };
|
|
329
343
|
/**
|
|
330
344
|
* Module to import `TsrxErrorBoundary` from when an `@try { ... } @catch (...)`
|
|
331
345
|
* block appears. Usually `'@tsrx/<platform>/error-boundary'`.
|
package/types/parse.d.ts
CHANGED
|
@@ -1621,11 +1621,13 @@ export namespace Parse {
|
|
|
1621
1621
|
| ReturnType<Parser['jsx_parseIdentifier']>;
|
|
1622
1622
|
|
|
1623
1623
|
/**
|
|
1624
|
-
* Parse JSX element name (identifier, member, namespaced
|
|
1624
|
+
* Parse JSX element name (identifier, member, namespaced, or a dynamic
|
|
1625
|
+
* `{expression}` container)
|
|
1625
1626
|
*/
|
|
1626
1627
|
jsx_parseElementName():
|
|
1627
1628
|
| ESTreeJSX.JSXMemberExpression
|
|
1628
1629
|
| ReturnType<Parser['jsx_parseNamespacedName']>
|
|
1630
|
+
| ESTreeJSX.JSXExpressionContainer
|
|
1629
1631
|
| '';
|
|
1630
1632
|
|
|
1631
1633
|
/**
|