@tsrx/core 0.1.17 → 0.1.19

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.
@@ -119,7 +119,7 @@ export function annotate_with_hash(
119
119
  return node;
120
120
  }
121
121
  if (!is_style_element(node) && !is_composite_element(node)) {
122
- add_hash_class(node, hash);
122
+ add_hash_class(node, hash, jsx_class_attr_name);
123
123
  }
124
124
  if (Array.isArray(node.children)) {
125
125
  node.children = node.children
@@ -184,13 +184,14 @@ export function annotate_component_with_hash(
184
184
  }
185
185
 
186
186
  /**
187
- * Ensure the element carries a `class` attribute containing the scoping hash.
187
+ * Ensure the element carries a class attribute containing the scoping hash.
188
188
  *
189
189
  * @param {any} element
190
190
  * @param {string} hash
191
+ * @param {'class' | 'className'} [class_attr_name='class']
191
192
  * @returns {void}
192
193
  */
193
- export function add_hash_class(element, hash) {
194
+ export function add_hash_class(element, hash, class_attr_name = 'class') {
194
195
  const attrs = element.attributes || (element.attributes = []);
195
196
  const existing = attrs.find(
196
197
  (/** @type {any} */ a) =>
@@ -203,7 +204,7 @@ export function add_hash_class(element, hash) {
203
204
  if (!existing) {
204
205
  attrs.push({
205
206
  type: 'Attribute',
206
- name: b.id('class'),
207
+ name: b.id(class_attr_name),
207
208
  value: { type: 'Literal', value: hash, raw: JSON.stringify(hash) },
208
209
  });
209
210
  return;
@@ -249,18 +250,6 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
249
250
  return;
250
251
  }
251
252
 
252
- if (existing.name.name !== jsx_class_attr_name) {
253
- existing.name = {
254
- type: 'JSXIdentifier',
255
- name: jsx_class_attr_name,
256
- metadata: {
257
- ...(existing.name.metadata || { path: [] }),
258
- source_name: existing.name.name,
259
- source_length: existing.name.name.length,
260
- },
261
- };
262
- }
263
-
264
253
  const value = existing.value;
265
254
  if (!value) {
266
255
  existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
@@ -3,6 +3,8 @@
3
3
  import * as b from '../utils/builders.js';
4
4
  import { clone_expression_node, clone_identifier } from './jsx/ast-builders.js';
5
5
 
6
+ const regex_backslash_and_following_character = /\\(.)/g;
7
+
6
8
  /**
7
9
  * @typedef {{
8
10
  * allowMutableRefTarget?: boolean;
@@ -19,7 +21,7 @@ import { clone_expression_node, clone_identifier } from './jsx/ast-builders.js';
19
21
  export function create_style_class_map(component, css) {
20
22
  const hash = css?.hash ?? null;
21
23
  const top_scoped_classes = /** @type {Map<string, any>} */ (
22
- component?.metadata?.topScopedClasses ?? new Map()
24
+ component?.metadata?.topScopedClasses ?? collect_style_class_map_entries(css)
23
25
  );
24
26
  const class_names = [...top_scoped_classes.keys()].sort();
25
27
 
@@ -30,6 +32,28 @@ export function create_style_class_map(component, css) {
30
32
  );
31
33
  }
32
34
 
35
+ /**
36
+ * @param {any} css
37
+ * @returns {AST.ObjectExpression}
38
+ */
39
+ export function create_style_class_map_from_stylesheet(css) {
40
+ return create_style_class_map(
41
+ { metadata: { topScopedClasses: collect_style_class_map_entries(css) } },
42
+ css,
43
+ );
44
+ }
45
+
46
+ /**
47
+ * @param {any} style_element
48
+ * @returns {any | null}
49
+ */
50
+ export function get_style_element_stylesheet(style_element) {
51
+ return (
52
+ style_element?.children?.find?.((/** @type {any} */ child) => child.type === 'StyleSheet') ??
53
+ null
54
+ );
55
+ }
56
+
33
57
  /**
34
58
  * @param {any} node
35
59
  * @param {any[]} [refs]
@@ -233,3 +257,70 @@ function is_function_or_class_boundary(node) {
233
257
  node?.type === 'ClassExpression'
234
258
  );
235
259
  }
260
+
261
+ /**
262
+ * @param {any} css
263
+ * @returns {Map<string, any>}
264
+ */
265
+ function collect_style_class_map_entries(css) {
266
+ const entries = new Map();
267
+ collect_rule_class_map_entries(css, entries);
268
+ return entries;
269
+ }
270
+
271
+ /**
272
+ * @param {any} node
273
+ * @param {Map<string, any>} entries
274
+ * @returns {void}
275
+ */
276
+ function collect_rule_class_map_entries(node, entries) {
277
+ if (!node || typeof node !== 'object') return;
278
+
279
+ if (Array.isArray(node)) {
280
+ for (const child of node) collect_rule_class_map_entries(child, entries);
281
+ return;
282
+ }
283
+
284
+ if (node.type === 'ComplexSelector') {
285
+ const class_selector = get_standalone_class_selector(node);
286
+ if (class_selector) {
287
+ const name = class_selector.name.replace(regex_backslash_and_following_character, '$1');
288
+ if (!entries.has(name)) {
289
+ entries.set(name, {
290
+ start: class_selector.start,
291
+ end: class_selector.end,
292
+ selector: class_selector,
293
+ });
294
+ }
295
+ }
296
+ }
297
+
298
+ if (is_function_or_class_boundary(node)) {
299
+ return;
300
+ }
301
+
302
+ for (const key of Object.keys(node)) {
303
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
304
+ continue;
305
+ }
306
+ collect_rule_class_map_entries(node[key], entries);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @param {any} complex_selector
312
+ * @returns {any | null}
313
+ */
314
+ function get_standalone_class_selector(complex_selector) {
315
+ if (complex_selector?.children?.length !== 1) return null;
316
+ const relative_selector = complex_selector.children[0];
317
+ if (
318
+ relative_selector?.metadata?.is_global ||
319
+ relative_selector?.metadata?.is_global_like ||
320
+ relative_selector?.selectors?.length !== 1
321
+ ) {
322
+ return null;
323
+ }
324
+ const selector = relative_selector.selectors[0];
325
+ return selector?.type === 'ClassSelector' ? selector : null;
326
+ }
package/types/index.d.ts CHANGED
@@ -26,6 +26,7 @@ export { createJsxTransform };
26
26
 
27
27
  export function collectStyleRefAttributes(node: any, refs?: any[]): any[];
28
28
  export function createStyleClassMap(component: any, css: any): AST.ObjectExpression;
29
+ export function createStyleClassMapFromStylesheet(css: any): AST.ObjectExpression;
29
30
  export function createStyleRefSetupStatements(
30
31
  refAttributes: any[],
31
32
  styleMap: AST.Expression,
@@ -35,6 +36,7 @@ export function createStyleRefSetupStatements(
35
36
  visitExpression?: (expression: AST.Expression) => AST.Expression;
36
37
  },
37
38
  ): AST.Statement[];
39
+ export function getStyleElementStylesheet(styleElement: any): any | null;
38
40
 
39
41
  /**
40
42
  * Compile error interface
@@ -100,11 +102,15 @@ interface BaseNodeMetaData {
100
102
  }
101
103
 
102
104
  interface FunctionMetaData extends BaseNodeMetaData {
105
+ native_tsrx?: boolean;
103
106
  native_tsrx_function?: boolean;
107
+ hook_split?: boolean;
104
108
  is_method?: boolean;
105
109
  tracked?: boolean;
106
110
  has_lazy_descendants?: boolean;
107
111
  synthetic_children?: boolean;
112
+ generated_helpers?: any[];
113
+ generated_statics?: any[];
108
114
  }
109
115
 
110
116
  // Strip parent, loc, and range from TSESTree nodes to match @sveltejs/acorn-typescript output
@@ -161,6 +167,13 @@ declare module 'estree' {
161
167
  };
162
168
  }
163
169
 
170
+ interface BlockStatement {
171
+ metadata: BaseNodeMetaData & {
172
+ hook_split_block?: boolean;
173
+ native_return_block?: boolean;
174
+ };
175
+ }
176
+
164
177
  type Accessibility = 'public' | 'protected' | 'private'; // missing in acorn-typescript types
165
178
  interface MethodDefinition {
166
179
  typeParameters?: TSTypeParameterDeclaration;
@@ -236,6 +249,7 @@ declare module 'estree' {
236
249
  }
237
250
 
238
251
  interface ExpressionMap {
252
+ Tsrx: Tsrx;
239
253
  Text: TextNode;
240
254
  JSXEmptyExpression: ESTreeJSX.JSXEmptyExpression;
241
255
  ParenthesizedExpression: ParenthesizedExpression;
@@ -341,7 +355,7 @@ declare module 'estree' {
341
355
  closingElement: ESTreeJSX.JSXClosingElement;
342
356
  }
343
357
 
344
- interface Tsrx extends AST.BaseNode {
358
+ interface Tsrx extends AST.BaseExpression {
345
359
  type: 'Tsrx';
346
360
  attributes: Array<any>;
347
361
  children: AST.Node[];
@@ -647,6 +661,12 @@ declare module 'estree-jsx' {
647
661
  };
648
662
  }
649
663
 
664
+ interface JSXOpeningElement {
665
+ metadata: BaseNodeMetaData & {
666
+ native_tsrx_pretransformed?: boolean;
667
+ };
668
+ }
669
+
650
670
  interface JSXExpressionContainer {
651
671
  text?: boolean;
652
672
  style?: boolean;
@@ -42,6 +42,7 @@ export interface JsxTransformContext {
42
42
  needs_for_of_iterable: boolean;
43
43
  needs_iteration_value_type: boolean;
44
44
  stylesheets: AST.CSS.StyleSheet[];
45
+ type_only_style_anchors: AST.Statement[];
45
46
  module_scoped_hook_components: boolean;
46
47
  helper_state: {
47
48
  base_name: string;
@@ -154,13 +155,6 @@ export interface JsxPlatformHooks {
154
155
  * state behaves like normal component state.
155
156
  */
156
157
  wrapHelperComponent?: (helperFn: any, helperId: any, ctx: any, sourceNode: any) => any;
157
- /**
158
- * Wrap an uppercase JavaScript function that returns native TSRX as a target
159
- * component. Vue uses this to turn `function App() { return <></>; }` into a
160
- * `defineVaporComponent(function App() { ... })` binding while lowercase
161
- * TSRX-returning callbacks stay plain functions.
162
- */
163
- wrapNativeFunctionComponent?: (fn: any, ctx: any, path: any[]) => any;
164
158
  /**
165
159
  * Emit hook-isolation helper components as unique module-scope declarations
166
160
  * instead of lazily creating and caching them from the parent component body.
@@ -354,10 +348,15 @@ export interface JsxPlatform {
354
348
 
355
349
  jsx: {
356
350
  /**
357
- * Rewrite Ripple's `class` attribute to React's `className`. React: true.
358
- * Preact and Solid accept `class` natively, so: false.
351
+ * Rewrite Ripple's `class` attribute to `className` for legacy targets
352
+ * that require it. First-party targets keep authored `class`.
359
353
  */
360
354
  rewriteClassAttr: boolean;
355
+ /**
356
+ * Attribute name to use when TSRX injects scoped CSS classes. This does
357
+ * not rewrite authored attributes.
358
+ */
359
+ classAttrName?: 'class' | 'className';
361
360
  /**
362
361
  * Accepted values of `kind` in `<tsx:kind>` compat blocks. React accepts
363
362
  * only `'react'`. Preact accepts both `'preact'` and `'react'`.