@tsrx/vue 0.0.21 → 0.0.23
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 +7 -6
- package/src/index.js +4 -1
- package/src/ref.js +1 -0
- package/src/transform.js +557 -196
- package/types/index.d.ts +5 -16
- package/types/ref.d.ts +1 -0
- package/src/merge-refs.js +0 -1
- package/types/merge-refs.d.ts +0 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Vue compiler built on @tsrx/core",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.23",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
@@ -22,19 +22,20 @@
|
|
|
22
22
|
"types": "./types/error-boundary.d.ts",
|
|
23
23
|
"default": "./src/error-boundary.js"
|
|
24
24
|
},
|
|
25
|
-
"./
|
|
26
|
-
"types": "./types/
|
|
27
|
-
"default": "./src/
|
|
25
|
+
"./ref": {
|
|
26
|
+
"types": "./types/ref.d.ts",
|
|
27
|
+
"default": "./src/ref.js"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"esrap": "^2.1.0",
|
|
32
|
+
"is-reference": "^3.0.3",
|
|
32
33
|
"zimmerframe": "^1.1.2",
|
|
33
|
-
"@tsrx/core": "0.0.
|
|
34
|
+
"@tsrx/core": "0.0.28"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"vue": ">=3.5",
|
|
37
|
-
"vue-jsx-vapor": ">=3.2.
|
|
38
|
+
"vue-jsx-vapor": ">=3.2.12"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/estree": "^1.0.8",
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { createVolarMappingsResult, dedupeMappings, parseModule } from '@tsrx/core';
|
|
5
5
|
import { transform } from './transform.js';
|
|
6
6
|
|
|
7
|
+
export { isRefProp } from './ref.js';
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Parse tsrx-vue source code to an ESTree AST.
|
|
9
11
|
* @param {string} source
|
|
@@ -22,7 +24,7 @@ export function parse(source, filename, options) {
|
|
|
22
24
|
* @param {string} source
|
|
23
25
|
* @param {string} [filename]
|
|
24
26
|
* @param {{ collect?: boolean, loose?: boolean }} [options]
|
|
25
|
-
* @returns {{ code: string, map: any, css:
|
|
27
|
+
* @returns {{ code: string, map: any, css: string, cssHash: string | null, errors: CompileError[] }}
|
|
26
28
|
*/
|
|
27
29
|
export function compile(source, filename, options) {
|
|
28
30
|
const errors = /** @type {CompileError[]} */ ([]);
|
|
@@ -63,6 +65,7 @@ export function compile_to_volar_mappings(source, filename, options) {
|
|
|
63
65
|
const transformed = transform(ast, source, filename, {
|
|
64
66
|
collect: true,
|
|
65
67
|
loose: !!options?.loose,
|
|
68
|
+
typeOnly: true,
|
|
66
69
|
errors,
|
|
67
70
|
comments,
|
|
68
71
|
});
|
package/src/ref.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tsrx/core/runtime/ref';
|
package/src/transform.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/** @import { JsxPlatform } from '@tsrx/core/types' */
|
|
2
2
|
|
|
3
|
+
import { walk } from 'zimmerframe';
|
|
4
|
+
import is_reference from 'is-reference';
|
|
3
5
|
import {
|
|
4
6
|
builders,
|
|
5
7
|
clone_expression_node,
|
|
6
8
|
clone_identifier,
|
|
9
|
+
CREATE_REF_PROP_INTERNAL_NAME,
|
|
10
|
+
create_generated_identifier,
|
|
7
11
|
componentToFunctionDeclaration,
|
|
8
12
|
createJsxTransform,
|
|
9
13
|
error,
|
|
10
|
-
|
|
14
|
+
MERGE_REFS_INTERNAL_NAME,
|
|
15
|
+
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
11
16
|
setLocation,
|
|
17
|
+
toJsxAttribute,
|
|
12
18
|
} from '@tsrx/core';
|
|
13
19
|
|
|
14
20
|
/**
|
|
@@ -25,12 +31,14 @@ const vue_platform = {
|
|
|
25
31
|
imports: {
|
|
26
32
|
suspense: 'vue',
|
|
27
33
|
errorBoundary: '@tsrx/vue/error-boundary',
|
|
28
|
-
mergeRefs: '@tsrx/vue/
|
|
34
|
+
mergeRefs: '@tsrx/vue/ref',
|
|
35
|
+
refProp: '@tsrx/vue/ref',
|
|
29
36
|
},
|
|
30
37
|
jsx: {
|
|
31
38
|
rewriteClassAttr: false,
|
|
32
39
|
acceptedTsxKinds: ['vue'],
|
|
33
40
|
multiRefStrategy: 'merge-refs',
|
|
41
|
+
hostSpreadRefStrategy: 'explicit-ref-attr',
|
|
34
42
|
},
|
|
35
43
|
validation: {
|
|
36
44
|
requireUseServerForAwait: true,
|
|
@@ -41,6 +49,7 @@ const vue_platform = {
|
|
|
41
49
|
hooks: {
|
|
42
50
|
initialState: () => ({
|
|
43
51
|
needs_define_vapor_component: false,
|
|
52
|
+
needs_vapor_for: false,
|
|
44
53
|
}),
|
|
45
54
|
isTopLevelSetupCall(call_expression) {
|
|
46
55
|
return is_vue_setup_call(call_expression);
|
|
@@ -55,18 +64,17 @@ const vue_platform = {
|
|
|
55
64
|
preprocessElementAttributes(attrs, ctx, element) {
|
|
56
65
|
return preprocess_ref_attributes(attrs, element, ctx);
|
|
57
66
|
},
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
transformElementAttributes(attrs, ctx, element) {
|
|
68
|
+
const result = attrs.map((attr) => toJsxAttribute(attr, ctx));
|
|
69
|
+
if (!ctx.typeOnly || is_component_like_element(element)) {
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
return result.map(mark_type_only_host_ref_attribute);
|
|
73
|
+
},
|
|
74
|
+
renderForOf: (node, loop_params, body_statements, ctx) =>
|
|
75
|
+
render_for_of_as_vapor_for(node, loop_params, body_statements, ctx),
|
|
60
76
|
createErrorBoundaryContent(try_content) {
|
|
61
|
-
return
|
|
62
|
-
type: 'ArrowFunctionExpression',
|
|
63
|
-
params: [],
|
|
64
|
-
body: try_content.expression,
|
|
65
|
-
async: false,
|
|
66
|
-
generator: false,
|
|
67
|
-
expression: true,
|
|
68
|
-
metadata: { path: [] },
|
|
69
|
-
};
|
|
77
|
+
return builders.arrow([], try_content.expression);
|
|
70
78
|
},
|
|
71
79
|
transformElementChildren(node, walked_children, raw_children, attributes, ctx) {
|
|
72
80
|
return rewrite_host_text_or_html_children(
|
|
@@ -98,6 +106,34 @@ const vue_platform = {
|
|
|
98
106
|
|
|
99
107
|
export const transform = createJsxTransform(vue_platform);
|
|
100
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Vue's `VNodeRef` type is wider than TSRX host refs because it also supports
|
|
111
|
+
* component instances and null teardown values. In editor-only TSX, keep the ref
|
|
112
|
+
* expression unchanged but stop TypeScript verification from reporting that
|
|
113
|
+
* Vue-specific assignability diagnostic on the generated `ref` prop token.
|
|
114
|
+
*
|
|
115
|
+
* @param {any} attr
|
|
116
|
+
* @returns {any}
|
|
117
|
+
*/
|
|
118
|
+
function mark_type_only_host_ref_attribute(attr) {
|
|
119
|
+
if (
|
|
120
|
+
!attr ||
|
|
121
|
+
attr.type !== 'JSXAttribute' ||
|
|
122
|
+
attr.name?.type !== 'JSXIdentifier' ||
|
|
123
|
+
attr.name.name !== 'ref'
|
|
124
|
+
) {
|
|
125
|
+
return attr;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...attr,
|
|
130
|
+
name: {
|
|
131
|
+
...attr.name,
|
|
132
|
+
metadata: { ...(attr.name.metadata || {}), disable_verification: true },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
101
137
|
/**
|
|
102
138
|
* @param {any} component
|
|
103
139
|
* @param {any} transform_context
|
|
@@ -128,26 +164,12 @@ function component_to_vapor_component_declaration(component, transform_context,
|
|
|
128
164
|
};
|
|
129
165
|
/** @type {any} */ (component_id.metadata).hover = create_component_hover_replacement(fn.params);
|
|
130
166
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type: 'VariableDeclarator',
|
|
138
|
-
id: component_id,
|
|
139
|
-
init: call,
|
|
140
|
-
metadata: { path: [] },
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
metadata: {
|
|
144
|
-
path: [],
|
|
145
|
-
generated_helpers,
|
|
146
|
-
generated_statics,
|
|
147
|
-
},
|
|
148
|
-
}),
|
|
149
|
-
component,
|
|
150
|
-
);
|
|
167
|
+
const declaration = builders.declaration('const', [builders.declarator(component_id, call)]);
|
|
168
|
+
Object.assign(/** @type {any} */ (declaration.metadata), {
|
|
169
|
+
generated_helpers,
|
|
170
|
+
generated_statics,
|
|
171
|
+
});
|
|
172
|
+
return setLocation(/** @type {any} */ (declaration), component);
|
|
151
173
|
}
|
|
152
174
|
|
|
153
175
|
/**
|
|
@@ -158,24 +180,17 @@ function component_to_vapor_component_declaration(component, transform_context,
|
|
|
158
180
|
*/
|
|
159
181
|
function wrap_helper_component(helper_fn, helper_id, source_node) {
|
|
160
182
|
return setLocation(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
source_node,
|
|
173
|
-
),
|
|
174
|
-
metadata: { path: [] },
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
metadata: { path: [] },
|
|
178
|
-
}),
|
|
183
|
+
builders.declaration('const', [
|
|
184
|
+
builders.declarator(
|
|
185
|
+
clone_identifier(helper_id),
|
|
186
|
+
create_define_vapor_component_call(
|
|
187
|
+
function_declaration_to_expression(helper_fn),
|
|
188
|
+
[],
|
|
189
|
+
[],
|
|
190
|
+
source_node,
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
]),
|
|
179
194
|
source_node,
|
|
180
195
|
);
|
|
181
196
|
}
|
|
@@ -193,33 +208,22 @@ function create_define_vapor_component_call(
|
|
|
193
208
|
generated_statics,
|
|
194
209
|
source_node,
|
|
195
210
|
) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
metadata: { path: [] },
|
|
203
|
-
},
|
|
204
|
-
arguments: [fn_expression],
|
|
205
|
-
optional: false,
|
|
206
|
-
metadata: {
|
|
207
|
-
path: [],
|
|
208
|
-
generated_helpers,
|
|
209
|
-
generated_statics,
|
|
210
|
-
},
|
|
211
|
-
}),
|
|
212
|
-
source_node,
|
|
213
|
-
);
|
|
211
|
+
const call = builders.call('defineVaporComponent', fn_expression);
|
|
212
|
+
Object.assign(/** @type {any} */ (call.metadata), {
|
|
213
|
+
generated_helpers,
|
|
214
|
+
generated_statics,
|
|
215
|
+
});
|
|
216
|
+
return setLocation(call, source_node);
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
/**
|
|
217
220
|
* @param {any} node
|
|
218
221
|
* @param {any[]} loop_params
|
|
219
222
|
* @param {any[]} body_statements
|
|
223
|
+
* @param {any} transform_context
|
|
220
224
|
* @returns {any | null}
|
|
221
225
|
*/
|
|
222
|
-
function
|
|
226
|
+
function render_for_of_as_vapor_for(node, loop_params, body_statements, transform_context) {
|
|
223
227
|
if (body_statements.length !== 1) {
|
|
224
228
|
return null;
|
|
225
229
|
}
|
|
@@ -238,49 +242,43 @@ function render_for_of_as_vapor_template(node, loop_params, body_statements) {
|
|
|
238
242
|
? clone_expression_node(node.key)
|
|
239
243
|
: (find_jsx_key_expression(rendered) ??
|
|
240
244
|
(node.index ? clone_expression_node(node.index) : null));
|
|
241
|
-
|
|
242
|
-
const
|
|
245
|
+
|
|
246
|
+
const slot = key_expression
|
|
247
|
+
? create_keyed_vapor_for_slot(loop_params, rendered)
|
|
248
|
+
: { params: loop_params, body: rendered, expression: true };
|
|
249
|
+
if (!slot) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
transform_context.needs_vapor_for = true;
|
|
254
|
+
|
|
255
|
+
if (key_expression) {
|
|
256
|
+
strip_top_level_jsx_keys(slot.body);
|
|
257
|
+
}
|
|
258
|
+
|
|
243
259
|
const attributes = [
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
type: 'BinaryExpression',
|
|
249
|
-
operator: 'in',
|
|
250
|
-
left: create_v_for_left(loop_params),
|
|
251
|
-
right: clone_expression_node(node.right),
|
|
252
|
-
metadata: { path: [] },
|
|
253
|
-
}),
|
|
254
|
-
metadata: { path: [] },
|
|
255
|
-
},
|
|
260
|
+
builders.jsx_attribute(
|
|
261
|
+
builders.jsx_id('in'),
|
|
262
|
+
to_jsx_expression_container(clone_expression_node(node.right)),
|
|
263
|
+
),
|
|
256
264
|
];
|
|
257
265
|
|
|
258
266
|
if (key_expression) {
|
|
259
|
-
attributes.push(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
attributes.push(
|
|
268
|
+
builders.jsx_attribute(
|
|
269
|
+
builders.jsx_id('getKey'),
|
|
270
|
+
to_jsx_expression_container(create_loop_callback(loop_params, key_expression, true)),
|
|
271
|
+
),
|
|
272
|
+
);
|
|
265
273
|
}
|
|
266
274
|
|
|
267
|
-
return to_jsx_expression_container(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
metadata: { path: [] },
|
|
275
|
-
},
|
|
276
|
-
closingElement: {
|
|
277
|
-
type: 'JSXClosingElement',
|
|
278
|
-
name: { type: 'JSXIdentifier', name: 'template', metadata: { path: [] } },
|
|
279
|
-
metadata: { path: [] },
|
|
280
|
-
},
|
|
281
|
-
children,
|
|
282
|
-
metadata: { path: [] },
|
|
283
|
-
});
|
|
275
|
+
return to_jsx_expression_container(
|
|
276
|
+
builders.jsx_element_fresh(
|
|
277
|
+
builders.jsx_opening_element(builders.jsx_id('VaporFor'), attributes),
|
|
278
|
+
builders.jsx_closing_element(builders.jsx_id('VaporFor')),
|
|
279
|
+
[to_jsx_expression_container(create_loop_callback(slot.params, slot.body, slot.expression))],
|
|
280
|
+
),
|
|
281
|
+
);
|
|
284
282
|
}
|
|
285
283
|
|
|
286
284
|
/**
|
|
@@ -290,47 +288,20 @@ function render_for_of_as_vapor_template(node, loop_params, body_statements) {
|
|
|
290
288
|
* @returns {any}
|
|
291
289
|
*/
|
|
292
290
|
function render_for_of_as_flat_map(node, loop_params, rendered) {
|
|
293
|
-
return to_jsx_expression_container(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
},
|
|
303
|
-
arguments: [
|
|
304
|
-
{
|
|
305
|
-
type: 'ArrowFunctionExpression',
|
|
306
|
-
params: loop_params,
|
|
307
|
-
body: {
|
|
308
|
-
type: 'BlockStatement',
|
|
309
|
-
body: [
|
|
310
|
-
{
|
|
311
|
-
type: 'ReturnStatement',
|
|
312
|
-
argument: to_array_render_expression(rendered),
|
|
313
|
-
metadata: { path: [] },
|
|
314
|
-
},
|
|
315
|
-
],
|
|
316
|
-
metadata: { path: [] },
|
|
317
|
-
},
|
|
318
|
-
async: false,
|
|
319
|
-
generator: false,
|
|
320
|
-
expression: false,
|
|
321
|
-
metadata: { path: [] },
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
async: false,
|
|
325
|
-
optional: false,
|
|
326
|
-
metadata: { path: [] },
|
|
327
|
-
});
|
|
291
|
+
return to_jsx_expression_container(
|
|
292
|
+
builders.call(
|
|
293
|
+
builders.member(clone_expression_node(node.right), 'flatMap'),
|
|
294
|
+
builders.arrow(
|
|
295
|
+
loop_params,
|
|
296
|
+
builders.block([builders.return(to_array_render_expression(rendered))]),
|
|
297
|
+
),
|
|
298
|
+
),
|
|
299
|
+
);
|
|
328
300
|
}
|
|
329
301
|
|
|
330
302
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* lowering so `continue` truly skips the iteration.
|
|
303
|
+
* Loop bodies that can return `null` need the shared callback lowering so
|
|
304
|
+
* `continue` truly skips the iteration.
|
|
334
305
|
*
|
|
335
306
|
* @param {any} node
|
|
336
307
|
* @returns {boolean}
|
|
@@ -386,22 +357,6 @@ function to_array_render_expression(node) {
|
|
|
386
357
|
return builders.array([node]);
|
|
387
358
|
}
|
|
388
359
|
|
|
389
|
-
/**
|
|
390
|
-
* @param {any[]} loop_params
|
|
391
|
-
* @returns {any}
|
|
392
|
-
*/
|
|
393
|
-
function create_v_for_left(loop_params) {
|
|
394
|
-
if (loop_params.length === 1) {
|
|
395
|
-
return clone_expression_node(loop_params[0]);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
type: 'SequenceExpression',
|
|
400
|
-
expressions: loop_params.map((param) => clone_expression_node(param)),
|
|
401
|
-
metadata: { path: [] },
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
360
|
/**
|
|
406
361
|
* @param {any} node
|
|
407
362
|
* @returns {any | null}
|
|
@@ -450,6 +405,360 @@ function strip_top_level_jsx_keys(node) {
|
|
|
450
405
|
}
|
|
451
406
|
}
|
|
452
407
|
|
|
408
|
+
/**
|
|
409
|
+
* @param {any[]} loop_params
|
|
410
|
+
* @param {any} body
|
|
411
|
+
* @param {boolean} expression
|
|
412
|
+
* @returns {any}
|
|
413
|
+
*/
|
|
414
|
+
function create_loop_callback(loop_params, body, expression) {
|
|
415
|
+
const callback = builders.arrow(
|
|
416
|
+
loop_params.map((param) => clone_expression_node(param)),
|
|
417
|
+
body,
|
|
418
|
+
);
|
|
419
|
+
callback.expression = expression;
|
|
420
|
+
return callback;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {any[]} loop_params
|
|
425
|
+
* @param {any} rendered
|
|
426
|
+
* @returns {{ params: any[], body: any, expression: boolean } | null}
|
|
427
|
+
*/
|
|
428
|
+
function create_keyed_vapor_for_slot(loop_params, rendered) {
|
|
429
|
+
if (loop_params[0]?.type === 'Identifier') {
|
|
430
|
+
return {
|
|
431
|
+
params: loop_params,
|
|
432
|
+
body: rewrite_vapor_for_keyed_slot_refs(rendered, loop_params),
|
|
433
|
+
expression: true,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const item_ref = create_generated_identifier('__vapor_item');
|
|
438
|
+
const item_ref_value = create_value_member_expression(item_ref);
|
|
439
|
+
const replacements = create_pattern_replacements(loop_params[0], item_ref_value);
|
|
440
|
+
if (!replacements) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const params = [item_ref, ...loop_params.slice(1)];
|
|
445
|
+
const rewritten_rendered = rewrite_vapor_for_keyed_slot_refs(
|
|
446
|
+
rendered,
|
|
447
|
+
loop_params.slice(1),
|
|
448
|
+
replacements,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
params,
|
|
453
|
+
body: rewritten_rendered,
|
|
454
|
+
expression: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Vue's `VaporFor` passes plain item values to unkeyed slots, but keyed slots
|
|
460
|
+
* receive shallow refs so row instances can update in place. Match that runtime
|
|
461
|
+
* shape by reading loop params through `.value` inside the slot body.
|
|
462
|
+
*
|
|
463
|
+
* @param {any} node
|
|
464
|
+
* @param {any[]} loop_params
|
|
465
|
+
* @param {Map<string, any>} [replacements]
|
|
466
|
+
* @returns {any}
|
|
467
|
+
*/
|
|
468
|
+
function rewrite_vapor_for_keyed_slot_refs(node, loop_params, replacements = new Map()) {
|
|
469
|
+
const loop_param_names = new Set();
|
|
470
|
+
for (const param of loop_params) {
|
|
471
|
+
collect_pattern_names(param, loop_param_names);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (loop_param_names.size === 0 && replacements.size === 0) {
|
|
475
|
+
return node;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return walk(
|
|
479
|
+
node,
|
|
480
|
+
{ loop_param_names, shadowed_names: new Set() },
|
|
481
|
+
{
|
|
482
|
+
Identifier(identifier, { path, state, next }) {
|
|
483
|
+
const parent = path.at(-1);
|
|
484
|
+
if (
|
|
485
|
+
(state.loop_param_names.has(identifier.name) || replacements.has(identifier.name)) &&
|
|
486
|
+
!state.shadowed_names.has(identifier.name) &&
|
|
487
|
+
parent &&
|
|
488
|
+
is_runtime_reference(identifier, parent)
|
|
489
|
+
) {
|
|
490
|
+
const replacement = replacements.get(identifier.name);
|
|
491
|
+
if (replacement) {
|
|
492
|
+
return clone_expression_node(replacement);
|
|
493
|
+
}
|
|
494
|
+
return create_value_member_expression(identifier);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return next();
|
|
498
|
+
},
|
|
499
|
+
FunctionDeclaration: rewrite_function_shadowed_refs,
|
|
500
|
+
FunctionExpression: rewrite_function_shadowed_refs,
|
|
501
|
+
ArrowFunctionExpression: rewrite_function_shadowed_refs,
|
|
502
|
+
BlockStatement: rewrite_block_shadowed_refs,
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @param {any} identifier
|
|
509
|
+
* @param {any} parent
|
|
510
|
+
* @returns {boolean}
|
|
511
|
+
*/
|
|
512
|
+
function is_runtime_reference(identifier, parent) {
|
|
513
|
+
if (parent.type === 'JSXExpressionContainer') {
|
|
514
|
+
return parent.expression === identifier;
|
|
515
|
+
}
|
|
516
|
+
if (parent.type === 'JSXAttribute') {
|
|
517
|
+
return parent.value === identifier || parent.value?.expression === identifier;
|
|
518
|
+
}
|
|
519
|
+
return is_reference(identifier, parent);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* @param {any} pattern
|
|
524
|
+
* @param {any} source
|
|
525
|
+
* @returns {Map<string, any> | null}
|
|
526
|
+
*/
|
|
527
|
+
function create_pattern_replacements(pattern, source) {
|
|
528
|
+
const replacements = new Map();
|
|
529
|
+
return collect_pattern_replacements(pattern, source, replacements) ? replacements : null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @param {any} pattern
|
|
534
|
+
* @param {any} source
|
|
535
|
+
* @param {Map<string, any>} replacements
|
|
536
|
+
* @returns {boolean}
|
|
537
|
+
*/
|
|
538
|
+
function collect_pattern_replacements(pattern, source, replacements) {
|
|
539
|
+
if (!pattern) return true;
|
|
540
|
+
|
|
541
|
+
switch (pattern.type) {
|
|
542
|
+
case 'Identifier':
|
|
543
|
+
replacements.set(pattern.name, source);
|
|
544
|
+
return true;
|
|
545
|
+
case 'ObjectPattern':
|
|
546
|
+
for (const property of pattern.properties || []) {
|
|
547
|
+
if (property.type === 'RestElement' || property.computed) {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
if (
|
|
551
|
+
property.type !== 'Property' ||
|
|
552
|
+
!collect_pattern_replacements(
|
|
553
|
+
property.value,
|
|
554
|
+
create_property_member_expression(source, property.key),
|
|
555
|
+
replacements,
|
|
556
|
+
)
|
|
557
|
+
) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return true;
|
|
562
|
+
case 'ArrayPattern':
|
|
563
|
+
for (let index = 0; index < (pattern.elements || []).length; index++) {
|
|
564
|
+
const element = pattern.elements[index];
|
|
565
|
+
if (
|
|
566
|
+
element &&
|
|
567
|
+
!collect_pattern_replacements(
|
|
568
|
+
element,
|
|
569
|
+
create_index_member_expression(source, index),
|
|
570
|
+
replacements,
|
|
571
|
+
)
|
|
572
|
+
) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
default:
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* @param {any} node
|
|
584
|
+
* @param {{ state: { loop_param_names: Set<string>, shadowed_names: Set<string> }, next: (state?: any) => any }} context
|
|
585
|
+
* @returns {any}
|
|
586
|
+
*/
|
|
587
|
+
function rewrite_function_shadowed_refs(node, { state, next }) {
|
|
588
|
+
const shadowed_names = new Set(state.shadowed_names);
|
|
589
|
+
if (node.id) {
|
|
590
|
+
collect_pattern_names(node.id, shadowed_names);
|
|
591
|
+
}
|
|
592
|
+
for (const param of node.params || []) {
|
|
593
|
+
collect_pattern_names(param, shadowed_names);
|
|
594
|
+
}
|
|
595
|
+
collect_function_var_names(node.body, shadowed_names);
|
|
596
|
+
return next({ ...state, shadowed_names });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @param {any} node
|
|
601
|
+
* @param {{ state: { loop_param_names: Set<string>, shadowed_names: Set<string> }, next: (state?: any) => any }} context
|
|
602
|
+
* @returns {any}
|
|
603
|
+
*/
|
|
604
|
+
function rewrite_block_shadowed_refs(node, { state, next }) {
|
|
605
|
+
const shadowed_names = new Set(state.shadowed_names);
|
|
606
|
+
collect_block_lexical_names(node.body, shadowed_names);
|
|
607
|
+
return next({ ...state, shadowed_names });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @param {any[]} statements
|
|
612
|
+
* @param {Set<string>} names
|
|
613
|
+
* @returns {void}
|
|
614
|
+
*/
|
|
615
|
+
function collect_block_lexical_names(statements, names) {
|
|
616
|
+
for (const statement of statements || []) {
|
|
617
|
+
if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') {
|
|
618
|
+
for (const declaration of statement.declarations || []) {
|
|
619
|
+
collect_pattern_names(declaration.id, names);
|
|
620
|
+
}
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (
|
|
625
|
+
(statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') &&
|
|
626
|
+
statement.id
|
|
627
|
+
) {
|
|
628
|
+
collect_pattern_names(statement.id, names);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* @param {any} node
|
|
635
|
+
* @param {Set<string>} names
|
|
636
|
+
* @returns {void}
|
|
637
|
+
*/
|
|
638
|
+
function collect_function_var_names(node, names) {
|
|
639
|
+
if (!node || typeof node !== 'object') return;
|
|
640
|
+
|
|
641
|
+
if (Array.isArray(node)) {
|
|
642
|
+
for (const child of node) {
|
|
643
|
+
collect_function_var_names(child, names);
|
|
644
|
+
}
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (
|
|
649
|
+
node.type === 'FunctionDeclaration' ||
|
|
650
|
+
node.type === 'FunctionExpression' ||
|
|
651
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
652
|
+
node.type === 'ClassDeclaration' ||
|
|
653
|
+
node.type === 'ClassExpression'
|
|
654
|
+
) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
|
|
659
|
+
for (const declaration of node.declarations || []) {
|
|
660
|
+
collect_pattern_names(declaration.id, names);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
for (const key of Object.keys(node)) {
|
|
665
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
collect_function_var_names(node[key], names);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* @param {any} node
|
|
674
|
+
* @param {Set<string>} names
|
|
675
|
+
* @returns {void}
|
|
676
|
+
*/
|
|
677
|
+
function collect_pattern_names(node, names) {
|
|
678
|
+
if (!node) return;
|
|
679
|
+
|
|
680
|
+
switch (node.type) {
|
|
681
|
+
case 'Identifier':
|
|
682
|
+
names.add(node.name);
|
|
683
|
+
break;
|
|
684
|
+
case 'RestElement':
|
|
685
|
+
collect_pattern_names(node.argument, names);
|
|
686
|
+
break;
|
|
687
|
+
case 'AssignmentPattern':
|
|
688
|
+
collect_pattern_names(node.left, names);
|
|
689
|
+
break;
|
|
690
|
+
case 'ArrayPattern':
|
|
691
|
+
for (const element of node.elements || []) {
|
|
692
|
+
collect_pattern_names(element, names);
|
|
693
|
+
}
|
|
694
|
+
break;
|
|
695
|
+
case 'ObjectPattern':
|
|
696
|
+
for (const property of node.properties || []) {
|
|
697
|
+
collect_pattern_names(property, names);
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
case 'Property':
|
|
701
|
+
collect_pattern_names(node.value, names);
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* @param {any} object
|
|
708
|
+
* @param {any} key
|
|
709
|
+
* @returns {any}
|
|
710
|
+
*/
|
|
711
|
+
function create_property_member_expression(object, key) {
|
|
712
|
+
if (key?.type === 'Identifier') {
|
|
713
|
+
return create_member_expression(
|
|
714
|
+
clone_expression_node(object),
|
|
715
|
+
clone_identifier(key),
|
|
716
|
+
false,
|
|
717
|
+
key,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return create_member_expression(
|
|
722
|
+
clone_expression_node(object),
|
|
723
|
+
clone_expression_node(key),
|
|
724
|
+
true,
|
|
725
|
+
key,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* @param {any} object
|
|
731
|
+
* @param {number} index
|
|
732
|
+
* @returns {any}
|
|
733
|
+
*/
|
|
734
|
+
function create_index_member_expression(object, index) {
|
|
735
|
+
return create_member_expression(
|
|
736
|
+
clone_expression_node(object),
|
|
737
|
+
builders.literal(index),
|
|
738
|
+
true,
|
|
739
|
+
object,
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* @param {any} identifier
|
|
745
|
+
* @returns {any}
|
|
746
|
+
*/
|
|
747
|
+
function create_value_member_expression(identifier) {
|
|
748
|
+
return create_member_expression(clone_identifier(identifier), 'value', false, identifier);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* @param {any} object
|
|
753
|
+
* @param {any} property
|
|
754
|
+
* @param {boolean} computed
|
|
755
|
+
* @param {any} source_node
|
|
756
|
+
* @returns {any}
|
|
757
|
+
*/
|
|
758
|
+
function create_member_expression(object, property, computed, source_node) {
|
|
759
|
+
return builders.member(object, property, computed, false, source_node);
|
|
760
|
+
}
|
|
761
|
+
|
|
453
762
|
/**
|
|
454
763
|
* @param {any} fn
|
|
455
764
|
* @returns {any}
|
|
@@ -549,6 +858,7 @@ function preprocess_ref_attributes(attrs, element, transform_context) {
|
|
|
549
858
|
if (!is_component_like_element(element)) {
|
|
550
859
|
return attrs;
|
|
551
860
|
}
|
|
861
|
+
const result = [];
|
|
552
862
|
for (const attr of attrs) {
|
|
553
863
|
if (attr?.type === 'RefAttribute') {
|
|
554
864
|
error(
|
|
@@ -559,8 +869,72 @@ function preprocess_ref_attributes(attrs, element, transform_context) {
|
|
|
559
869
|
transform_context?.comments,
|
|
560
870
|
);
|
|
561
871
|
}
|
|
872
|
+
if (!transform_context.typeOnly && is_vue_named_ref_attribute(attr)) {
|
|
873
|
+
result.push(create_vue_named_ref_spread(attr));
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
result.push(attr);
|
|
877
|
+
}
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Vue's JSX transform treats prop names ending in `ref` as template-ref
|
|
883
|
+
* sugar on components. Keep named TSRX refs as ordinary runtime props by
|
|
884
|
+
* hiding the static prop name behind an object spread before Vue sees the JSX.
|
|
885
|
+
* Type-only virtual TSX skips that spread so Volar can offer completions on
|
|
886
|
+
* the real component prop name.
|
|
887
|
+
*
|
|
888
|
+
* @param {any} attr
|
|
889
|
+
* @returns {boolean}
|
|
890
|
+
*/
|
|
891
|
+
function is_vue_named_ref_attribute(attr) {
|
|
892
|
+
const attr_name = get_vue_attribute_name(attr);
|
|
893
|
+
const value = get_vue_attribute_expression(attr);
|
|
894
|
+
return !!(
|
|
895
|
+
attr_name &&
|
|
896
|
+
attr_name !== 'ref' &&
|
|
897
|
+
(attr?.type === 'Attribute' || attr?.type === 'JSXAttribute') &&
|
|
898
|
+
(value?.type === 'RefExpression' ||
|
|
899
|
+
(value?.type === 'CallExpression' &&
|
|
900
|
+
value.callee?.type === 'Identifier' &&
|
|
901
|
+
value.callee.name === CREATE_REF_PROP_INTERNAL_NAME))
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* @param {any} attr
|
|
907
|
+
* @returns {any}
|
|
908
|
+
*/
|
|
909
|
+
function create_vue_named_ref_spread(attr) {
|
|
910
|
+
const attr_name = get_vue_attribute_name(attr);
|
|
911
|
+
const value = get_vue_attribute_expression(attr);
|
|
912
|
+
if (attr_name === null) return attr;
|
|
913
|
+
const prop = builders.prop('init', builders.key(attr_name), value, false, false);
|
|
914
|
+
return builders.jsx_spread_attribute(builders.object([prop], attr), attr);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* @param {any} attr
|
|
919
|
+
* @returns {string | null}
|
|
920
|
+
*/
|
|
921
|
+
function get_vue_attribute_name(attr) {
|
|
922
|
+
if (attr?.type === 'Attribute') {
|
|
923
|
+
return typeof attr.name === 'string' ? attr.name : (attr.name?.name ?? null);
|
|
924
|
+
}
|
|
925
|
+
if (attr?.type === 'JSXAttribute') {
|
|
926
|
+
return attr.name?.type === 'JSXIdentifier' ? attr.name.name : null;
|
|
562
927
|
}
|
|
563
|
-
return
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* @param {any} attr
|
|
933
|
+
* @returns {any}
|
|
934
|
+
*/
|
|
935
|
+
function get_vue_attribute_expression(attr) {
|
|
936
|
+
const value = attr?.value;
|
|
937
|
+
return value?.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
564
938
|
}
|
|
565
939
|
|
|
566
940
|
/**
|
|
@@ -652,16 +1026,7 @@ function has_dom_content_attribute(attributes, name) {
|
|
|
652
1026
|
* @returns {any}
|
|
653
1027
|
*/
|
|
654
1028
|
function create_jsx_attribute(name, value, source_node) {
|
|
655
|
-
return
|
|
656
|
-
/** @type {any} */ ({
|
|
657
|
-
type: 'JSXAttribute',
|
|
658
|
-
name: identifier_to_jsx_name(builders.id(name)),
|
|
659
|
-
value,
|
|
660
|
-
shorthand: false,
|
|
661
|
-
metadata: { path: [] },
|
|
662
|
-
}),
|
|
663
|
-
source_node,
|
|
664
|
-
);
|
|
1029
|
+
return builders.jsx_attribute(builders.jsx_id(name), value, false, source_node);
|
|
665
1030
|
}
|
|
666
1031
|
|
|
667
1032
|
/**
|
|
@@ -670,12 +1035,7 @@ function create_jsx_attribute(name, value, source_node) {
|
|
|
670
1035
|
* @returns {any}
|
|
671
1036
|
*/
|
|
672
1037
|
function to_jsx_expression_container(expression, source_node = expression) {
|
|
673
|
-
|
|
674
|
-
return {
|
|
675
|
-
type: 'JSXExpressionContainer',
|
|
676
|
-
expression,
|
|
677
|
-
metadata: { path: [] },
|
|
678
|
-
};
|
|
1038
|
+
return builders.jsx_expression_container(expression, source_node);
|
|
679
1039
|
}
|
|
680
1040
|
|
|
681
1041
|
/**
|
|
@@ -741,6 +1101,10 @@ function inject_vue_imports(program, transform_context) {
|
|
|
741
1101
|
ensure_named_import(program, 'vue-jsx-vapor', 'defineVaporComponent');
|
|
742
1102
|
}
|
|
743
1103
|
|
|
1104
|
+
if (transform_context.needs_vapor_for) {
|
|
1105
|
+
ensure_named_import(program, 'vue-jsx-vapor', 'VaporFor');
|
|
1106
|
+
}
|
|
1107
|
+
|
|
744
1108
|
if (transform_context.needs_suspense) {
|
|
745
1109
|
ensure_named_import(program, 'vue', 'Suspense');
|
|
746
1110
|
}
|
|
@@ -750,7 +1114,20 @@ function inject_vue_imports(program, transform_context) {
|
|
|
750
1114
|
}
|
|
751
1115
|
|
|
752
1116
|
if (transform_context.needs_merge_refs) {
|
|
753
|
-
ensure_named_import(program, '@tsrx/vue/
|
|
1117
|
+
ensure_named_import(program, '@tsrx/vue/ref', 'mergeRefs', MERGE_REFS_INTERNAL_NAME);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (transform_context.needs_ref_prop) {
|
|
1121
|
+
ensure_named_import(program, '@tsrx/vue/ref', 'create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (transform_context.needs_normalize_spread_props) {
|
|
1125
|
+
ensure_named_import(
|
|
1126
|
+
program,
|
|
1127
|
+
'@tsrx/vue/ref',
|
|
1128
|
+
'normalize_spread_props',
|
|
1129
|
+
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
1130
|
+
);
|
|
754
1131
|
}
|
|
755
1132
|
}
|
|
756
1133
|
|
|
@@ -782,7 +1159,7 @@ function ensure_named_import(program, source, name, local = name) {
|
|
|
782
1159
|
return;
|
|
783
1160
|
}
|
|
784
1161
|
|
|
785
|
-
program.body.unshift(
|
|
1162
|
+
program.body.unshift(builders.imports([[name, local, 'value']], source));
|
|
786
1163
|
}
|
|
787
1164
|
|
|
788
1165
|
/**
|
|
@@ -799,19 +1176,3 @@ function create_import_specifier(name, local = name) {
|
|
|
799
1176
|
metadata: { path: [] },
|
|
800
1177
|
};
|
|
801
1178
|
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* @param {string} source
|
|
805
|
-
* @param {any[]} specifiers
|
|
806
|
-
* @returns {any}
|
|
807
|
-
*/
|
|
808
|
-
function create_import_declaration(source, specifiers) {
|
|
809
|
-
return {
|
|
810
|
-
type: 'ImportDeclaration',
|
|
811
|
-
attributes: [],
|
|
812
|
-
specifiers,
|
|
813
|
-
importKind: 'value',
|
|
814
|
-
source: builders.literal(source),
|
|
815
|
-
metadata: { path: [] },
|
|
816
|
-
};
|
|
817
|
-
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import type { Program } from 'estree';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CompileFn, ParseOptions, VolarCompileFn } from '@tsrx/core/types';
|
|
3
3
|
|
|
4
4
|
export function parse(source: string, filename?: string, options?: ParseOptions): Program;
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
source: string,
|
|
8
|
-
filename?: string,
|
|
9
|
-
options?: { collect?: boolean; loose?: boolean },
|
|
10
|
-
): {
|
|
11
|
-
code: string;
|
|
12
|
-
map: unknown;
|
|
13
|
-
css: { code: string; hash: string } | null;
|
|
14
|
-
errors: CompileError[];
|
|
15
|
-
};
|
|
6
|
+
export { isRefProp } from './ref.js';
|
|
16
7
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
options?: ParseOptions,
|
|
21
|
-
): VolarMappingsResult;
|
|
8
|
+
export const compile: CompileFn;
|
|
9
|
+
|
|
10
|
+
export const compile_to_volar_mappings: VolarCompileFn;
|
package/types/ref.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tsrx/core/runtime/ref';
|
package/src/merge-refs.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { mergeRefs } from '@tsrx/core/runtime/merge-refs';
|
package/types/merge-refs.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { mergeRefs } from '@tsrx/core/runtime/merge-refs';
|