@tsrx/core 0.1.20 → 0.1.22

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.
@@ -15,8 +15,7 @@
15
15
  * change what earlier children rendered.
16
16
  *
17
17
  * The `is_jsx_child` predicate is target-specific — each target recognizes
18
- * a different set of JSX-bearing node types (Ripple `Element`, `Text`,
19
- * `TSRXExpression`, etc. plus plain JSX nodes).
18
+ * its JSX-bearing child nodes and template-control expressions.
20
19
  *
21
20
  * @param {any[]} body_nodes
22
21
  * @param {(node: any) => boolean} is_jsx_child
@@ -37,45 +37,14 @@ export function prepare_stylesheet_for_render(stylesheet) {
37
37
  * @returns {boolean}
38
38
  */
39
39
  export function is_style_element(node) {
40
- return (
41
- node &&
42
- node.type === 'Element' &&
43
- node.id &&
44
- node.id.type === 'Identifier' &&
45
- node.id.name === 'style'
46
- );
47
- }
48
-
49
- /**
50
- * @param {any} node
51
- * @returns {boolean}
52
- */
53
- export function is_composite_element(node) {
54
- if (!node || node.type !== 'Element' || !node.id) {
55
- return false;
56
- }
57
-
58
- if (node.id.type === 'Identifier') {
59
- return /^[A-Z]/.test(node.id.name);
60
- }
61
-
62
- return node.id.type === 'MemberExpression';
63
- }
64
-
65
- /**
66
- * @param {any} node
67
- * @returns {boolean}
68
- */
69
- function is_style_jsx_element(node) {
70
- const name = node?.openingElement?.name;
71
- return node?.type === 'JSXElement' && name?.type === 'JSXIdentifier' && name.name === 'style';
40
+ return !!node && node.type === 'JSXStyleElement';
72
41
  }
73
42
 
74
43
  /**
75
44
  * @param {any} node
76
45
  * @returns {boolean}
77
46
  */
78
- function is_composite_jsx_element(node) {
47
+ export function is_composite_jsx_element(node) {
79
48
  const name = node?.openingElement?.name;
80
49
  if (node?.type !== 'JSXElement' || !name) {
81
50
  return false;
@@ -89,7 +58,7 @@ function is_composite_jsx_element(node) {
89
58
  }
90
59
 
91
60
  /**
92
- * Recursively walk `Element` nodes within a TSRX fragment and add the hash
61
+ * Recursively walk native JSX nodes within a TSRX fragment and add the hash
93
62
  * class name so scope-qualified selectors (e.g. `.foo.hash`) match.
94
63
  *
95
64
  * @param {any} node
@@ -113,34 +82,26 @@ export function annotate_with_hash(
113
82
  return node;
114
83
  }
115
84
 
116
- if (node.type === 'Element') {
117
- if (preserve_style_elements && is_style_element(node)) {
118
- node.children = [];
119
- return node;
120
- }
121
- if (!is_style_element(node) && !is_composite_element(node)) {
122
- add_hash_class(node, hash, jsx_class_attr_name);
85
+ if (node.type === 'JSXElement') {
86
+ if (!is_composite_jsx_element(node)) {
87
+ add_hash_class_to_jsx_element(node, hash, jsx_class_attr_name);
123
88
  }
124
89
  if (Array.isArray(node.children)) {
125
90
  node.children = node.children
126
- .filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
127
91
  .map((/** @type {any} */ child) =>
128
92
  annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
129
- );
93
+ )
94
+ .filter(Boolean);
130
95
  }
131
96
  return node;
132
97
  }
133
98
 
