ripple 0.2.135 → 0.2.137

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.135",
6
+ "version": "0.2.137",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -81,6 +81,6 @@
81
81
  "typescript": "^5.9.2"
82
82
  },
83
83
  "peerDependencies": {
84
- "ripple": "0.2.135"
84
+ "ripple": "0.2.137"
85
85
  }
86
86
  }
@@ -29,6 +29,28 @@ function convert_from_jsx(node) {
29
29
  return node;
30
30
  }
31
31
 
32
+ const regex_whitespace_only = /\s/;
33
+
34
+ /**
35
+ * Skip whitespace characters without skipping comments.
36
+ * This is needed because Acorn's skipSpace() also skips comments, which breaks
37
+ * parsing in certain contexts. Updates parser position and line tracking.
38
+ * @param {acorn.Parser} parser
39
+ */
40
+ function skipWhitespace(parser) {
41
+ const originalStart = parser.start;
42
+ while (parser.start < parser.input.length && regex_whitespace_only.test(parser.input[parser.start])) {
43
+ parser.start++;
44
+ }
45
+ // Update line tracking if whitespace was skipped
46
+ if (parser.start !== originalStart) {
47
+ const lineInfo = acorn.getLineInfo(parser.input, parser.start);
48
+ parser.curLine = lineInfo.line;
49
+ parser.lineStart = parser.start - lineInfo.column;
50
+ parser.startLoc = lineInfo;
51
+ }
52
+ }
53
+
32
54
  function isWhitespaceTextNode(node) {
33
55
  if (!node || node.type !== 'Text') {
34
56
  return false;
@@ -710,6 +732,7 @@ function RipplePlugin(config) {
710
732
  this.exitScope();
711
733
 
712
734
  this.next();
735
+ skipWhitespace(this);
713
736
  this.finishNode(node, 'Component');
714
737
  this.awaitPos = 0;
715
738
 
@@ -1576,6 +1599,7 @@ function RipplePlugin(config) {
1576
1599
 
1577
1600
  this.#path.pop();
1578
1601
  this.next();
1602
+ skipWhitespace(this);
1579
1603
  return;
1580
1604
  }
1581
1605
  const node = this.parseElement();
@@ -1588,12 +1612,8 @@ function RipplePlugin(config) {
1588
1612
 
1589
1613
  // Ensure we're not in JSX context before recursing
1590
1614
  // This is important when elements are parsed at statement level
1591
- const tokContexts = this.acornTypeScript?.tokContexts;
1592
- if (tokContexts && this.curContext) {
1593
- const curContext = this.curContext();
1594
- if (curContext === tokContexts.tc_expr) {
1595
- this.context.pop();
1596
- }
1615
+ if (this.curContext() === this.acornTypeScript.tokContexts.tc_expr) {
1616
+ this.context.pop();
1597
1617
  }
1598
1618
  }
1599
1619
 
@@ -1643,6 +1663,7 @@ function RipplePlugin(config) {
1643
1663
  this.exitScope();
1644
1664
 
1645
1665
  this.next();
1666
+ skipWhitespace(this);
1646
1667
  this.finishNode(node, 'Component');
1647
1668
  this.awaitPos = 0;
1648
1669
 
@@ -662,7 +662,7 @@ const visitors = {
662
662
  [b.id('__compat')],
663
663
  needs_fragment
664
664
  ? b.call(
665
- '__compat._jsxs',
665
+ '__compat.jsxs',
666
666
  b.id('__compat.Fragment'),
667
667
  b.object([
668
668
  b.prop(
@@ -1156,11 +1156,15 @@ const visitors = {
1156
1156
  }),
1157
1157
  ];
1158
1158
 
1159
- return b.function(
1159
+ const func = b.function(
1160
1160
  node.id,
1161
1161
  node.params.map((param) => context.visit(param, { ...context.state, metadata })),
1162
1162
  b.block(body_statements),
1163
1163
  );
1164
+ // Mark that this function was originally a component
1165
+ func.metadata = { ...func.metadata, was_component: true };
1166
+ func.loc = node.loc; // Copy source location for Volar mappings
1167
+ return func;
1164
1168
  }
1165
1169
 
1166
1170
  let props = b.id('__props');
@@ -1189,7 +1193,7 @@ const visitors = {
1189
1193
  context.state.stylesheets.push(node.css);
1190
1194
  }
1191
1195
 
1192
- return b.function(
1196
+ const func = b.function(
1193
1197
  node.id,
1194
1198
  node.params.length > 0
1195
1199
  ? [b.id('__anchor'), props, b.id('__block')]
@@ -1201,6 +1205,10 @@ const visitors = {
1201
1205
  : body_statements),
1202
1206
  ]),
1203
1207
  );
1208
+ // Mark that this function was originally a component
1209
+ func.metadata = { ...func.metadata, was_component: true };
1210
+ func.loc = node.loc; // Copy source location for Volar mappings
1211
+ return func;
1204
1212
  },
1205
1213
 
1206
1214
  AssignmentExpression(node, context) {
@@ -1,4 +1,4 @@
1
- import { walk } from 'zimmerframe';
1
+ import { walk } from 'zimmerframe';
2
2
 
3
3
  export const mapping_data = {
4
4
  verification: true,
@@ -354,6 +354,13 @@ export function convert_source_map_to_mappings(ast, source, generated_code, sour
354
354
 
355
355
  return;
356
356
  } else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
357
+ // Map function/component keywords
358
+ if (node.metadata?.was_component && node.loc) {
359
+ tokens.push({ source: 'component', generated: 'function' });
360
+ } else if (node.loc && node.type === 'FunctionDeclaration') {
361
+ tokens.push('function');
362
+ }
363
+
357
364
  // Visit in source order: id, params, body
358
365
  if (node.id) {
359
366
  visit(node.id);
@@ -361,6 +368,9 @@ export function convert_source_map_to_mappings(ast, source, generated_code, sour
361
368
  if (node.params) {
362
369
  for (const param of node.params) {
363
370
  visit(param);
371
+ if (param.typeAnnotation) {
372
+ visit(param.typeAnnotation);
373
+ }
364
374
  }
365
375
  }
366
376
  if (node.body) {
@@ -1260,11 +1270,11 @@ export function convert_source_map_to_mappings(ast, source, generated_code, sour
1260
1270
  sourceOffsets: [0],
1261
1271
  generatedOffsets: [0],
1262
1272
  lengths: [1],
1263
- data: {
1264
- ...mapping_data,
1265
- codeActions: true, // auto-import
1266
- rename: false, // avoid rename for a “dummy” mapping
1267
- }
1273
+ data: {
1274
+ ...mapping_data,
1275
+ codeActions: true, // auto-import
1276
+ rename: false, // avoid rename for a “dummy” mapping
1277
+ }
1268
1278
  });
1269
1279
  }
1270
1280
 
@@ -169,7 +169,7 @@ describe('compiler > basics', () => {
169
169
 
170
170
  function Wrapper() {
171
171
  return {
172
- unwrap: function() {
172
+ unwrap: function <T>() {
173
173
  return null as unknown as T;
174
174
  },
175
175
  };
@@ -186,10 +186,10 @@ describe('compiler > basics', () => {
186
186
  value: T;
187
187
  }
188
188
 
189
- class Box {
189
+ class Box<T> {
190
190
  value: T;
191
191
 
192
- method(): T {
192
+ method<T>(): T {
193
193
  return this.value;
194
194
  }
195
195
  }
@@ -2,13 +2,13 @@ import { beforeEach, afterEach } from 'vitest';
2
2
  import { mount } from 'ripple';
3
3
 
4
4
  /**
5
- * @param {() => void} component
5
+ * @param {() => void} component
6
6
  */
7
7
  globalThis.render = function render(component) {
8
8
  mount(component, {
9
- target: /** @type {HTMLDivElement} */ (globalThis.container)
9
+ target: /** @type {HTMLDivElement} */ (globalThis.container),
10
10
  });
11
- }
11
+ };
12
12
 
13
13
  beforeEach(() => {
14
14
  globalThis.container = /** @type {HTMLDivElement} */ (document.createElement('div'));
@@ -22,7 +22,7 @@ afterEach(() => {
22
22
  // And when we unset it, we just type-cast it to HTMLDivElement to avoid TS errors, because we
23
23
  // know it's guaranteed to exist in the next test again.
24
24
  document.body.removeChild(/** @type {HTMLDivElement} */ (globalThis.container));
25
- globalThis.container = /** @type {HTMLDivElement} */ (/** @type {unknown} */(undefined));
25
+ globalThis.container = /** @type {HTMLDivElement} */ (/** @type {unknown} */ (undefined));
26
26
 
27
27
  globalThis.error = undefined;
28
28
  });
package/types/index.d.ts CHANGED
@@ -1,8 +1,18 @@
1
1
  export type Component<T = Record<string, any>> = (props: T) => void;
2
2
 
3
+ export type CompatApi = {
4
+ createRoot: () => void;
5
+ createComponent: (node: any, children_fn: () => any) => void;
6
+ jsx: (type: any, props: any) => any;
7
+ };
8
+
9
+ export type CompatOptions = {
10
+ [key: string]: CompatApi;
11
+ };
12
+
3
13
  export declare function mount(
4
14
  component: () => void,
5
- options: { target: HTMLElement; props?: Record<string, any> },
15
+ options: { target: HTMLElement; props?: Record<string, any>; compat?: CompatOptions },
6
16
  ): () => void;
7
17
 
8
18
  export declare function tick(): Promise<void>;