@tsrx/core 0.0.27 → 0.0.28
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 +5 -4
- package/src/index.js +6 -0
- package/src/plugin.js +16 -1
- package/src/runtime/language-helpers.js +57 -0
- package/src/runtime/ref.js +250 -0
- package/src/transform/jsx/ast-builders.js +13 -13
- package/src/transform/jsx/index.js +424 -55
- package/src/transform/segments.js +19 -9
- package/types/index.d.ts +8 -0
- package/types/jsx-platform.d.ts +14 -1
- package/types/runtime/ref.d.ts +32 -0
- package/src/runtime/merge-refs.js +0 -61
- package/types/runtime/merge-refs.d.ts +0 -12
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.28",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
"./types/acorn": {
|
|
28
28
|
"types": "./types/acorn.d.ts"
|
|
29
29
|
},
|
|
30
|
-
"./runtime/
|
|
31
|
-
"types": "./types/runtime/
|
|
32
|
-
"default": "./src/runtime/
|
|
30
|
+
"./runtime/ref": {
|
|
31
|
+
"types": "./types/runtime/ref.d.ts",
|
|
32
|
+
"default": "./src/runtime/ref.js"
|
|
33
33
|
},
|
|
34
|
+
"./runtime/*": "./src/runtime/*.js",
|
|
34
35
|
"./test-harness/source-mappings": "./tests/shared/source-mappings.js",
|
|
35
36
|
"./test-harness/compile": "./tests/shared/compile.js"
|
|
36
37
|
},
|
package/src/index.js
CHANGED
|
@@ -141,8 +141,14 @@ export { escape, escape_script as escapeScript } from './utils/escaping.js';
|
|
|
141
141
|
|
|
142
142
|
// Transform
|
|
143
143
|
export {
|
|
144
|
+
add_jsx_setup_declaration as addJsxSetupDeclaration,
|
|
144
145
|
createJsxTransform,
|
|
146
|
+
CREATE_REF_PROP_INTERNAL_NAME,
|
|
147
|
+
extract_jsx_setup_declarations as extractJsxSetupDeclarations,
|
|
148
|
+
is_ref_prop_expression as isRefPropExpression,
|
|
149
|
+
MERGE_REFS_INTERNAL_NAME,
|
|
145
150
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
151
|
+
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
146
152
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
147
153
|
to_jsx_attribute as toJsxAttribute,
|
|
148
154
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
package/src/plugin.js
CHANGED
|
@@ -1320,7 +1320,7 @@ export function TSRXPlugin(config) {
|
|
|
1320
1320
|
if (!nextChars) {
|
|
1321
1321
|
this.raise(
|
|
1322
1322
|
ref.start,
|
|
1323
|
-
'"component" is a
|
|
1323
|
+
'"component" is a TSRX keyword and cannot be used as an identifier',
|
|
1324
1324
|
);
|
|
1325
1325
|
}
|
|
1326
1326
|
}
|
|
@@ -1345,6 +1345,21 @@ export function TSRXPlugin(config) {
|
|
|
1345
1345
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1346
1346
|
this.next();
|
|
1347
1347
|
|
|
1348
|
+
if (this.type === tt.name && this.value === 'ref') {
|
|
1349
|
+
const ref_node = /** @type {AST.RefExpression} */ (this.startNode());
|
|
1350
|
+
this.next();
|
|
1351
|
+
if (this.type === tt.braceR) {
|
|
1352
|
+
this.raise(
|
|
1353
|
+
this.start,
|
|
1354
|
+
'"ref" is a TSRX keyword and must be used in the form {ref item}',
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
ref_node.argument = this.parseMaybeAssign();
|
|
1358
|
+
node.expression = /** @type {any} */ (this.finishNode(ref_node, 'RefExpression'));
|
|
1359
|
+
this.expect(tt.braceR);
|
|
1360
|
+
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1348
1363
|
if (this.type === tt.name && this.value === 'html') {
|
|
1349
1364
|
node.html = true;
|
|
1350
1365
|
this.next();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** @type {typeof Object.getOwnPropertyDescriptor} */
|
|
2
|
+
export var get_descriptor = Object.getOwnPropertyDescriptor;
|
|
3
|
+
/** @type {typeof Object.getOwnPropertyDescriptors} */
|
|
4
|
+
export var get_descriptors = Object.getOwnPropertyDescriptors;
|
|
5
|
+
/** @type {typeof Array.from} */
|
|
6
|
+
export var array_from = Array.from;
|
|
7
|
+
/** @type {typeof Array.isArray} */
|
|
8
|
+
export var is_array = Array.isArray;
|
|
9
|
+
/** @type {typeof Object.defineProperty} */
|
|
10
|
+
export var define_property = Object.defineProperty;
|
|
11
|
+
/** @type {typeof Object.getPrototypeOf} */
|
|
12
|
+
export var get_prototype_of = Object.getPrototypeOf;
|
|
13
|
+
/** @type {typeof Object.values} */
|
|
14
|
+
export var object_values = Object.values;
|
|
15
|
+
/** @type {typeof Object.entries} */
|
|
16
|
+
export var object_entries = Object.entries;
|
|
17
|
+
/** @type {typeof Object.keys} */
|
|
18
|
+
export var object_keys = Object.keys;
|
|
19
|
+
/** @type {typeof Object.getOwnPropertySymbols} */
|
|
20
|
+
export var get_own_property_symbols = Object.getOwnPropertySymbols;
|
|
21
|
+
/** @type {typeof structuredClone} */
|
|
22
|
+
export var structured_clone = structuredClone;
|
|
23
|
+
/** @type {typeof Object.prototype} */
|
|
24
|
+
export var object_prototype = Object.prototype;
|
|
25
|
+
/** @type {typeof Array.prototype} */
|
|
26
|
+
export var array_prototype = Array.prototype;
|
|
27
|
+
/** @type {typeof Object.prototype.hasOwnProperty} */
|
|
28
|
+
export var has_own_property = object_prototype.hasOwnProperty;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} value
|
|
32
|
+
* @param {PropertyKey} key
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
export function has_prototype_accessor(value, key) {
|
|
36
|
+
var proto = get_prototype_of(value);
|
|
37
|
+
while (proto != null) {
|
|
38
|
+
var descriptor = get_descriptor(proto, key);
|
|
39
|
+
if (descriptor !== undefined) {
|
|
40
|
+
return typeof descriptor.get === 'function' || typeof descriptor.set === 'function';
|
|
41
|
+
}
|
|
42
|
+
proto = get_prototype_of(proto);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Slice helper for arrays and array-like values.
|
|
49
|
+
* @param {ArrayLike<any>} array_like
|
|
50
|
+
* @param {...number} args
|
|
51
|
+
* @returns {any[]}
|
|
52
|
+
*/
|
|
53
|
+
export function array_slice(array_like, ...args) {
|
|
54
|
+
return is_array(array_like)
|
|
55
|
+
? array_like.slice(...args)
|
|
56
|
+
: array_prototype.slice.call(array_like, ...args);
|
|
57
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
has_own_property,
|
|
3
|
+
get_descriptor,
|
|
4
|
+
has_prototype_accessor,
|
|
5
|
+
} from '@tsrx/core/runtime/language-helpers';
|
|
6
|
+
|
|
7
|
+
const REF_VALUE = Symbol();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merge multiple refs (function refs and ref objects) into a single
|
|
11
|
+
* callback ref. Used by React, Preact, and Vue targets when an element has
|
|
12
|
+
* more than one `ref` attribute.
|
|
13
|
+
* This is a public method and also used by the compiler to unite any refs with
|
|
14
|
+
* any of the supported syntaxes. It does not process spreads, that is delegated to
|
|
15
|
+
* `normalize_spread_props`.
|
|
16
|
+
*
|
|
17
|
+
* @param {...((node: any) => void | (() => void)) | { current: any } | { value: any } | null | undefined} refs
|
|
18
|
+
* @returns {(node: any) => (() => void)}
|
|
19
|
+
*/
|
|
20
|
+
export function mergeRefs(...refs) {
|
|
21
|
+
return (node) => {
|
|
22
|
+
/** @type {Array<() => void>} */
|
|
23
|
+
const cleanups = [];
|
|
24
|
+
for (const ref of refs) {
|
|
25
|
+
if (ref == null) continue;
|
|
26
|
+
if (typeof ref === 'function') {
|
|
27
|
+
const result = ref(node);
|
|
28
|
+
if (typeof result === 'function') {
|
|
29
|
+
cleanups.push(result);
|
|
30
|
+
} else {
|
|
31
|
+
cleanups.push(() => ref(null));
|
|
32
|
+
}
|
|
33
|
+
} else if (is_ref_object(ref, 'current')) {
|
|
34
|
+
/** @type {{ current: any }} */ (ref).current = node;
|
|
35
|
+
cleanups.push(() => {
|
|
36
|
+
/** @type {{ current: any }} */ (ref).current = null;
|
|
37
|
+
});
|
|
38
|
+
} else if (is_ref_object(ref, 'value')) {
|
|
39
|
+
/** @type {{ value: any }} */ (ref).value = node;
|
|
40
|
+
cleanups.push(() => {
|
|
41
|
+
/** @type {{ value: any }} */ (ref).value = null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return () => {
|
|
46
|
+
for (const cleanup of cleanups) cleanup();
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { is_ref_prop as isRefProp };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} value
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function is_ref_prop(value) {
|
|
58
|
+
return typeof value === 'function' && REF_VALUE in value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {any} ref_value
|
|
63
|
+
* @param {any} node
|
|
64
|
+
* @param {(value: any) => void} [set_ref_value]
|
|
65
|
+
* @returns {void | (() => void)}
|
|
66
|
+
*/
|
|
67
|
+
export function apply_ref_value(ref_value, node, set_ref_value) {
|
|
68
|
+
if (typeof ref_value === 'function') {
|
|
69
|
+
return ref_value(node);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (ref_value && typeof ref_value === 'object') {
|
|
73
|
+
if (is_ref_object(ref_value, 'current')) {
|
|
74
|
+
ref_value.current = node;
|
|
75
|
+
return () => {
|
|
76
|
+
ref_value.current = null;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (is_ref_object(ref_value, 'value')) {
|
|
81
|
+
ref_value.value = node;
|
|
82
|
+
return () => {
|
|
83
|
+
ref_value.value = null;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (set_ref_value !== undefined) {
|
|
89
|
+
set_ref_value(node);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {() => any} get_ref_value
|
|
95
|
+
* @param {(value: any) => void} [set_ref_value]
|
|
96
|
+
* @returns {(node: any) => void | (() => void)}
|
|
97
|
+
*/
|
|
98
|
+
export function create_ref_prop(get_ref_value, set_ref_value) {
|
|
99
|
+
/**
|
|
100
|
+
* @param {any} node
|
|
101
|
+
* @returns {void | (() => void)}
|
|
102
|
+
*/
|
|
103
|
+
function ref_prop_callback(node) {
|
|
104
|
+
const ref_value = get_ref_value();
|
|
105
|
+
const cleanup = apply_ref_value(ref_value, node, set_ref_value);
|
|
106
|
+
if (typeof cleanup === 'function' || node === null) {
|
|
107
|
+
return cleanup;
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
apply_ref_value(ref_value, null, set_ref_value);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Object.defineProperty(ref_prop_callback, REF_VALUE, {
|
|
115
|
+
value: 'ref_value',
|
|
116
|
+
enumerable: false,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return ref_prop_callback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {...any} refs
|
|
124
|
+
* @returns {any}
|
|
125
|
+
*/
|
|
126
|
+
export function merge_ref_props(...refs) {
|
|
127
|
+
const filtered = refs.filter((ref) => ref != null);
|
|
128
|
+
|
|
129
|
+
if (filtered.length === 0) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (filtered.length === 1) {
|
|
134
|
+
return filtered[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {any} node
|
|
139
|
+
* @returns {void | (() => void)}
|
|
140
|
+
*/
|
|
141
|
+
function merged_ref_prop(node) {
|
|
142
|
+
/** @type {Array<() => void>} */
|
|
143
|
+
const cleanups = [];
|
|
144
|
+
|
|
145
|
+
for (const ref of filtered) {
|
|
146
|
+
const cleanup = apply_ref_value(ref, node);
|
|
147
|
+
if (typeof cleanup === 'function') {
|
|
148
|
+
cleanups.push(cleanup);
|
|
149
|
+
} else if (typeof ref === 'function' && node !== null) {
|
|
150
|
+
cleanups.push(() => ref(null));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
for (const cleanup of cleanups) {
|
|
156
|
+
cleanup();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return merged_ref_prop;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {Record<string | symbol, any> | null | undefined} props
|
|
166
|
+
* @param {...any} outer_refs
|
|
167
|
+
* @returns {Record<string | symbol, any> | null | undefined}
|
|
168
|
+
*/
|
|
169
|
+
export function normalize_spread_props(props, ...outer_refs) {
|
|
170
|
+
if (props == null) {
|
|
171
|
+
return props;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @type {any[]} */
|
|
175
|
+
const refs = [];
|
|
176
|
+
/** @type {Record<string | symbol, any>} */
|
|
177
|
+
let next = {};
|
|
178
|
+
let changed = false;
|
|
179
|
+
let existing_ref;
|
|
180
|
+
|
|
181
|
+
for (const key of Reflect.ownKeys(props)) {
|
|
182
|
+
const descriptor = get_descriptor(props, key);
|
|
183
|
+
if (!descriptor?.enumerable) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const value = /** @type {any} */ (props)[key];
|
|
188
|
+
|
|
189
|
+
if (key === 'ref') {
|
|
190
|
+
if (is_ref_prop(value)) {
|
|
191
|
+
refs.push(value);
|
|
192
|
+
changed = true;
|
|
193
|
+
} else {
|
|
194
|
+
existing_ref = value;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (is_ref_prop(value)) {
|
|
200
|
+
refs.push(value);
|
|
201
|
+
changed = true;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
next[key] = value;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!changed && outer_refs.length === 0) {
|
|
209
|
+
return props;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const merged_ref = merge_ref_props(existing_ref, ...refs, ...outer_refs);
|
|
213
|
+
if (merged_ref !== undefined) {
|
|
214
|
+
next.ref = merged_ref;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return next;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {object} value
|
|
222
|
+
* @param {'current' | 'value'} key
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
function is_ref_object(value, key) {
|
|
226
|
+
if (is_dom_node(value)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (key === 'value' && '__v_isRef' in value) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (has_own_property.call(value, key)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return key === 'value' && has_prototype_accessor(value, 'value');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {object} value
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
function is_dom_node(value) {
|
|
243
|
+
return (
|
|
244
|
+
(typeof Node !== 'undefined' && value instanceof Node) ||
|
|
245
|
+
('nodeType' in value &&
|
|
246
|
+
typeof (/** @type {{ nodeType?: unknown }} */ (value).nodeType) === 'number' &&
|
|
247
|
+
'nodeName' in value &&
|
|
248
|
+
typeof (/** @type {{ nodeName?: unknown }} */ (value).nodeName) === 'string')
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -143,7 +143,7 @@ export function identifier_to_jsx_name(id) {
|
|
|
143
143
|
/** @type {any} */ ({
|
|
144
144
|
type: 'JSXIdentifier',
|
|
145
145
|
name: id.name,
|
|
146
|
-
metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
146
|
+
metadata: { ...(id.metadata || {}), path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
147
147
|
}),
|
|
148
148
|
id,
|
|
149
149
|
);
|
|
@@ -376,26 +376,26 @@ export function to_text_expression(expression, source_node = expression) {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
/**
|
|
379
|
-
* Deep-clone an AST subtree.
|
|
380
|
-
* reference rather than recursed into — `loc` objects can contain back-refs
|
|
381
|
-
* to sub-objects that would blow the stack with a naive deep clone, and
|
|
382
|
-
* every other traversal in the targets treats these positional keys as
|
|
383
|
-
* shared.
|
|
379
|
+
* Deep-clone an AST subtree.
|
|
384
380
|
*
|
|
385
381
|
* @param {any} node
|
|
382
|
+
* @param {boolean} with_locations
|
|
386
383
|
* @returns {any}
|
|
387
384
|
*/
|
|
388
|
-
export function clone_expression_node(node) {
|
|
385
|
+
export function clone_expression_node(node, with_locations = true) {
|
|
389
386
|
if (!node || typeof node !== 'object') return node;
|
|
390
|
-
if (Array.isArray(node)) return node.map(clone_expression_node);
|
|
391
|
-
const clone = {
|
|
392
|
-
|
|
393
|
-
|
|
387
|
+
if (Array.isArray(node)) return node.map((child) => clone_expression_node(child, with_locations));
|
|
388
|
+
const clone = /** @type {Record<string, any>} */ ({});
|
|
389
|
+
|
|
390
|
+
for (const key of Object.keys(node)) {
|
|
391
|
+
if (!with_locations && (key === 'loc' || key === 'start' || key === 'end')) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
394
|
if (key === 'metadata') {
|
|
395
|
-
clone.metadata =
|
|
395
|
+
clone.metadata = node.metadata ? { ...node.metadata } : { path: [] };
|
|
396
396
|
continue;
|
|
397
397
|
}
|
|
398
|
-
clone[key] = clone_expression_node(
|
|
398
|
+
clone[key] = clone_expression_node(node[key], with_locations);
|
|
399
399
|
}
|
|
400
400
|
return clone;
|
|
401
401
|
}
|
|
@@ -169,6 +169,8 @@ export function createJsxTransform(platform) {
|
|
|
169
169
|
needs_error_boundary: false,
|
|
170
170
|
needs_suspense: false,
|
|
171
171
|
needs_merge_refs: false,
|
|
172
|
+
needs_ref_prop: false,
|
|
173
|
+
needs_normalize_spread_props: false,
|
|
172
174
|
needs_fragment: false,
|
|
173
175
|
module_scoped_hook_components:
|
|
174
176
|
options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
|
|
@@ -382,13 +384,20 @@ export function createJsxTransform(platform) {
|
|
|
382
384
|
|
|
383
385
|
Tsx(node, { next, path }) {
|
|
384
386
|
const inner = /** @type {any} */ (next() ?? node);
|
|
385
|
-
|
|
387
|
+
const in_jsx_child = in_jsx_child_context(path);
|
|
388
|
+
return /** @type {any} */ (
|
|
389
|
+
wrap_jsx_setup_declarations(tsx_node_to_jsx_expression(inner, in_jsx_child), in_jsx_child)
|
|
390
|
+
);
|
|
386
391
|
},
|
|
387
392
|
|
|
388
393
|
TsxCompat(node, { next, path, state }) {
|
|
389
394
|
const inner = /** @type {any} */ (next() ?? node);
|
|
395
|
+
const in_jsx_child = in_jsx_child_context(path);
|
|
390
396
|
return /** @type {any} */ (
|
|
391
|
-
|
|
397
|
+
wrap_jsx_setup_declarations(
|
|
398
|
+
tsx_compat_node_to_jsx_expression(inner, state, in_jsx_child),
|
|
399
|
+
in_jsx_child,
|
|
400
|
+
)
|
|
392
401
|
);
|
|
393
402
|
},
|
|
394
403
|
|
|
@@ -432,6 +441,27 @@ export function createJsxTransform(platform) {
|
|
|
432
441
|
FunctionDeclaration: ensure_function_metadata,
|
|
433
442
|
FunctionExpression: ensure_function_metadata,
|
|
434
443
|
ArrowFunctionExpression: ensure_function_metadata,
|
|
444
|
+
|
|
445
|
+
RefExpression(node) {
|
|
446
|
+
return create_ref_prop_call(node, transform_context);
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
JSXOpeningElement(node, { next }) {
|
|
450
|
+
const visited = next() || node;
|
|
451
|
+
const is_component = is_component_like_jsx_name(visited.name);
|
|
452
|
+
const attrs = normalize_named_ref_attributes(
|
|
453
|
+
visited.attributes || [],
|
|
454
|
+
!is_component,
|
|
455
|
+
transform_context,
|
|
456
|
+
);
|
|
457
|
+
return {
|
|
458
|
+
...visited,
|
|
459
|
+
attributes: merge_duplicate_refs(
|
|
460
|
+
normalize_host_ref_spreads(attrs, !is_component, transform_context),
|
|
461
|
+
transform_context,
|
|
462
|
+
),
|
|
463
|
+
};
|
|
464
|
+
},
|
|
435
465
|
});
|
|
436
466
|
|
|
437
467
|
const expanded = expand_component_helpers(/** @type {AST.Program} */ (transformed));
|
|
@@ -906,6 +936,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
906
936
|
|
|
907
937
|
if (is_jsx_child(child)) {
|
|
908
938
|
const jsx = to_jsx_child(child, transform_context);
|
|
939
|
+
statements.push(...extract_jsx_setup_declarations(jsx));
|
|
909
940
|
if (interleaved && is_capturable_jsx_child(jsx)) {
|
|
910
941
|
const { declaration, reference } = captureJsxChild(jsx, capture_index++);
|
|
911
942
|
statements.push(declaration);
|
|
@@ -1576,9 +1607,7 @@ function create_component_return_statement(
|
|
|
1576
1607
|
map_render_node_locations = true,
|
|
1577
1608
|
) {
|
|
1578
1609
|
const cloned = render_nodes.map((node) =>
|
|
1579
|
-
map_render_node_locations
|
|
1580
|
-
? clone_expression_node(node)
|
|
1581
|
-
: clone_expression_node_without_locations(node),
|
|
1610
|
+
map_render_node_locations ? clone_expression_node(node) : clone_expression_node(node, false),
|
|
1582
1611
|
);
|
|
1583
1612
|
|
|
1584
1613
|
return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
|
|
@@ -1645,7 +1674,7 @@ function build_tail_helper(continuation_body, source_node, transform_context) {
|
|
|
1645
1674
|
* @returns {any}
|
|
1646
1675
|
*/
|
|
1647
1676
|
function clone_tail_invocation(tail_helper) {
|
|
1648
|
-
return
|
|
1677
|
+
return clone_expression_node(tail_helper.component_element, false);
|
|
1649
1678
|
}
|
|
1650
1679
|
|
|
1651
1680
|
/**
|
|
@@ -2553,7 +2582,7 @@ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nes
|
|
|
2553
2582
|
* @returns {any}
|
|
2554
2583
|
*/
|
|
2555
2584
|
function combine_render_return_argument(render_nodes, return_argument) {
|
|
2556
|
-
const combined = render_nodes.map((node) =>
|
|
2585
|
+
const combined = render_nodes.map((node) => clone_expression_node(node, false));
|
|
2557
2586
|
|
|
2558
2587
|
if (return_argument != null && !is_null_literal(return_argument)) {
|
|
2559
2588
|
combined.push(return_argument_to_render_node(return_argument));
|
|
@@ -2586,30 +2615,6 @@ function is_null_literal(node) {
|
|
|
2586
2615
|
return node?.type === 'Literal' && node.value == null;
|
|
2587
2616
|
}
|
|
2588
2617
|
|
|
2589
|
-
/**
|
|
2590
|
-
* @param {any} node
|
|
2591
|
-
* @returns {any}
|
|
2592
|
-
*/
|
|
2593
|
-
function clone_expression_node_without_locations(node) {
|
|
2594
|
-
if (!node || typeof node !== 'object') return node;
|
|
2595
|
-
if (Array.isArray(node)) return node.map(clone_expression_node_without_locations);
|
|
2596
|
-
|
|
2597
|
-
const clone = { ...node };
|
|
2598
|
-
delete clone.loc;
|
|
2599
|
-
delete clone.start;
|
|
2600
|
-
delete clone.end;
|
|
2601
|
-
|
|
2602
|
-
for (const key of Object.keys(clone)) {
|
|
2603
|
-
if (key === 'metadata') {
|
|
2604
|
-
clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
|
|
2605
|
-
continue;
|
|
2606
|
-
}
|
|
2607
|
-
clone[key] = clone_expression_node_without_locations(clone[key]);
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
return clone;
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
2618
|
const TEMPLATE_FRAGMENT_ERROR =
|
|
2614
2619
|
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
2615
2620
|
|
|
@@ -4201,30 +4206,79 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
4201
4206
|
});
|
|
4202
4207
|
}
|
|
4203
4208
|
|
|
4204
|
-
|
|
4205
|
-
|
|
4209
|
+
const merge_refs_source =
|
|
4210
|
+
transform_context.needs_merge_refs && platform.imports.mergeRefs
|
|
4211
|
+
? platform.imports.mergeRefs
|
|
4212
|
+
: null;
|
|
4213
|
+
const ref_prop_source =
|
|
4214
|
+
transform_context.needs_ref_prop && platform.imports.refProp ? platform.imports.refProp : null;
|
|
4215
|
+
const normalize_spread_props_source =
|
|
4216
|
+
transform_context.needs_normalize_spread_props && platform.imports.refProp
|
|
4217
|
+
? platform.imports.refProp
|
|
4218
|
+
: null;
|
|
4219
|
+
|
|
4220
|
+
/** @type {Map<string, any[]>} */
|
|
4221
|
+
const ref_imports = new Map();
|
|
4222
|
+
|
|
4223
|
+
if (merge_refs_source !== null) {
|
|
4224
|
+
add_ref_import_specifier(ref_imports, merge_refs_source, {
|
|
4225
|
+
type: 'ImportSpecifier',
|
|
4226
|
+
imported: {
|
|
4227
|
+
type: 'Identifier',
|
|
4228
|
+
name: 'mergeRefs',
|
|
4229
|
+
metadata: { path: [] },
|
|
4230
|
+
},
|
|
4231
|
+
local: {
|
|
4232
|
+
type: 'Identifier',
|
|
4233
|
+
name: MERGE_REFS_INTERNAL_NAME,
|
|
4234
|
+
metadata: { path: [] },
|
|
4235
|
+
},
|
|
4236
|
+
metadata: { path: [] },
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
if (ref_prop_source !== null) {
|
|
4241
|
+
add_ref_import_specifier(ref_imports, ref_prop_source, {
|
|
4242
|
+
type: 'ImportSpecifier',
|
|
4243
|
+
imported: {
|
|
4244
|
+
type: 'Identifier',
|
|
4245
|
+
name: 'create_ref_prop',
|
|
4246
|
+
metadata: { path: [] },
|
|
4247
|
+
},
|
|
4248
|
+
local: {
|
|
4249
|
+
type: 'Identifier',
|
|
4250
|
+
name: CREATE_REF_PROP_INTERNAL_NAME,
|
|
4251
|
+
metadata: { path: [] },
|
|
4252
|
+
},
|
|
4253
|
+
metadata: { path: [] },
|
|
4254
|
+
});
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
if (normalize_spread_props_source !== null) {
|
|
4258
|
+
add_ref_import_specifier(ref_imports, normalize_spread_props_source, {
|
|
4259
|
+
type: 'ImportSpecifier',
|
|
4260
|
+
imported: {
|
|
4261
|
+
type: 'Identifier',
|
|
4262
|
+
name: 'normalize_spread_props',
|
|
4263
|
+
metadata: { path: [] },
|
|
4264
|
+
},
|
|
4265
|
+
local: {
|
|
4266
|
+
type: 'Identifier',
|
|
4267
|
+
name: NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
4268
|
+
metadata: { path: [] },
|
|
4269
|
+
},
|
|
4270
|
+
metadata: { path: [] },
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
for (const [source, ref_specifiers] of ref_imports) {
|
|
4206
4275
|
imports.push({
|
|
4207
4276
|
type: 'ImportDeclaration',
|
|
4208
|
-
specifiers:
|
|
4209
|
-
{
|
|
4210
|
-
type: 'ImportSpecifier',
|
|
4211
|
-
imported: {
|
|
4212
|
-
type: 'Identifier',
|
|
4213
|
-
name: 'mergeRefs',
|
|
4214
|
-
metadata: { path: [] },
|
|
4215
|
-
},
|
|
4216
|
-
local: {
|
|
4217
|
-
type: 'Identifier',
|
|
4218
|
-
name: MERGE_REFS_LOCAL_NAME,
|
|
4219
|
-
metadata: { path: [] },
|
|
4220
|
-
},
|
|
4221
|
-
metadata: { path: [] },
|
|
4222
|
-
},
|
|
4223
|
-
],
|
|
4277
|
+
specifiers: ref_specifiers,
|
|
4224
4278
|
source: {
|
|
4225
4279
|
type: 'Literal',
|
|
4226
|
-
value:
|
|
4227
|
-
raw: `'${
|
|
4280
|
+
value: source,
|
|
4281
|
+
raw: `'${source}'`,
|
|
4228
4282
|
},
|
|
4229
4283
|
metadata: { path: [] },
|
|
4230
4284
|
});
|
|
@@ -4235,6 +4289,20 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
|
|
|
4235
4289
|
}
|
|
4236
4290
|
}
|
|
4237
4291
|
|
|
4292
|
+
/**
|
|
4293
|
+
* @param {Map<string, any[]>} imports
|
|
4294
|
+
* @param {string} source
|
|
4295
|
+
* @param {any} specifier
|
|
4296
|
+
*/
|
|
4297
|
+
function add_ref_import_specifier(imports, source, specifier) {
|
|
4298
|
+
const specifiers = imports.get(source);
|
|
4299
|
+
if (specifiers) {
|
|
4300
|
+
specifiers.push(specifier);
|
|
4301
|
+
} else {
|
|
4302
|
+
imports.set(source, [specifier]);
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4238
4306
|
/**
|
|
4239
4307
|
* @param {any} node
|
|
4240
4308
|
* @param {TransformContext} transform_context
|
|
@@ -4421,6 +4489,8 @@ function to_jsx_expression_container(expression, source_node = expression) {
|
|
|
4421
4489
|
*/
|
|
4422
4490
|
function transform_element_attributes_dispatch(attrs, transform_context, element) {
|
|
4423
4491
|
validate_at_most_one_ref_attribute(attrs, transform_context);
|
|
4492
|
+
const is_component = is_component_like_element(element);
|
|
4493
|
+
attrs = normalize_named_ref_attributes(attrs, !is_component, transform_context);
|
|
4424
4494
|
const preprocess = transform_context.platform.hooks?.preprocessElementAttributes;
|
|
4425
4495
|
if (preprocess) {
|
|
4426
4496
|
attrs = preprocess(attrs, transform_context, element);
|
|
@@ -4429,7 +4499,238 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
|
|
|
4429
4499
|
const result = hook
|
|
4430
4500
|
? hook(attrs, transform_context, element)
|
|
4431
4501
|
: attrs.map((/** @type {any} */ a) => to_jsx_attribute(a, transform_context));
|
|
4432
|
-
return merge_duplicate_refs(
|
|
4502
|
+
return merge_duplicate_refs(
|
|
4503
|
+
normalize_host_ref_spreads(result, !is_component, transform_context),
|
|
4504
|
+
transform_context,
|
|
4505
|
+
);
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
/**
|
|
4509
|
+
* @param {any} element
|
|
4510
|
+
* @returns {boolean}
|
|
4511
|
+
*/
|
|
4512
|
+
function is_component_like_element(element) {
|
|
4513
|
+
const id = element?.id;
|
|
4514
|
+
if (!id) return false;
|
|
4515
|
+
if (id.type === 'Identifier') return /^[A-Z]/.test(id.name);
|
|
4516
|
+
if (id.type === 'JSXIdentifier') return /^[A-Z]/.test(id.name);
|
|
4517
|
+
if (id.type === 'MemberExpression') return true;
|
|
4518
|
+
if (id.type === 'JSXMemberExpression') return true;
|
|
4519
|
+
return false;
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
/**
|
|
4523
|
+
* @param {any} name
|
|
4524
|
+
* @returns {boolean}
|
|
4525
|
+
*/
|
|
4526
|
+
function is_component_like_jsx_name(name) {
|
|
4527
|
+
if (!name) return false;
|
|
4528
|
+
if (name.type === 'JSXIdentifier') return /^[A-Z]/.test(name.name);
|
|
4529
|
+
if (name.type === 'JSXMemberExpression') return true;
|
|
4530
|
+
return false;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
/**
|
|
4534
|
+
* @param {any[]} attrs
|
|
4535
|
+
* @param {boolean} is_host
|
|
4536
|
+
* @param {TransformContext} transform_context
|
|
4537
|
+
* @returns {any[]}
|
|
4538
|
+
*/
|
|
4539
|
+
function normalize_named_ref_attributes(attrs, is_host, transform_context) {
|
|
4540
|
+
if (!is_host) return attrs;
|
|
4541
|
+
|
|
4542
|
+
return attrs.map((attr) => {
|
|
4543
|
+
if (!is_named_ref_attribute(attr)) {
|
|
4544
|
+
return attr;
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
if (transform_context.typeOnly) {
|
|
4548
|
+
return mark_type_only_named_ref_attribute(attr);
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
return {
|
|
4552
|
+
...attr,
|
|
4553
|
+
metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
|
|
4554
|
+
name:
|
|
4555
|
+
attr.name?.type === 'JSXIdentifier'
|
|
4556
|
+
? { ...attr.name, name: 'ref' }
|
|
4557
|
+
: { type: 'Identifier', name: 'ref', metadata: { path: [] } },
|
|
4558
|
+
};
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
/**
|
|
4563
|
+
* @param {any} attr
|
|
4564
|
+
* @returns {any}
|
|
4565
|
+
*/
|
|
4566
|
+
function mark_type_only_named_ref_attribute(attr) {
|
|
4567
|
+
return {
|
|
4568
|
+
...attr,
|
|
4569
|
+
name: attr.name
|
|
4570
|
+
? {
|
|
4571
|
+
...attr.name,
|
|
4572
|
+
metadata: { ...(attr.name.metadata || {}), disable_verification: true },
|
|
4573
|
+
}
|
|
4574
|
+
: attr.name,
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
/**
|
|
4579
|
+
* @param {any[]} attrs
|
|
4580
|
+
* @param {boolean} is_host
|
|
4581
|
+
* @param {TransformContext} transform_context
|
|
4582
|
+
* @returns {any[]}
|
|
4583
|
+
*/
|
|
4584
|
+
function normalize_host_ref_spreads(attrs, is_host, transform_context) {
|
|
4585
|
+
if (!is_host) return attrs;
|
|
4586
|
+
|
|
4587
|
+
const needs_explicit_spread_ref =
|
|
4588
|
+
transform_context.platform.jsx?.hostSpreadRefStrategy === 'explicit-ref-attr';
|
|
4589
|
+
const ref_exprs = attrs
|
|
4590
|
+
.filter((attr) => is_jsx_ref_attribute(attr))
|
|
4591
|
+
.map((attr) => attr.value.expression);
|
|
4592
|
+
const needs_synthetic_spread_ref = needs_explicit_spread_ref || ref_exprs.length > 0;
|
|
4593
|
+
|
|
4594
|
+
return attrs.flatMap((attr) => {
|
|
4595
|
+
if (!attr || attr.type !== 'JSXSpreadAttribute') {
|
|
4596
|
+
return [attr];
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
transform_context.needs_normalize_spread_props = true;
|
|
4600
|
+
const normalized = b.call(NORMALIZE_SPREAD_PROPS_INTERNAL_NAME, attr.argument);
|
|
4601
|
+
|
|
4602
|
+
if (needs_synthetic_spread_ref) {
|
|
4603
|
+
const normalized_id = create_generated_identifier(
|
|
4604
|
+
create_spread_props_name(transform_context),
|
|
4605
|
+
);
|
|
4606
|
+
const spread = {
|
|
4607
|
+
...attr,
|
|
4608
|
+
argument: clone_identifier(normalized_id),
|
|
4609
|
+
};
|
|
4610
|
+
const ref_attr = b.jsx_attribute(
|
|
4611
|
+
b.jsx_id('ref'),
|
|
4612
|
+
to_jsx_expression_container(b.member(clone_identifier(normalized_id), 'ref'), attr),
|
|
4613
|
+
false,
|
|
4614
|
+
attr,
|
|
4615
|
+
);
|
|
4616
|
+
ref_attr.metadata = { ...(ref_attr.metadata || {}) };
|
|
4617
|
+
/** @type {any} */ (ref_attr.metadata).from_ref_keyword = true;
|
|
4618
|
+
add_jsx_setup_declaration(spread, b.let(clone_identifier(normalized_id), normalized));
|
|
4619
|
+
|
|
4620
|
+
return [spread, ref_attr];
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
return [
|
|
4624
|
+
{
|
|
4625
|
+
...attr,
|
|
4626
|
+
argument: normalized,
|
|
4627
|
+
},
|
|
4628
|
+
];
|
|
4629
|
+
});
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
/**
|
|
4633
|
+
* @param {TransformContext} transform_context
|
|
4634
|
+
* @returns {string}
|
|
4635
|
+
*/
|
|
4636
|
+
function create_spread_props_name(transform_context) {
|
|
4637
|
+
if (transform_context.helper_state) {
|
|
4638
|
+
return create_helper_name(transform_context.helper_state, 'spread_props');
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
transform_context.local_statement_component_index += 1;
|
|
4642
|
+
return `_tsrx_spread_props_${transform_context.local_statement_component_index}`;
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
/**
|
|
4646
|
+
* @param {any} node
|
|
4647
|
+
* @param {any} declaration
|
|
4648
|
+
*/
|
|
4649
|
+
export function add_jsx_setup_declaration(node, declaration) {
|
|
4650
|
+
node.metadata ??= { path: [] };
|
|
4651
|
+
(node.metadata.generated_setup_declarations ??= []).push(declaration);
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4654
|
+
/**
|
|
4655
|
+
* @param {any} node
|
|
4656
|
+
* @param {Set<any>} [seen]
|
|
4657
|
+
* @returns {any[]}
|
|
4658
|
+
*/
|
|
4659
|
+
export function extract_jsx_setup_declarations(node, seen = new Set()) {
|
|
4660
|
+
if (node == null || typeof node !== 'object' || seen.has(node)) {
|
|
4661
|
+
return [];
|
|
4662
|
+
}
|
|
4663
|
+
seen.add(node);
|
|
4664
|
+
|
|
4665
|
+
const declarations = node.metadata?.generated_setup_declarations ?? [];
|
|
4666
|
+
if (node.metadata?.generated_setup_declarations) {
|
|
4667
|
+
delete node.metadata.generated_setup_declarations;
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
for (const key of Object.keys(node)) {
|
|
4671
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
4672
|
+
continue;
|
|
4673
|
+
}
|
|
4674
|
+
declarations.push(...extract_jsx_setup_declarations(node[key], seen));
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
return declarations;
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
/**
|
|
4681
|
+
* @param {any} expression
|
|
4682
|
+
* @param {boolean} in_jsx_child
|
|
4683
|
+
* @returns {any}
|
|
4684
|
+
*/
|
|
4685
|
+
function wrap_jsx_setup_declarations(expression, in_jsx_child) {
|
|
4686
|
+
const declarations = extract_jsx_setup_declarations(expression);
|
|
4687
|
+
if (declarations.length === 0) {
|
|
4688
|
+
return expression;
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
const return_expression =
|
|
4692
|
+
expression?.type === 'JSXExpressionContainer' ? expression.expression : expression;
|
|
4693
|
+
const call = b.call(
|
|
4694
|
+
b.arrow(
|
|
4695
|
+
[],
|
|
4696
|
+
b.block([...declarations, b.return(return_expression)], expression),
|
|
4697
|
+
false,
|
|
4698
|
+
expression,
|
|
4699
|
+
),
|
|
4700
|
+
);
|
|
4701
|
+
|
|
4702
|
+
return in_jsx_child ? to_jsx_expression_container(call, expression) : call;
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
/**
|
|
4706
|
+
* @param {any} attr
|
|
4707
|
+
* @returns {boolean}
|
|
4708
|
+
*/
|
|
4709
|
+
function is_named_ref_attribute(attr) {
|
|
4710
|
+
return !!(
|
|
4711
|
+
attr &&
|
|
4712
|
+
(attr.type === 'Attribute' || attr.type === 'JSXAttribute') &&
|
|
4713
|
+
attr.name &&
|
|
4714
|
+
((attr.name.type === 'Identifier' && attr.name.name !== 'ref') ||
|
|
4715
|
+
(attr.name.type === 'JSXIdentifier' && attr.name.name !== 'ref')) &&
|
|
4716
|
+
(attr.value?.type === 'RefExpression' ||
|
|
4717
|
+
is_ref_prop_expression(attr.value) ||
|
|
4718
|
+
(attr.value?.type === 'JSXExpressionContainer' &&
|
|
4719
|
+
is_ref_prop_expression(attr.value.expression)))
|
|
4720
|
+
);
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
/**
|
|
4724
|
+
* @param {any} expression
|
|
4725
|
+
* @returns {boolean}
|
|
4726
|
+
*/
|
|
4727
|
+
export function is_ref_prop_expression(expression) {
|
|
4728
|
+
return (
|
|
4729
|
+
expression?.type === 'RefExpression' ||
|
|
4730
|
+
(expression?.type === 'CallExpression' &&
|
|
4731
|
+
expression.callee?.type === 'Identifier' &&
|
|
4732
|
+
expression.callee.name === CREATE_REF_PROP_INTERNAL_NAME)
|
|
4733
|
+
);
|
|
4433
4734
|
}
|
|
4434
4735
|
|
|
4435
4736
|
/**
|
|
@@ -4549,7 +4850,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
|
|
|
4549
4850
|
type: 'CallExpression',
|
|
4550
4851
|
callee: {
|
|
4551
4852
|
type: 'Identifier',
|
|
4552
|
-
name:
|
|
4853
|
+
name: MERGE_REFS_INTERNAL_NAME,
|
|
4553
4854
|
metadata: { path: [] },
|
|
4554
4855
|
},
|
|
4555
4856
|
arguments: ref_exprs,
|
|
@@ -4610,7 +4911,9 @@ function is_jsx_ref_attribute(attr) {
|
|
|
4610
4911
|
* double-underscore matches the convention for compiler-generated
|
|
4611
4912
|
* identifiers and avoids shadowing user-declared `mergeRefs` symbols.
|
|
4612
4913
|
*/
|
|
4613
|
-
const
|
|
4914
|
+
export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
|
|
4915
|
+
export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
|
|
4916
|
+
export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
|
|
4614
4917
|
|
|
4615
4918
|
/**
|
|
4616
4919
|
* @param {any} attr
|
|
@@ -4619,7 +4922,31 @@ const MERGE_REFS_LOCAL_NAME = '__mergeRefs';
|
|
|
4619
4922
|
*/
|
|
4620
4923
|
export function to_jsx_attribute(attr, transform_context) {
|
|
4621
4924
|
if (!attr) return attr;
|
|
4622
|
-
if (attr.type === 'JSXAttribute'
|
|
4925
|
+
if (attr.type === 'JSXAttribute') {
|
|
4926
|
+
if (
|
|
4927
|
+
attr.value?.type === 'JSXExpressionContainer' &&
|
|
4928
|
+
attr.value.expression?.type === 'RefExpression'
|
|
4929
|
+
) {
|
|
4930
|
+
return {
|
|
4931
|
+
...attr,
|
|
4932
|
+
value: to_jsx_expression_container(
|
|
4933
|
+
create_ref_prop_call(attr.value.expression, transform_context),
|
|
4934
|
+
),
|
|
4935
|
+
metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
|
|
4936
|
+
};
|
|
4937
|
+
}
|
|
4938
|
+
if (
|
|
4939
|
+
attr.value?.type === 'JSXExpressionContainer' &&
|
|
4940
|
+
is_ref_prop_expression(attr.value.expression)
|
|
4941
|
+
) {
|
|
4942
|
+
return {
|
|
4943
|
+
...attr,
|
|
4944
|
+
metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
return attr;
|
|
4948
|
+
}
|
|
4949
|
+
if (attr.type === 'JSXSpreadAttribute') {
|
|
4623
4950
|
return attr;
|
|
4624
4951
|
}
|
|
4625
4952
|
if (attr.type === 'SpreadAttribute') {
|
|
@@ -4670,15 +4997,28 @@ export function to_jsx_attribute(attr, transform_context) {
|
|
|
4670
4997
|
attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
|
|
4671
4998
|
|
|
4672
4999
|
let value = attr.value;
|
|
5000
|
+
const is_ref_expression_value =
|
|
5001
|
+
value?.type === 'RefExpression' ||
|
|
5002
|
+
is_ref_prop_expression(value) ||
|
|
5003
|
+
(value?.type === 'JSXExpressionContainer' && is_ref_prop_expression(value.expression));
|
|
4673
5004
|
if (value) {
|
|
4674
5005
|
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
4675
5006
|
// Keep string literal as attribute string.
|
|
5007
|
+
} else if (value.type === 'RefExpression') {
|
|
5008
|
+
value = to_jsx_expression_container(create_ref_prop_call(value, transform_context));
|
|
4676
5009
|
} else if (value.type !== 'JSXExpressionContainer') {
|
|
4677
5010
|
value = to_jsx_expression_container(value);
|
|
5011
|
+
} else if (value.expression?.type === 'RefExpression') {
|
|
5012
|
+
value = to_jsx_expression_container(
|
|
5013
|
+
create_ref_prop_call(value.expression, transform_context),
|
|
5014
|
+
);
|
|
4678
5015
|
}
|
|
4679
5016
|
}
|
|
4680
5017
|
|
|
4681
5018
|
const jsx_attribute = build_jsx_attribute(name, value || null, attr.shorthand === true);
|
|
5019
|
+
if (is_ref_expression_value) {
|
|
5020
|
+
/** @type {any} */ (jsx_attribute.metadata).from_ref_keyword = true;
|
|
5021
|
+
}
|
|
4682
5022
|
|
|
4683
5023
|
if (value_has_unmappable_jsx_loc(value)) {
|
|
4684
5024
|
/** @type {any} */ (jsx_attribute.metadata).has_unmappable_value = true;
|
|
@@ -4700,6 +5040,35 @@ function value_has_unmappable_jsx_loc(value) {
|
|
|
4700
5040
|
);
|
|
4701
5041
|
}
|
|
4702
5042
|
|
|
5043
|
+
/**
|
|
5044
|
+
* @param {any} node
|
|
5045
|
+
* @param {TransformContext} transform_context
|
|
5046
|
+
* @returns {any}
|
|
5047
|
+
*/
|
|
5048
|
+
function create_ref_prop_call(node, transform_context) {
|
|
5049
|
+
transform_context.needs_ref_prop = true;
|
|
5050
|
+
|
|
5051
|
+
const argument = node.argument;
|
|
5052
|
+
const args = [b.thunk(argument)];
|
|
5053
|
+
|
|
5054
|
+
if (argument.type === 'Identifier' || argument.type === 'MemberExpression') {
|
|
5055
|
+
args.push(
|
|
5056
|
+
b.arrow(
|
|
5057
|
+
[b.id('v')],
|
|
5058
|
+
/** @type {any} */ ({
|
|
5059
|
+
type: 'AssignmentExpression',
|
|
5060
|
+
operator: '=',
|
|
5061
|
+
left: clone_expression_node(argument, false),
|
|
5062
|
+
right: b.id('v'),
|
|
5063
|
+
metadata: { path: [] },
|
|
5064
|
+
}),
|
|
5065
|
+
),
|
|
5066
|
+
);
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5069
|
+
return b.call(CREATE_REF_PROP_INTERNAL_NAME, ...args);
|
|
5070
|
+
}
|
|
5071
|
+
|
|
4703
5072
|
/**
|
|
4704
5073
|
* @param {any} node
|
|
4705
5074
|
* @param {TransformContext} transform_context
|
|
@@ -562,16 +562,17 @@ export function convert_source_map_to_mappings(
|
|
|
562
562
|
} else if (node.type === 'JSXIdentifier') {
|
|
563
563
|
// JSXIdentifiers can also be capitalized (for dynamic components)
|
|
564
564
|
if (node.loc && node.name) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
565
|
+
/** @type {Token} */
|
|
566
|
+
const token = {
|
|
567
|
+
source: node.metadata?.is_capitalized ? node.metadata.source_name : node.name,
|
|
568
|
+
generated: node.name,
|
|
569
|
+
loc: node.loc,
|
|
570
|
+
metadata: {},
|
|
571
|
+
};
|
|
572
|
+
if (node.metadata?.disable_verification) {
|
|
573
|
+
token.mappingData = { ...mapping_data, verification: false };
|
|
574
574
|
}
|
|
575
|
+
tokens.push(token);
|
|
575
576
|
}
|
|
576
577
|
return; // Leaf node, don't traverse further
|
|
577
578
|
} else if (node.type === 'Literal') {
|
|
@@ -866,6 +867,15 @@ export function convert_source_map_to_mappings(
|
|
|
866
867
|
// but since we already map the opening - start, we just need the proper end
|
|
867
868
|
// and it was causing some issues with mappings
|
|
868
869
|
mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
|
|
870
|
+
if (!closing && opening.selfClosing) {
|
|
871
|
+
const generated_close_length = '/>;'.length;
|
|
872
|
+
mapping.sourceOffsets = [/** @type {AST.NodeWithLocation} */ (opening).end - 2];
|
|
873
|
+
mapping.lengths = ['/>'.length];
|
|
874
|
+
mapping.generatedOffsets = [
|
|
875
|
+
mapping.generatedOffsets[0] + mapping.generatedLengths[0] - generated_close_length,
|
|
876
|
+
];
|
|
877
|
+
mapping.generatedLengths = [generated_close_length];
|
|
878
|
+
}
|
|
869
879
|
mappings.push(mapping);
|
|
870
880
|
}
|
|
871
881
|
|
package/types/index.d.ts
CHANGED
|
@@ -213,6 +213,7 @@ declare module 'estree' {
|
|
|
213
213
|
Text: TextNode;
|
|
214
214
|
Attribute: Attribute;
|
|
215
215
|
RefAttribute: RefAttribute;
|
|
216
|
+
RefExpression: RefExpression;
|
|
216
217
|
SpreadAttribute: SpreadAttribute;
|
|
217
218
|
ParenthesizedExpression: ParenthesizedExpression;
|
|
218
219
|
ScriptContent: ScriptContent;
|
|
@@ -220,6 +221,7 @@ declare module 'estree' {
|
|
|
220
221
|
|
|
221
222
|
interface ExpressionMap {
|
|
222
223
|
Style: Style;
|
|
224
|
+
RefExpression: RefExpression;
|
|
223
225
|
Text: TextNode;
|
|
224
226
|
JSXEmptyExpression: ESTreeJSX.JSXEmptyExpression;
|
|
225
227
|
ParenthesizedExpression: ParenthesizedExpression;
|
|
@@ -437,6 +439,12 @@ declare module 'estree' {
|
|
|
437
439
|
loc?: AST.SourceLocation;
|
|
438
440
|
}
|
|
439
441
|
|
|
442
|
+
interface RefExpression extends AST.BaseNode {
|
|
443
|
+
type: 'RefExpression';
|
|
444
|
+
argument: AST.Expression;
|
|
445
|
+
loc?: AST.SourceLocation;
|
|
446
|
+
}
|
|
447
|
+
|
|
440
448
|
interface SpreadAttribute extends AST.BaseNode {
|
|
441
449
|
type: 'SpreadAttribute';
|
|
442
450
|
argument: AST.Expression;
|
package/types/jsx-platform.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface JsxTransformContext {
|
|
|
36
36
|
needs_error_boundary: boolean;
|
|
37
37
|
needs_suspense: boolean;
|
|
38
38
|
needs_merge_refs: boolean;
|
|
39
|
+
needs_ref_prop: boolean;
|
|
40
|
+
needs_normalize_spread_props: boolean;
|
|
39
41
|
needs_fragment: boolean;
|
|
40
42
|
module_scoped_hook_components: boolean;
|
|
41
43
|
helper_state: {
|
|
@@ -290,9 +292,14 @@ export interface JsxPlatform {
|
|
|
290
292
|
* Module to import `mergeRefs` from when an element has more than one
|
|
291
293
|
* `ref` attribute and the platform uses the `'merge-refs'` strategy.
|
|
292
294
|
* Required when `jsx.multiRefStrategy === 'merge-refs'`; ignored
|
|
293
|
-
* otherwise. React: `'@tsrx/react/
|
|
295
|
+
* otherwise. React: `'@tsrx/react/ref'`. Preact: `'@tsrx/preact/ref'`.
|
|
294
296
|
*/
|
|
295
297
|
mergeRefs?: string;
|
|
298
|
+
/**
|
|
299
|
+
* Module to import named-ref-prop helpers from when compiling
|
|
300
|
+
* `prop={ref expr}` or normalizing host spreads containing named refs.
|
|
301
|
+
*/
|
|
302
|
+
refProp?: string;
|
|
296
303
|
};
|
|
297
304
|
|
|
298
305
|
jsx: {
|
|
@@ -320,6 +327,12 @@ export interface JsxPlatform {
|
|
|
320
327
|
* unchanged. The platform's runtime is responsible.
|
|
321
328
|
*/
|
|
322
329
|
multiRefStrategy?: 'merge-refs' | 'array';
|
|
330
|
+
/**
|
|
331
|
+
* Some JSX runtimes do not apply a `ref` that arrives through a props
|
|
332
|
+
* spread. In that case, host spread normalization also emits an
|
|
333
|
+
* explicit `ref={normalized.ref}` attribute.
|
|
334
|
+
*/
|
|
335
|
+
hostSpreadRefStrategy?: 'explicit-ref-attr';
|
|
323
336
|
};
|
|
324
337
|
|
|
325
338
|
validation: {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type MergeableRefCallback<T> = {
|
|
2
|
+
bivarianceHack(node: T | null): void | (() => void);
|
|
3
|
+
}['bivarianceHack'];
|
|
4
|
+
export type MergeableRefObject<T> = { current: T | null };
|
|
5
|
+
export type MergeableVueRef<T> = { value: T | null };
|
|
6
|
+
export type RefProp<T = unknown> = (node: T | null) => void | (() => void);
|
|
7
|
+
|
|
8
|
+
export type MergeableRef<T> =
|
|
9
|
+
| MergeableRefCallback<T>
|
|
10
|
+
| MergeableRefObject<T>
|
|
11
|
+
| MergeableVueRef<T>
|
|
12
|
+
| null
|
|
13
|
+
| undefined;
|
|
14
|
+
|
|
15
|
+
export function mergeRefs<T = any>(...refs: Array<MergeableRef<T>>): (node: T | null) => () => void;
|
|
16
|
+
export function isRefProp(value: unknown): boolean;
|
|
17
|
+
export function create_ref_prop<T>(
|
|
18
|
+
get_ref_value: () => T,
|
|
19
|
+
set_ref_value?: (value: T) => void,
|
|
20
|
+
): RefProp<T>;
|
|
21
|
+
export function apply_ref_value<T>(
|
|
22
|
+
ref_value: unknown,
|
|
23
|
+
node: T | null,
|
|
24
|
+
set_ref_value?: (value: T) => void,
|
|
25
|
+
): void | (() => void);
|
|
26
|
+
export function merge_ref_props<T = any>(
|
|
27
|
+
...refs: unknown[]
|
|
28
|
+
): undefined | ((node: T | null) => void | (() => void));
|
|
29
|
+
export function normalize_spread_props<T extends Record<PropertyKey, any> | null | undefined>(
|
|
30
|
+
props: T,
|
|
31
|
+
...outer_refs: unknown[]
|
|
32
|
+
): T | Record<PropertyKey, any>;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Merge multiple refs (function refs and ref objects) into a single
|
|
3
|
-
* callback ref. Used by the tsrx-react, tsrx-preact, and tsrx-vue
|
|
4
|
-
* compilers when an element has more than one `ref` attribute, since
|
|
5
|
-
* those runtimes treat duplicate `ref` props as a regular duplicate-prop
|
|
6
|
-
* collision (last wins) rather than running both. Solid does not use this
|
|
7
|
-
* helper — its native runtime accepts an array of refs and the compiler
|
|
8
|
-
* emits an array literal directly.
|
|
9
|
-
*
|
|
10
|
-
* The returned callback ref handles four cases per input:
|
|
11
|
-
* - `null` / `undefined`: skipped.
|
|
12
|
-
* - function ref: invoked with the node. If it returns a function, that
|
|
13
|
-
* return value is treated as a React 19 cleanup. Otherwise we record a
|
|
14
|
-
* cleanup that calls the ref with `null` so the legacy unmount contract
|
|
15
|
-
* still fires.
|
|
16
|
-
* - React-style ref object (`{ current }`): assigned on mount, cleared
|
|
17
|
-
* on unmount.
|
|
18
|
-
* - Vue-style ref object (`{ value }`, e.g. `ref()` / `useTemplateRef()`):
|
|
19
|
-
* assigned on mount, cleared on unmount.
|
|
20
|
-
*
|
|
21
|
-
* The merged ref always returns a cleanup. Under React 19 the cleanup
|
|
22
|
-
* runs on unmount and the inner refs are not separately re-invoked with
|
|
23
|
-
* `null`. Under older React, Preact, and Vue the cleanup return value
|
|
24
|
-
* is ignored, so the runtime instead invokes the merged ref a second
|
|
25
|
-
* time with `null` — which re-runs the loop body, calling each inner
|
|
26
|
-
* with `null` and clearing each ref object. Either way every inner ref
|
|
27
|
-
* sees a balanced mount/unmount.
|
|
28
|
-
*
|
|
29
|
-
* @param {...((node: any) => void | (() => void)) | { current: any } | { value: any } | null | undefined} refs
|
|
30
|
-
* @returns {(node: any) => (() => void)}
|
|
31
|
-
*/
|
|
32
|
-
export function mergeRefs(...refs) {
|
|
33
|
-
return (node) => {
|
|
34
|
-
/** @type {Array<() => void>} */
|
|
35
|
-
const cleanups = [];
|
|
36
|
-
for (const ref of refs) {
|
|
37
|
-
if (ref == null) continue;
|
|
38
|
-
if (typeof ref === 'function') {
|
|
39
|
-
const result = ref(node);
|
|
40
|
-
if (typeof result === 'function') {
|
|
41
|
-
cleanups.push(result);
|
|
42
|
-
} else {
|
|
43
|
-
cleanups.push(() => ref(null));
|
|
44
|
-
}
|
|
45
|
-
} else if ('current' in ref) {
|
|
46
|
-
ref.current = node;
|
|
47
|
-
cleanups.push(() => {
|
|
48
|
-
ref.current = null;
|
|
49
|
-
});
|
|
50
|
-
} else if ('value' in ref) {
|
|
51
|
-
ref.value = node;
|
|
52
|
-
cleanups.push(() => {
|
|
53
|
-
ref.value = null;
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return () => {
|
|
58
|
-
for (const cleanup of cleanups) cleanup();
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type MergeableRefCallback<T> = (node: T | null) => void | (() => void);
|
|
2
|
-
export type MergeableRefObject<T> = { current: T | null };
|
|
3
|
-
export type MergeableVueRef<T> = { value: T | null };
|
|
4
|
-
|
|
5
|
-
export type MergeableRef<T> =
|
|
6
|
-
| MergeableRefCallback<T>
|
|
7
|
-
| MergeableRefObject<T>
|
|
8
|
-
| MergeableVueRef<T>
|
|
9
|
-
| null
|
|
10
|
-
| undefined;
|
|
11
|
-
|
|
12
|
-
export function mergeRefs<T = any>(...refs: Array<MergeableRef<T>>): (node: T | null) => () => void;
|