134
- if (node.type === 'JSXElement') {
135
- if (!is_style_jsx_element(node) && !is_composite_jsx_element(node)) {
136
- add_hash_class_to_jsx_element(node, hash, jsx_class_attr_name);
137
- }
138
- if (Array.isArray(node.children)) {
139
- node.children = node.children.map((/** @type {any} */ child) =>
140
- annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
141
- );
99
+ if (node.type === 'JSXStyleElement') {
100
+ if (preserve_style_elements) {
101
+ node.children = [];
102
+ return node;
142
103
  }
143
- return node;
104
+ return null;
144
105
  }
145
106
 
146
107
  for (const key of Object.keys(node)) {
@@ -192,25 +153,22 @@ export function annotate_component_with_hash(
192
153
  * @returns {void}
193
154
  */
194
155
  export function add_hash_class(element, hash, class_attr_name = 'class') {
195
- const attrs = element.attributes || (element.attributes = []);
156
+ const attrs = element.openingElement.attributes;
196
157
  const existing = attrs.find(
197
158
  (/** @type {any} */ a) =>
198
- a.type === 'Attribute' &&
159
+ a.type === 'JSXAttribute' &&
199
160
  a.name &&
200
- a.name.type === 'Identifier' &&
161
+ a.name.type === 'JSXIdentifier' &&
201
162
  (a.name.name === 'class' || a.name.name === 'className'),
202
163
  );
203
164
 
204
165
  if (!existing) {
205
- attrs.push({
206
- type: 'Attribute',
207
- name: b.id(class_attr_name),
208
- value: { type: 'Literal', value: hash, raw: JSON.stringify(hash) },
209
- });
166
+ attrs.push(b.jsx_attribute(b.jsx_id(class_attr_name), b.literal(hash)));
210
167
  return;
211
168
  }
212
169
 
213
- const value = existing.value;
170
+ const value =
171
+ existing.value?.type === 'JSXExpressionContainer' ? existing.value.expression : existing.value;
214
172
  if (!value) {
215
173
  existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
216
174
  return;
@@ -224,8 +182,9 @@ export function add_hash_class(element, hash, class_attr_name = 'class') {
224
182
  }
225
183
 
226
184
  // Dynamic expression. Concatenate at runtime via template literal.
227
- const expression = value.type === 'JSXExpressionContainer' ? value.expression : value;
228
- existing.value = b.template([b.quasi('', false), b.quasi(` ${hash}`, true)], [expression]);
185
+ existing.value = b.jsx_expression_container(
186
+ b.template([b.quasi('', false), b.quasi(` ${hash}`, true)], [value]),
187
+ );
229
188
  }
230
189
 
231
190
  /**
@@ -247,12 +206,14 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
247
206
  const hash_literal = b.literal(hash);
248
207
  /** @type {any} */ (hash_literal).raw = JSON.stringify(hash);
249
208
  attrs.push(b.jsx_attribute(b.jsx_id(jsx_class_attr_name), hash_literal));
209
+ element.attributes = attrs;
250
210
  return;
251
211
  }
252
212
 
253
213
  const value = existing.value;
254
214
  if (!value) {
255
215
  existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
216
+ element.attributes = attrs;
256
217
  return;
257
218
  }
258
219
 
@@ -260,6 +221,7 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
260
221
  const merged = `${value.value} ${hash}`;
261
222
  value.value = merged;
262
223
  value.raw = JSON.stringify(merged);
224
+ element.attributes = attrs;
263
225
  return;
264
226
  }
265
227
 
@@ -267,4 +229,5 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
267
229
  existing.value = b.jsx_expression_container(
268
230
  b.template([b.quasi('', false), b.quasi(` ${hash}`, true)], [expression]),
269
231
  );
232
+ element.attributes = attrs;
270
233
  }
@@ -55,6 +55,7 @@ import {
55
55
  build_line_offsets,
56
56
  get_mapping_from_node,
57
57
  } from '../source-map-utils.js';
58
+ import { should_preserve_comment } from '../comment-utils.js';
58
59
 
59
60
  const LAZY_PARAM_IDENTIFIER_REGEX = /^__lazy\d+$/;
60
61
 
@@ -133,9 +134,8 @@ function get_style_region_id(hash, fallback) {
133
134
  function visit_source_ast(ast, src_line_offsets, { regions, css_element_info }) {
134
135
  let region_id = 0;
135
136
  walk(ast, null, {
136
- Element(node, context) {
137
- // Check if this is a style element with CSS content
138
- if (node.id?.type === 'Identifier' && node.id?.name === 'style' && node.css) {
137
+ JSXStyleElement(node, context) {
138
+ if (node.css) {
139
139
  const openLoc = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
140
140
  node.openingElement
141
141
  ).loc;
@@ -156,8 +156,10 @@ function visit_source_ast(ast, src_line_offsets, { regions, css_element_info })
156
156
 
157
157
  context.next();
158
158
  },
159
- Attribute(node, context) {
160
- const element = context.path?.findLast((n) => n.type === 'Element');
159
+ JSXAttribute(node, context) {
160
+ const element = context.path?.findLast(
161
+ (n) => n.type === 'JSXElement' && n.metadata?.native_tsrx,
162
+ );
161
163
  if (element?.metadata?.css?.scopedClasses) {
162
164
  // we don't need to check is_element_dom_element(node)
163
165
  // since scopedClasses are added during pruning only to DOM elements
@@ -430,6 +432,42 @@ export function convert_source_map_to_mappings(
430
432
  }
431
433
  }
432
434
 
435
+ /** @type {Set<string>} */
436
+ const mapped_comments = new Set();
437
+
438
+ /**
439
+ * @param {any} node
440
+ * @returns {void}
441
+ */
442
+ function add_preserved_comment_mappings(node) {
443
+ for (const key of ['leadingComments', 'trailingComments', 'innerComments', 'comments']) {
444
+ const comments = node?.[key];
445
+ if (!Array.isArray(comments)) continue;
446
+
447
+ for (const comment of comments) {
448
+ if (!comment?.loc || !should_preserve_comment(comment)) continue;
449
+
450
+ const comment_key = `${comment.start}:${comment.end}`;
451
+ if (mapped_comments.has(comment_key)) continue;
452
+ mapped_comments.add(comment_key);
453
+
454
+ try {
455
+ mappings.push(
456
+ get_mapping_from_node(
457
+ comment,
458
+ src_to_gen_map,
459
+ gen_line_offsets,
460
+ mapping_data_verify_only,
461
+ ),
462
+ );
463
+ } catch {
464
+ // Comments that were not emitted in generated TSX have no source-map
465
+ // segment. They should not produce Volar mappings.
466
+ }
467
+ }
468
+ }
469
+ }
470
+
433
471
  /**
434
472
  * @param {AST.Literal} node
435
473
  */
@@ -444,6 +482,8 @@ export function convert_source_map_to_mappings(
444
482
 
445
483
  walk(ast, null, {
446
484
  _(node, { visit }) {
485
+ add_preserved_comment_mappings(node);
486
+
447
487
  // Collect key node types: Identifiers, Literals, and JSX Elements
448
488
  if (node.type === 'Identifier') {
449
489
  // Only create mappings for identifiers with location info (from source)
@@ -738,6 +778,14 @@ export function convert_source_map_to_mappings(
738
778
  } else if (node.type === 'JSXText') {
739
779
  // Text content, no tokens to collect
740
780
  return;
781
+ } else if (node.type === 'JSXCodeBlock') {
782
+ for (const statement of node.body) {
783
+ visit(statement);
784
+ }
785
+ if (node.render) {
786
+ visit(node.render);
787
+ }
788
+ return;
741
789
  } else if (node.type === 'JSXElement') {
742
790
  // Manually visit in source order: opening element, children, closing element
743
791
 
@@ -68,7 +68,7 @@ export function collect_style_ref_attributes(node, refs = []) {
68
68
  }
69
69
 
70
70
  if (is_style_element(node)) {
71
- for (const attr of node.attributes || []) {
71
+ for (const attr of node.openingElement?.attributes || []) {
72
72
  if (is_ref_attribute(attr) && attr.value) {
73
73
  refs.push(attr);
74
74
  }
@@ -224,10 +224,7 @@ function get_ref_attribute_expression(attr) {
224
224
  */
225
225
  function is_ref_attribute(attr) {
226
226
  return (
227
- (attr?.type === 'Attribute' && attr.name?.type === 'Identifier' && attr.name.name === 'ref') ||
228
- (attr?.type === 'JSXAttribute' &&
229
- attr.name?.type === 'JSXIdentifier' &&
230
- attr.name.name === 'ref')
227
+ attr?.type === 'JSXAttribute' && attr.name?.type === 'JSXIdentifier' && attr.name.name === 'ref'
231
228
  );
232
229
  }
233
230
 
@@ -236,12 +233,7 @@ function is_ref_attribute(attr) {
236
233
  * @returns {boolean}
237
234
  */
238
235
  function is_style_element(node) {
239
- return !!(
240
- node &&
241
- node.type === 'Element' &&
242
- node.id?.type === 'Identifier' &&
243
- node.id.name === 'style'
244
- );
236
+ return !!node && node.type === 'JSXStyleElement';
245
237
  }
246
238
 
247
239
  /**
@@ -283,7 +283,6 @@ export function export_builder(
283
283
  * @param {AST.BlockStatement} body
284
284
  * @param {boolean} [async]
285
285
  * @param {AST.TSTypeParameterDeclaration} [type_parameters]
286
- * @param
287
286
  * @returns {AST.FunctionDeclaration}
288
287
  */
289
288
  export function function_declaration(id, params, body, async = false, type_parameters) {
@@ -1125,7 +1124,7 @@ export function jsx_attribute(name, value = null, shorthand = false, loc_info) {
1125
1124
 
1126
1125
  /**
1127
1126
  * Build a fresh `JSXOpeningElement`. For elements derived from an existing
1128
- * Element node, prefer `jsx_element` which spreads from the source.
1127
+ * JSX element node, prefer `jsx_element` which spreads from the source.
1129
1128
  *
1130
1129
  * @param {ESTreeJSX.JSXOpeningElement['name']} name
1131
1130
  * @param {ESTreeJSX.JSXOpeningElement['attributes']} [attributes]
@@ -1200,7 +1199,7 @@ export function jsx_element_fresh(
1200
1199
  }
1201
1200
 
1202
1201
  /**
1203
- * @param {AST.Element} node
1202
+ * @param {ESTreeJSX.JSXElement} node
1204
1203
  * @param {ESTreeJSX.JSXOpeningElement['attributes']} attributes
1205
1204
  * @param {ESTreeJSX.JSXElement['children']} children
1206
1205
  * @returns {ESTreeJSX.JSXElement}