@tsrx/core 0.1.9 → 0.1.10

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/src/index.js CHANGED
@@ -236,6 +236,7 @@ export {
236
236
 
237
237
  // Analyze
238
238
  export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
239
+ export { prune_css as pruneCss } from './analyze/prune.js';
239
240
  export {
240
241
  CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
241
242
  COMPONENT_DO_WHILE_STATEMENT_ERROR,
@@ -5,6 +5,8 @@
5
5
  import { walk } from 'zimmerframe';
6
6
  import { print } from 'esrap';
7
7
  import { error } from '../../errors.js';
8
+ import { analyze_css } from '../../analyze/css-analyze.js';
9
+ import { prune_css } from '../../analyze/prune.js';
8
10
  import {
9
11
  ensure_function_metadata,
10
12
  in_jsx_child_context,
@@ -36,7 +38,11 @@ import {
36
38
  import * as b from '../../utils/builders.js';
37
39
  import { apply_lazy_transforms, preallocate_lazy_ids } from '../lazy.js';
38
40
  import { find_first_top_level_await_in_component_body } from '../await.js';
39
- import { prepare_stylesheet_for_render, annotate_component_with_hash } from '../scoping.js';
41
+ import {
42
+ prepare_stylesheet_for_render,
43
+ annotate_component_with_hash,
44
+ is_style_element,
45
+ } from '../scoping.js';
40
46
  import {
41
47
  validate_class_component_declarations,
42
48
  validate_component_loop_break_statement,
@@ -426,12 +432,14 @@ export function createJsxTransform(platform) {
426
432
 
427
433
  const css = as_any.css;
428
434
  if (css) {
435
+ apply_css_definition_metadata(as_any, css);
429
436
  stylesheets.push(css);
430
437
  const hash = css.hash;
431
438
  annotate_component_with_hash(
432
439
  as_any,
433
440
  hash,
434
441
  platform.jsx.rewriteClassAttr ? 'className' : 'class',
442
+ transform_context.typeOnly,
435
443
  );
436
444
  }
437
445
  return next(state);
@@ -609,6 +617,73 @@ export function createJsxTransform(platform) {
609
617
  return transform;
610
618
  }
611
619
 
620
+ /**
621
+ * Attach selector-location metadata used by editor definitions/hover before
622
+ * the shared scoping pass mutates class attributes with the component hash.
623
+ *
624
+ * @param {any} component
625
+ * @param {any} css
626
+ * @returns {void}
627
+ */
628
+ function apply_css_definition_metadata(component, css) {
629
+ analyze_css(css);
630
+
631
+ const metadata = component.metadata || (component.metadata = { path: [] });
632
+ const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
633
+ const top_scoped_classes = metadata.topScopedClasses || new Map();
634
+ const elements = collect_css_prunable_elements(component.body || []);
635
+
636
+ for (const element of elements) {
637
+ prune_css(css, element, style_classes, top_scoped_classes);
638
+ }
639
+
640
+ if (top_scoped_classes.size > 0) {
641
+ metadata.topScopedClasses = top_scoped_classes;
642
+ }
643
+ }
644
+
645
+ /**
646
+ * @param {any} value
647
+ * @param {any[]} [elements]
648
+ * @returns {any[]}
649
+ */
650
+ function collect_css_prunable_elements(value, elements = []) {
651
+ if (!value || typeof value !== 'object') {
652
+ return elements;
653
+ }
654
+
655
+ if (Array.isArray(value)) {
656
+ for (const child of value) {
657
+ collect_css_prunable_elements(child, elements);
658
+ }
659
+ return elements;
660
+ }
661
+
662
+ if (
663
+ value.type === 'FunctionDeclaration' ||
664
+ value.type === 'FunctionExpression' ||
665
+ value.type === 'ArrowFunctionExpression' ||
666
+ value.type === 'Component'
667
+ ) {
668
+ return elements;
669
+ }
670
+
671
+ if (value.type === 'Element') {
672
+ if (!is_style_element(value)) {
673
+ elements.push(value);
674
+ }
675
+ }
676
+
677
+ for (const key of Object.keys(value)) {
678
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
679
+ continue;
680
+ }
681
+ collect_css_prunable_elements(value[key], elements);
682
+ }
683
+
684
+ return elements;
685
+ }
686
+
612
687
  /**
613
688
  * Detect a top-level `"use server"` directive. Used by platforms whose
614
689
  * validation rule requires the directive to enable top-level `await`
@@ -5547,6 +5622,8 @@ export function to_jsx_attribute(attr, transform_context) {
5547
5622
  attr_name.name === 'class'
5548
5623
  ) {
5549
5624
  attr_name = set_loc(b.id('className'), attr.name);
5625
+ attr_name.metadata.source_name = 'class';
5626
+ attr_name.metadata.source_length = 'class'.length;
5550
5627
  }
5551
5628
 
5552
5629
  const name =
@@ -95,9 +95,15 @@ function is_composite_jsx_element(node) {
95
95
  * @param {any} node
96
96
  * @param {string} hash
97
97
  * @param {'class' | 'className'} [jsx_class_attr_name='class']
98
+ * @param {boolean} [preserve_style_elements=false]
98
99
  * @returns {any}
99
100
  */
100
- export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
101
+ export function annotate_with_hash(
102
+ node,
103
+ hash,
104
+ jsx_class_attr_name = 'class',
105
+ preserve_style_elements = false,
106
+ ) {
101
107
  if (!node || typeof node !== 'object') return node;
102
108
  if (
103
109
  node.type === 'Component' ||
@@ -109,13 +115,19 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
109
115
  }
110
116
 
111
117
  if (node.type === 'Element') {
118
+ if (preserve_style_elements && is_style_element(node)) {
119
+ node.children = [];
120
+ return node;
121
+ }
112
122
  if (!is_style_element(node) && !is_composite_element(node)) {
113
123
  add_hash_class(node, hash);
114
124
  }
115
125
  if (Array.isArray(node.children)) {
116
126
  node.children = node.children
117
- .filter((/** @type {any} */ child) => !is_style_element(child))
118
- .map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
127
+ .filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
128
+ .map((/** @type {any} */ child) =>
129
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
130
+ );
119
131
  }
120
132
  return node;
121
133
  }
@@ -126,7 +138,7 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
126
138
  }
127
139
  if (Array.isArray(node.children)) {
128
140
  node.children = node.children.map((/** @type {any} */ child) =>
129
- annotate_with_hash(child, hash, jsx_class_attr_name),
141
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
130
142
  );
131
143
  }
132
144
  return node;
@@ -140,10 +152,10 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
140
152
  const value = node[key];
141
153
  if (Array.isArray(value)) {
142
154
  node[key] = value.map((/** @type {any} */ child) =>
143
- annotate_with_hash(child, hash, jsx_class_attr_name),
155
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
144
156
  );
145
157
  } else if (value && typeof value === 'object') {
146
- node[key] = annotate_with_hash(value, hash, jsx_class_attr_name);
158
+ node[key] = annotate_with_hash(value, hash, jsx_class_attr_name, preserve_style_elements);
147
159
  }
148
160
  }
149
161
 
@@ -154,14 +166,22 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
154
166
  * @param {any} component
155
167
  * @param {string} hash
156
168
  * @param {'class' | 'className'} [jsx_class_attr_name='class']
169
+ * @param {boolean} [preserve_style_elements=false]
157
170
  * @returns {void}
158
171
  */
159
- export function annotate_component_with_hash(component, hash, jsx_class_attr_name = 'class') {
172
+ export function annotate_component_with_hash(
173
+ component,
174
+ hash,
175
+ jsx_class_attr_name = 'class',
176
+ preserve_style_elements = false,
177
+ ) {
160
178
  /** @type {any[]} */
161
179
  const body = component.body;
162
180
  component.body = body
163
- .filter((/** @type {any} */ child) => !is_style_element(child))
164
- .map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
181
+ .filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
182
+ .map((/** @type {any} */ child) =>
183
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
184
+ );
165
185
  }
166
186
 
167
187
  /**
@@ -198,7 +218,8 @@ export function add_hash_class(element, hash) {
198
218
 
199
219
  if (value.type === 'Literal' && typeof value.value === 'string') {
200
220
  const merged = `${value.value} ${hash}`;
201
- existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
221
+ value.value = merged;
222
+ value.raw = JSON.stringify(merged);
202
223
  return;
203
224
  }
204
225
 
@@ -233,7 +254,11 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
233
254
  existing.name = {
234
255
  type: 'JSXIdentifier',
235
256
  name: jsx_class_attr_name,
236
- metadata: existing.name.metadata || { path: [] },
257
+ metadata: {
258
+ ...(existing.name.metadata || { path: [] }),
259
+ source_name: existing.name.name,
260
+ source_length: existing.name.name.length,
261
+ },
237
262
  };
238
263
  }
239
264
 
@@ -245,7 +270,8 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
245
270
 
246
271
  if (value.type === 'Literal' && typeof value.value === 'string') {
247
272
  const merged = `${value.value} ${hash}`;
248
- existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
273
+ value.value = merged;
274
+ value.raw = JSON.stringify(merged);
249
275
  return;
250
276
  }
251
277
 
@@ -162,14 +162,14 @@ function visit_source_ast(ast, src_line_offsets, { regions, css_element_info })
162
162
  start: cssStart,
163
163
  end: cssEnd,
164
164
  content: node.css,
165
- id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id}`),
165
+ id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id++}`),
166
166
  });
167
167
  }
168
168
 
169
169
  context.next();
170
170
  },
171
171
  Attribute(node, context) {
172
- const element = context.path?.find((n) => n.type === 'Element');
172
+ const element = context.path?.findLast((n) => n.type === 'Element');
173
173
  if (element?.metadata?.css?.scopedClasses) {
174
174
  // we don't need to check is_element_dom_element(node)
175
175
  // since scopedClasses are added during pruning only to DOM elements
@@ -568,10 +568,11 @@ export function convert_source_map_to_mappings(
568
568
  if (node.loc && node.name) {
569
569
  /** @type {Token} */
570
570
  const token = {
571
- source: node.metadata?.is_capitalized ? node.metadata.source_name : node.name,
571
+ source: node.metadata?.source_name ?? node.name,
572
572
  generated: node.name,
573
573
  loc: node.loc,
574
574
  metadata: {},
575
+ sourceLength: node.metadata?.source_length,
575
576
  };
576
577
  if (node.metadata?.disable_verification) {
577
578
  token.mappingData = { ...mapping_data, verification: false };
@@ -708,16 +709,24 @@ export function convert_source_map_to_mappings(
708
709
  visit(node.value);
709
710
  }
710
711
  } else {
712
+ const is_class_attribute =
713
+ node.name?.type === 'JSXIdentifier' &&
714
+ (node.name.name === 'class' || node.name.name === 'className');
711
715
  const attr =
712
- node.name.name === 'class' && node.value?.type === 'JSXExpressionContainer'
716
+ is_class_attribute && node.value?.type === 'JSXExpressionContainer'
713
717
  ? node.value.expression
714
718
  : node.value;
715
719
 
716
- const css = attr
717
- ? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
718
- : null;
720
+ const css =
721
+ is_class_attribute && attr
722
+ ? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
723
+ : null;
719
724
 
720
725
  if (attr && css) {
726
+ if (node.name) {
727
+ visit(node.name);
728
+ }
729
+
721
730
  // Extract class names from the attribute value
722
731
  const classes = extract_classes(
723
732
  attr,
@@ -861,7 +870,7 @@ export function convert_source_map_to_mappings(
861
870
  target_node,
862
871
  src_to_gen_map,
863
872
  gen_line_offsets,
864
- mapping_data_verify_only,
873
+ closing ? mapping_data_verify_only : mapping_data_verify_complete,
865
874
  );
866
875
  // The generated code includes a semicolon after the closing or self-closed tag
867
876
  // We're extending the mapping to include the semicolon