@tsrx/core 0.1.49 → 0.1.51
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 +10 -1
- package/src/analyze/prune.js +62 -77
- package/src/analyze/validation.js +7 -7
- package/src/parse/index.js +59 -80
- package/src/plugin.js +219 -107
- package/src/runtime/language-helpers.js +34 -25
- package/src/runtime/ref.js +57 -36
- package/src/scope.js +4 -5
- package/src/source-map-utils.js +8 -22
- package/src/transform/imports.js +8 -14
- package/src/transform/jsx/ast-builders.js +4 -1
- package/src/transform/jsx/index.js +1 -13
- package/src/transform/jsx/server-module.js +102 -96
- package/src/transform/jsx-hoist.js +4 -4
- package/src/transform/lazy.js +196 -137
- package/src/transform/scoping.js +108 -100
- package/src/transform/segments.js +24 -49
- package/src/transform/style-ref.js +92 -106
- package/src/transform/stylesheet.js +5 -21
- package/src/utils/ast.js +25 -20
- package/src/utils/builders.js +66 -2
- package/src/vite/dep-scan.js +135 -0
- package/types/index.d.ts +317 -17
- package/types/parse.d.ts +98 -13
- package/types/runtime/language-helpers.d.ts +10 -5
- package/types/runtime/ref.d.ts +63 -14
- package/types/vite/dep-scan.d.ts +26 -0
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
+
/** @import { ClassMapCollectionState, StyleRefOptions, TopScopedClasses, Visitors } from '../../types/index' */
|
|
2
4
|
|
|
5
|
+
import { walk } from 'zimmerframe';
|
|
3
6
|
import * as b from '../utils/builders.js';
|
|
4
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
child_nodes,
|
|
9
|
+
is_function_or_class_node as is_function_or_class_boundary,
|
|
10
|
+
is_style_element,
|
|
11
|
+
} from '../utils/ast.js';
|
|
5
12
|
import { clone_ast_node, clone_identifier } from './jsx/ast-builders.js';
|
|
6
13
|
|
|
7
14
|
const regex_backslash_and_following_character = /\\(.)/g;
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
|
-
* @
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* visitExpression?: (expression: AST.Expression) => AST.Expression;
|
|
14
|
-
* }} StyleRefOptions
|
|
17
|
+
* @param {AST.Node} component
|
|
18
|
+
* @param {AST.CSS.StyleSheet} css
|
|
19
|
+
* @returns {AST.ObjectExpression}
|
|
15
20
|
*/
|
|
21
|
+
export function create_style_class_map(component, css) {
|
|
22
|
+
return build_style_class_map(
|
|
23
|
+
component.metadata?.topScopedClasses ?? collect_style_class_map_entries(css),
|
|
24
|
+
css.hash,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
16
27
|
|
|
17
28
|
/**
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {any} css
|
|
29
|
+
* @param {AST.CSS.StyleSheet} css
|
|
20
30
|
* @returns {AST.ObjectExpression}
|
|
21
31
|
*/
|
|
22
|
-
export function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
export function create_style_class_map_from_stylesheet(css) {
|
|
33
|
+
return build_style_class_map(collect_style_class_map_entries(css), css.hash);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `{ foo: 'hash foo', … }` for every class the style expression exposes.
|
|
38
|
+
*
|
|
39
|
+
* @param {TopScopedClasses} top_scoped_classes
|
|
40
|
+
* @param {string | null} hash
|
|
41
|
+
* @returns {AST.ObjectExpression}
|
|
42
|
+
*/
|
|
43
|
+
function build_style_class_map(top_scoped_classes, hash) {
|
|
27
44
|
const class_names = [...top_scoped_classes.keys()].sort();
|
|
28
45
|
|
|
29
46
|
return b.object(
|
|
@@ -34,42 +51,28 @@ export function create_style_class_map(component, css) {
|
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
/**
|
|
37
|
-
* @param {
|
|
38
|
-
* @returns {AST.
|
|
39
|
-
*/
|
|
40
|
-
export function create_style_class_map_from_stylesheet(css) {
|
|
41
|
-
return create_style_class_map(
|
|
42
|
-
{ metadata: { topScopedClasses: collect_style_class_map_entries(css) } },
|
|
43
|
-
css,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {any} style_element
|
|
49
|
-
* @returns {any | null}
|
|
54
|
+
* @param {AST.JSXStyleElement} style_element
|
|
55
|
+
* @returns {AST.CSS.StyleSheet | null}
|
|
50
56
|
*/
|
|
51
57
|
export function get_style_element_stylesheet(style_element) {
|
|
52
|
-
return (
|
|
53
|
-
style_element?.children?.find?.((/** @type {any} */ child) => child.type === 'StyleSheet') ??
|
|
54
|
-
null
|
|
55
|
-
);
|
|
58
|
+
return style_element.children?.find((child) => child.type === 'StyleSheet') ?? null;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
|
-
* @param {
|
|
60
|
-
* @param {
|
|
61
|
-
* @returns {
|
|
62
|
+
* @param {AST.Node | AST.Node[]} node
|
|
63
|
+
* @param {ESTreeJSX.JSXAttribute[]} [refs]
|
|
64
|
+
* @returns {ESTreeJSX.JSXAttribute[]}
|
|
62
65
|
*/
|
|
63
66
|
export function collect_style_ref_attributes(node, refs = []) {
|
|
64
|
-
if (!node || typeof node !== 'object') return refs;
|
|
65
|
-
|
|
66
67
|
if (Array.isArray(node)) {
|
|
67
68
|
for (const child of node) collect_style_ref_attributes(child, refs);
|
|
68
69
|
return refs;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
if (!node || typeof node !== 'object') return refs;
|
|
73
|
+
|
|
71
74
|
if (is_style_element(node)) {
|
|
72
|
-
for (const attr of node.openingElement
|
|
75
|
+
for (const attr of node.openingElement.attributes) {
|
|
73
76
|
if (is_ref_attribute(attr) && attr.value) {
|
|
74
77
|
refs.push(attr);
|
|
75
78
|
}
|
|
@@ -81,18 +84,15 @@ export function collect_style_ref_attributes(node, refs = []) {
|
|
|
81
84
|
return refs;
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
for (const
|
|
85
|
-
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
collect_style_ref_attributes(node[key], refs);
|
|
87
|
+
for (const child of child_nodes(node, 'css')) {
|
|
88
|
+
collect_style_ref_attributes(child, refs);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
return refs;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* @param {
|
|
95
|
+
* @param {ESTreeJSX.JSXAttribute[]} ref_attributes
|
|
96
96
|
* @param {AST.Expression} style_map
|
|
97
97
|
* @param {StyleRefOptions} [options]
|
|
98
98
|
* @returns {AST.Statement[]}
|
|
@@ -203,7 +203,7 @@ function visit_expression(expression, options) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
* @param {
|
|
206
|
+
* @param {ESTreeJSX.JSXAttribute} attr
|
|
207
207
|
* @returns {AST.Expression | null}
|
|
208
208
|
*/
|
|
209
209
|
function get_ref_attribute_expression(attr) {
|
|
@@ -216,28 +216,21 @@ function get_ref_attribute_expression(attr) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
|
-
* @param {
|
|
220
|
-
* @returns {
|
|
219
|
+
* @param {ESTreeJSX.JSXAttributeNode} attr
|
|
220
|
+
* @returns {attr is ESTreeJSX.JSXAttribute}
|
|
221
221
|
*/
|
|
222
222
|
function is_ref_attribute(attr) {
|
|
223
223
|
return (
|
|
224
|
-
attr
|
|
224
|
+
attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'ref'
|
|
225
225
|
);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
|
-
* @param {
|
|
230
|
-
* @returns {
|
|
231
|
-
*/
|
|
232
|
-
function is_style_element(node) {
|
|
233
|
-
return !!node && node.type === 'JSXStyleElement';
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* @param {any} css
|
|
238
|
-
* @returns {Map<string, any>}
|
|
229
|
+
* @param {AST.CSS.StyleSheet} css
|
|
230
|
+
* @returns {TopScopedClasses}
|
|
239
231
|
*/
|
|
240
232
|
function collect_style_class_map_entries(css) {
|
|
233
|
+
/** @type {TopScopedClasses} */
|
|
241
234
|
const entries = new Map();
|
|
242
235
|
collect_rule_class_map_entries(css, entries);
|
|
243
236
|
return entries;
|
|
@@ -249,7 +242,7 @@ function collect_style_class_map_entries(css) {
|
|
|
249
242
|
* `create_style_class_map_from_stylesheet`, so marking and the generated map
|
|
250
243
|
* always agree; calling both is harmless.
|
|
251
244
|
*
|
|
252
|
-
* @param {
|
|
245
|
+
* @param {AST.CSS.StyleSheet} css
|
|
253
246
|
* @returns {void}
|
|
254
247
|
*/
|
|
255
248
|
export function mark_class_map_selectors(css) {
|
|
@@ -257,66 +250,59 @@ export function mark_class_map_selectors(css) {
|
|
|
257
250
|
}
|
|
258
251
|
|
|
259
252
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
253
|
+
* The state threaded through the class-map collection walk: the nearest
|
|
254
|
+
* prelude-level selector. Classes found inside another selector (e.g. in
|
|
255
|
+
* `:global(...)` args) mark it as the selector that carries their class map
|
|
256
|
+
* entry.
|
|
257
|
+
*
|
|
258
|
+
* @param {AST.CSS.StyleSheet} css
|
|
259
|
+
* @param {TopScopedClasses} entries
|
|
265
260
|
* @returns {void}
|
|
266
261
|
*/
|
|
267
|
-
function collect_rule_class_map_entries(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
for (const key of Object.keys(node)) {
|
|
299
|
-
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
collect_rule_class_map_entries(node[key], entries, enclosing_selector);
|
|
303
|
-
}
|
|
262
|
+
function collect_rule_class_map_entries(css, entries) {
|
|
263
|
+
walk(
|
|
264
|
+
/** @type {AST.CSS.Node} */ (css),
|
|
265
|
+
/** @type {ClassMapCollectionState} */ ({ enclosing_selector: null }),
|
|
266
|
+
/** @type {Visitors<AST.CSS.Node, ClassMapCollectionState>} */ ({
|
|
267
|
+
ComplexSelector(node, context) {
|
|
268
|
+
const enclosing_selector = context.state.enclosing_selector ?? node;
|
|
269
|
+
const class_selector = get_standalone_class_selector(node);
|
|
270
|
+
|
|
271
|
+
if (class_selector) {
|
|
272
|
+
// Mark the prelude-level selector for every occurrence (not just the
|
|
273
|
+
// deduped first) so the render preparation of style expressions keeps
|
|
274
|
+
// exactly the selectors whose classes the map exposes.
|
|
275
|
+
enclosing_selector.metadata.class_map_selector = true;
|
|
276
|
+
const name = class_selector.name.replace(regex_backslash_and_following_character, '$1');
|
|
277
|
+
if (!entries.has(name)) {
|
|
278
|
+
entries.set(name, {
|
|
279
|
+
start: class_selector.start,
|
|
280
|
+
end: class_selector.end,
|
|
281
|
+
selector: class_selector,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
context.next({ enclosing_selector });
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
304
290
|
}
|
|
305
291
|
|
|
306
292
|
/**
|
|
307
|
-
* @param {
|
|
308
|
-
* @returns {
|
|
293
|
+
* @param {AST.CSS.ComplexSelector} complex_selector
|
|
294
|
+
* @returns {AST.CSS.ClassSelector | null}
|
|
309
295
|
*/
|
|
310
296
|
function get_standalone_class_selector(complex_selector) {
|
|
311
|
-
if (complex_selector
|
|
297
|
+
if (complex_selector.children.length !== 1) return null;
|
|
312
298
|
const relative_selector = complex_selector.children[0];
|
|
313
299
|
if (
|
|
314
|
-
relative_selector
|
|
315
|
-
relative_selector
|
|
316
|
-
relative_selector
|
|
300
|
+
relative_selector.metadata.is_global ||
|
|
301
|
+
relative_selector.metadata.is_global_like ||
|
|
302
|
+
relative_selector.selectors.length !== 1
|
|
317
303
|
) {
|
|
318
304
|
return null;
|
|
319
305
|
}
|
|
320
306
|
const selector = relative_selector.selectors[0];
|
|
321
|
-
return selector
|
|
307
|
+
return selector.type === 'ClassSelector' ? selector : null;
|
|
322
308
|
}
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@import * as AST from 'estree';
|
|
3
|
-
@import { Visitors } from '../../types/index';
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
@typedef {{
|
|
8
|
-
code: MagicString;
|
|
9
|
-
hash: string;
|
|
10
|
-
minify: boolean;
|
|
11
|
-
selector: string;
|
|
12
|
-
keyframes: Record<string, {
|
|
13
|
-
indexes: number[];
|
|
14
|
-
local: boolean | undefined;
|
|
15
|
-
}>;
|
|
16
|
-
specificity: {
|
|
17
|
-
bumped: boolean
|
|
18
|
-
}
|
|
19
|
-
}} State
|
|
3
|
+
@import { StylesheetRenderState, Visitors } from '../../types/index';
|
|
20
4
|
*/
|
|
21
5
|
|
|
22
6
|
import MagicString from 'magic-string';
|
|
@@ -39,7 +23,7 @@ function remove_css_prefix(name) {
|
|
|
39
23
|
/**
|
|
40
24
|
* Walk backwards until we find a non-whitespace character
|
|
41
25
|
* @param {number} end
|
|
42
|
-
* @param {
|
|
26
|
+
* @param {StylesheetRenderState} state
|
|
43
27
|
*/
|
|
44
28
|
function remove_preceding_whitespace(end, state) {
|
|
45
29
|
let start = end;
|
|
@@ -121,7 +105,7 @@ function has_global_in_middle(rule) {
|
|
|
121
105
|
/**
|
|
122
106
|
* @param {AST.CSS.PseudoClassSelector} selector
|
|
123
107
|
* @param {AST.CSS.Combinator | null} combinator
|
|
124
|
-
* @param {
|
|
108
|
+
* @param {StylesheetRenderState} state
|
|
125
109
|
*/
|
|
126
110
|
function remove_global_pseudo_class(selector, combinator, state) {
|
|
127
111
|
if (selector.args === null) {
|
|
@@ -166,7 +150,7 @@ function escape_comment_close(node, code) {
|
|
|
166
150
|
}
|
|
167
151
|
|
|
168
152
|
/**
|
|
169
|
-
* @param {
|
|
153
|
+
* @param {StylesheetRenderState} state
|
|
170
154
|
* @param {number} index
|
|
171
155
|
*/
|
|
172
156
|
function append_hash(state, index) {
|
|
@@ -207,7 +191,7 @@ function is_empty(rule, is_in_global_block) {
|
|
|
207
191
|
return true;
|
|
208
192
|
}
|
|
209
193
|
|
|
210
|
-
/** @type {Visitors<AST.CSS.Node,
|
|
194
|
+
/** @type {Visitors<AST.CSS.Node, StylesheetRenderState>} */
|
|
211
195
|
const visitors = {
|
|
212
196
|
_: (node, context) => {
|
|
213
197
|
context.state.code.addSourcemapLocation(node.start);
|
package/src/utils/ast.js
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Represents the path of a destructured assignment from either a declaration
|
|
5
|
-
* or assignment expression. For example, given `const { foo: { bar: baz } } = quux`,
|
|
6
|
-
* the path of `baz` is `foo.bar`
|
|
7
|
-
* @typedef {{
|
|
8
|
-
* node: AST.Identifier | AST.MemberExpression;
|
|
9
|
-
* is_rest: boolean;
|
|
10
|
-
* has_default_value: boolean;
|
|
11
|
-
* expression: (object: AST.Identifier | AST.CallExpression) => AST.Expression;
|
|
12
|
-
* update_expression: (object: AST.Identifier) => AST.Expression;
|
|
13
|
-
* }} DestructuredAssignment
|
|
14
|
-
* - `node`: The node the destructuring path ends in. Can be a member expression only for assignment expressions
|
|
15
|
-
* - `is_rest`: `true` if this is a `...rest` destructuring
|
|
16
|
-
* - `has_default_value`: `true` if this has a fallback value like `const { foo = 'bar' } = ..`
|
|
17
|
-
* - `expression`: The value of the current path. Will be a call expression if a rest element or default is involved — e.g. `const { foo: { bar: baz = 42 }, ...rest } = quux` — since we can't represent `baz` or `rest` purely as a path. Will be an await expression in case of an async default value (`const { foo = await bar } = ...`)
|
|
18
|
-
* - `update_expression`: Like `expression` but without default values.
|
|
19
|
-
*/
|
|
3
|
+
/** @import { DestructuredAssignment } from '../../types/index' */
|
|
20
4
|
|
|
21
5
|
import * as b from './builders.js';
|
|
22
6
|
|
|
@@ -57,6 +41,19 @@ export function child_nodes(node, skip_key) {
|
|
|
57
41
|
return children;
|
|
58
42
|
}
|
|
59
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The children a node carries, as nodes. Node types differ in whether they
|
|
46
|
+
* have a `children` slot at all (`JSXCodeBlock` does not) and in what it may
|
|
47
|
+
* hold, so this reads it uniformly instead of forcing every caller to narrow.
|
|
48
|
+
*
|
|
49
|
+
* @param {AST.Node} node
|
|
50
|
+
* @returns {AST.Node[]}
|
|
51
|
+
*/
|
|
52
|
+
export function node_children(node) {
|
|
53
|
+
const children = /** @type {AST.TraversableAstNode} */ (node).children;
|
|
54
|
+
return Array.isArray(children) ? children.filter(is_ast_node) : [];
|
|
55
|
+
}
|
|
56
|
+
|
|
60
57
|
/**
|
|
61
58
|
* @template {object} T
|
|
62
59
|
* @param {T | null | undefined} node
|
|
@@ -68,6 +65,14 @@ export function has_location(node) {
|
|
|
68
65
|
return location.loc != null && location.start !== undefined && location.end !== undefined;
|
|
69
66
|
}
|
|
70
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @param {AST.Node | null | undefined} node
|
|
70
|
+
* @returns {node is AST.JSXStyleElement}
|
|
71
|
+
*/
|
|
72
|
+
export function is_style_element(node) {
|
|
73
|
+
return !!node && node.type === 'JSXStyleElement';
|
|
74
|
+
}
|
|
75
|
+
|
|
71
76
|
/**
|
|
72
77
|
* @param {AST.Node} node
|
|
73
78
|
* @returns {node is AST.Function}
|
|
@@ -90,7 +95,7 @@ export function is_function_or_class_node(node) {
|
|
|
90
95
|
|
|
91
96
|
/**
|
|
92
97
|
* @param {AST.Node} node
|
|
93
|
-
* @returns {
|
|
98
|
+
* @returns {node is AST.Function}
|
|
94
99
|
*/
|
|
95
100
|
export function is_function_or_component_node(node) {
|
|
96
101
|
return is_function_node(node);
|
|
@@ -199,8 +204,8 @@ export function get_component_from_path(path, includes_functions = false) {
|
|
|
199
204
|
const node = path[i];
|
|
200
205
|
|
|
201
206
|
if (is_function_node(node)) {
|
|
202
|
-
if (
|
|
203
|
-
return
|
|
207
|
+
if (node.metadata?.native_tsrx_function) {
|
|
208
|
+
return node;
|
|
204
209
|
}
|
|
205
210
|
if (!includes_functions) {
|
|
206
211
|
return;
|
package/src/utils/builders.js
CHANGED
|
@@ -604,6 +604,47 @@ export function ts_type_literal(members, loc_info) {
|
|
|
604
604
|
return set_location(node, loc_info);
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
+
/**
|
|
608
|
+
* `<left>.<right>` — the qualified entity name of a type reference or of an
|
|
609
|
+
* import-equals module reference.
|
|
610
|
+
*
|
|
611
|
+
* @param {AST.EntityName} left
|
|
612
|
+
* @param {AST.Identifier} right
|
|
613
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
614
|
+
* @returns {AST.TSQualifiedName}
|
|
615
|
+
*/
|
|
616
|
+
export function ts_qualified_name(left, right, loc_info) {
|
|
617
|
+
const node = /** @type {AST.TSQualifiedName} */ ({
|
|
618
|
+
type: 'TSQualifiedName',
|
|
619
|
+
left,
|
|
620
|
+
right,
|
|
621
|
+
metadata: { path: [] },
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
return set_location(node, loc_info);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* `import <id> = <module_reference>;` — the alias form that keeps every meaning
|
|
629
|
+
* (value, type, namespace) of the referenced binding.
|
|
630
|
+
*
|
|
631
|
+
* @param {AST.Identifier} id
|
|
632
|
+
* @param {AST.EntityName | AST.TSExternalModuleReference} module_reference
|
|
633
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
634
|
+
* @returns {AST.TSStatement<AST.TSImportEqualsDeclaration>}
|
|
635
|
+
*/
|
|
636
|
+
export function ts_import_equals(id, module_reference, loc_info) {
|
|
637
|
+
const node = /** @type {AST.TSStatement<AST.TSImportEqualsDeclaration>} */ ({
|
|
638
|
+
type: 'TSImportEqualsDeclaration',
|
|
639
|
+
id,
|
|
640
|
+
moduleReference: module_reference,
|
|
641
|
+
importKind: 'value',
|
|
642
|
+
metadata: { path: [] },
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
return set_location(node, loc_info);
|
|
646
|
+
}
|
|
647
|
+
|
|
607
648
|
/**
|
|
608
649
|
* `@types/estree`'s `Statement` union has no TS declarations in it, so the
|
|
609
650
|
* result is typed as both: a `type X = …` alias occupies a statement slot
|
|
@@ -612,10 +653,10 @@ export function ts_type_literal(members, loc_info) {
|
|
|
612
653
|
* @param {AST.Identifier} id
|
|
613
654
|
* @param {AST.Node} type_annotation
|
|
614
655
|
* @param {AST.NodeWithLocation} [loc_info]
|
|
615
|
-
* @returns {AST.
|
|
656
|
+
* @returns {AST.TSStatement<AST.TSTypeAliasDeclaration>}
|
|
616
657
|
*/
|
|
617
658
|
export function ts_type_alias(id, type_annotation, loc_info) {
|
|
618
|
-
const node = /** @type {AST.
|
|
659
|
+
const node = /** @type {AST.TSStatement<AST.TSTypeAliasDeclaration>} */ ({
|
|
619
660
|
type: 'TSTypeAliasDeclaration',
|
|
620
661
|
id,
|
|
621
662
|
typeParameters: undefined,
|
|
@@ -685,6 +726,29 @@ export function prop(kind, key, value, computed = false, shorthand = false) {
|
|
|
685
726
|
};
|
|
686
727
|
}
|
|
687
728
|
|
|
729
|
+
/**
|
|
730
|
+
* A property of an object *pattern* — its value is a binding target, not an
|
|
731
|
+
* expression, so it cannot be built with {@link prop}.
|
|
732
|
+
*
|
|
733
|
+
* @param {AST.Expression} key
|
|
734
|
+
* @param {AST.Pattern} value
|
|
735
|
+
* @param {boolean} computed
|
|
736
|
+
* @param {boolean} shorthand
|
|
737
|
+
* @returns {AST.AssignmentProperty}
|
|
738
|
+
*/
|
|
739
|
+
export function assignment_prop(key, value, computed = false, shorthand = false) {
|
|
740
|
+
return {
|
|
741
|
+
type: 'Property',
|
|
742
|
+
kind: 'init',
|
|
743
|
+
key,
|
|
744
|
+
value,
|
|
745
|
+
method: false,
|
|
746
|
+
shorthand,
|
|
747
|
+
computed,
|
|
748
|
+
metadata: { path: [] },
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
688
752
|
/**
|
|
689
753
|
* @param {AST.Expression | AST.PrivateIdentifier} key
|
|
690
754
|
* @param {AST.Expression | null | undefined} value
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rolldown plugins that make `.tsrx` modules visible to Vite's dependency
|
|
3
|
+
* scanner.
|
|
4
|
+
*
|
|
5
|
+
* The scanner runs through Rolldown without the main plugin pipeline, so on its
|
|
6
|
+
* own it cannot read `.tsrx` modules. Any npm dependency imported only from
|
|
7
|
+
* `.tsrx` files is then discovered at request time rather than at startup,
|
|
8
|
+
* which forces a re-optimize and a full page reload. Registering one of these
|
|
9
|
+
* under `optimizeDeps.rolldownOptions.plugins` teaches the scan pass to compile
|
|
10
|
+
* `.tsrx` modules so their imports are crawled up front.
|
|
11
|
+
*
|
|
12
|
+
* Which factory to use depends on how the host plugin exposes `.tsrx` modules:
|
|
13
|
+
*
|
|
14
|
+
* - {@link createDepScanTransformPlugin} for plugins that transform `.tsrx`
|
|
15
|
+
* ids directly. Those plugins must also list the extension in
|
|
16
|
+
* `optimizeDeps.extensions`, because the scanner externalizes anything that
|
|
17
|
+
* is not a known JS type before a plugin gets a chance to run.
|
|
18
|
+
* - {@link createDepScanLoadPlugin} for plugins that rewrite `.tsrx` ids to a
|
|
19
|
+
* virtual `<path>.tsx` form. The scanner accepts those on extension alone, so
|
|
20
|
+
* no `optimizeDeps.extensions` entry is needed, but it never calls Vite
|
|
21
|
+
* `load()` hooks for virtual ids.
|
|
22
|
+
*
|
|
23
|
+
* Both swallow compile failures. Vite responds to a scan error by skipping
|
|
24
|
+
* pre-bundling for the whole project, so a single malformed file would
|
|
25
|
+
* otherwise cost every other dependency its pre-bundle. Handing back an empty
|
|
26
|
+
* module keeps the rest of the graph crawlable and leaves the error to the host
|
|
27
|
+
* plugin's own transform, which reports it at request time where it can be
|
|
28
|
+
* surfaced properly.
|
|
29
|
+
*
|
|
30
|
+
* @import { DepScanCompile, DepScanLoadPlugin, DepScanTransformPlugin } from '../../types/vite/dep-scan.js'
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render `imports` as a side-effect import prelude. Used for runtime modules
|
|
37
|
+
* that the host plugin's own output depends on but that the scan pass would
|
|
38
|
+
* otherwise miss — a JSX runtime, for instance, which the scanner's own JSX
|
|
39
|
+
* transform may resolve to a different specifier than the host plugin does.
|
|
40
|
+
*
|
|
41
|
+
* @param {string[] | undefined} imports
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function render_prelude(imports) {
|
|
45
|
+
if (imports === undefined || imports.length === 0) return '';
|
|
46
|
+
|
|
47
|
+
return imports.map((source) => `import ${JSON.stringify(source)};`).join('\n') + '\n';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Scan plugin for host plugins that transform `.tsrx` ids directly.
|
|
52
|
+
*
|
|
53
|
+
* @param {{
|
|
54
|
+
* name: string,
|
|
55
|
+
* filter: RegExp,
|
|
56
|
+
* compile: DepScanCompile,
|
|
57
|
+
* imports?: string[],
|
|
58
|
+
* moduleType?: string,
|
|
59
|
+
* }} options
|
|
60
|
+
* @returns {DepScanTransformPlugin}
|
|
61
|
+
*/
|
|
62
|
+
export function createDepScanTransformPlugin({
|
|
63
|
+
name,
|
|
64
|
+
filter,
|
|
65
|
+
compile,
|
|
66
|
+
imports,
|
|
67
|
+
moduleType = 'tsx',
|
|
68
|
+
}) {
|
|
69
|
+
const prelude = render_prelude(imports);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
transform: {
|
|
74
|
+
filter: { id: filter },
|
|
75
|
+
async handler(/** @type {string} */ code, /** @type {string} */ id) {
|
|
76
|
+
try {
|
|
77
|
+
const { code: compiled } = await compile(code, id);
|
|
78
|
+
return { code: prelude + compiled, moduleType };
|
|
79
|
+
} catch {
|
|
80
|
+
return { code: '', moduleType };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Scan plugin for host plugins that rewrite `.tsrx` ids to a virtual
|
|
89
|
+
* `<path>.tsx` form, which the scanner resolves but never loads.
|
|
90
|
+
*
|
|
91
|
+
* @param {{
|
|
92
|
+
* name: string,
|
|
93
|
+
* isVirtual: (id: string) => boolean,
|
|
94
|
+
* toRealPath: (id: string) => string,
|
|
95
|
+
* compile: DepScanCompile,
|
|
96
|
+
* imports?: string[],
|
|
97
|
+
* moduleType?: string,
|
|
98
|
+
* }} options
|
|
99
|
+
* @returns {DepScanLoadPlugin}
|
|
100
|
+
*/
|
|
101
|
+
export function createDepScanLoadPlugin({
|
|
102
|
+
name,
|
|
103
|
+
isVirtual,
|
|
104
|
+
toRealPath,
|
|
105
|
+
compile,
|
|
106
|
+
imports,
|
|
107
|
+
moduleType = 'tsx',
|
|
108
|
+
}) {
|
|
109
|
+
const prelude = render_prelude(imports);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
async load(/** @type {string} */ id) {
|
|
114
|
+
// Both callbacks are written against plain paths, so drop any query
|
|
115
|
+
// suffix once here rather than leaving each caller to remember.
|
|
116
|
+
const path = id.split('?')[0];
|
|
117
|
+
|
|
118
|
+
if (!isVirtual(path)) return null;
|
|
119
|
+
|
|
120
|
+
const real_path = toRealPath(path);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// The read is inside the try along with the compile: a virtual id
|
|
124
|
+
// pointing at a file that has since moved is just as survivable as
|
|
125
|
+
// one that fails to parse, and the host plugin's own `load()` will
|
|
126
|
+
// report either at request time.
|
|
127
|
+
const source = await readFile(real_path, 'utf-8');
|
|
128
|
+
const { code } = await compile(source, real_path);
|
|
129
|
+
return { code: prelude + code, moduleType };
|
|
130
|
+
} catch {
|
|
131
|
+
return { code: '', moduleType };
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|