@tsrx/solid 0.0.5 → 0.0.6

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/transform.js +119 -10
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Solid compiler built on @tsrx/core",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.0.5",
6
+ "version": "0.0.6",
7
7
  "type": "module",
8
8
  "publishConfig": {
9
9
  "access": "public"
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "esrap": "^2.1.0",
24
24
  "zimmerframe": "^1.1.2",
25
- "@tsrx/core": "0.0.5"
25
+ "@tsrx/core": "0.0.6"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "solid-js": ">=1.8 || >=2.0.0-beta"
package/src/transform.js CHANGED
@@ -8,11 +8,15 @@ import {
8
8
  renderStylesheets,
9
9
  setLocation,
10
10
  applyLazyTransforms as apply_lazy_transforms,
11
+ findFirstTopLevelAwaitInComponentBody as find_first_top_level_await_in_component_body,
11
12
  collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
12
13
  preallocateLazyIds as preallocate_lazy_ids,
13
14
  replaceLazyParams as replace_lazy_params,
14
15
  prepareStylesheetForRender as prepare_stylesheet_for_render,
15
16
  annotateComponentWithHash as annotate_component_with_hash,
17
+ isInterleavedBody as is_interleaved_body_core,
18
+ isCapturableJsxChild as is_capturable_jsx_child,
19
+ captureJsxChild,
16
20
  } from '@tsrx/core';
17
21
 
18
22
  /**
@@ -69,6 +73,22 @@ export function transform(ast, source, filename) {
69
73
  walk(/** @type {any} */ (ast), transform_context, {
70
74
  Component(node, { next, state }) {
71
75
  const as_any = /** @type {any} */ (node);
76
+ const await_expression = find_first_top_level_await_in_component_body(as_any.body || []);
77
+
78
+ if (await_expression) {
79
+ const await_start = get_await_keyword_start(await_expression, source);
80
+ const adjusted_node = /** @type {any} */ ({
81
+ ...await_expression,
82
+ start: await_start,
83
+ end: await_start + 'await'.length,
84
+ });
85
+
86
+ throw create_compile_error(
87
+ adjusted_node,
88
+ '`await` is not allowed inside Solid components.',
89
+ );
90
+ }
91
+
72
92
  const css = as_any.css;
73
93
  if (css) {
74
94
  stylesheets.push(css);
@@ -161,6 +181,29 @@ export function transform(ast, source, filename) {
161
181
  };
162
182
  }
163
183
 
184
+ /**
185
+ * @param {any} await_node
186
+ * @param {string} source
187
+ * @returns {number}
188
+ */
189
+ function get_await_keyword_start(await_node, source) {
190
+ if (await_node?.type === 'AwaitExpression') {
191
+ return await_node.start ?? 0;
192
+ }
193
+
194
+ if (await_node?.type === 'ForOfStatement' && await_node.await === true) {
195
+ const statement_start = await_node.start ?? 0;
196
+ const statement_end = await_node.end ?? statement_start;
197
+ const statement_source = source.slice(statement_start, statement_end);
198
+ const await_offset = statement_source.search(/\bawait\b/);
199
+
200
+ if (await_offset !== -1) {
201
+ return statement_start + await_offset;
202
+ }
203
+ }
204
+
205
+ return await_node?.start ?? 0;
206
+ }
164
207
  // =====================================================================
165
208
  // Component → FunctionDeclaration
166
209
  // =====================================================================
@@ -201,22 +244,53 @@ function component_to_function_declaration(component, transform_context) {
201
244
  const early_if = /** @type {any} */ (body[early_idx]);
202
245
  const before = body.slice(0, early_idx);
203
246
  const after = body.slice(early_idx + 1);
247
+
248
+ // If mutations are interleaved with JSX children, the mutation and the
249
+ // JSX it affects can't both be hoisted out of order — that is the same
250
+ // bug `body_to_jsx_child` avoids. Capture each JSX child into a const
251
+ // at its source position so later mutations in the outer body don't
252
+ // retroactively change what earlier children rendered.
253
+ const early_interleaved = is_interleaved_body([...before, ...after]);
254
+
204
255
  /** @type {any[]} */
205
256
  const before_non_jsx = [];
206
257
  /** @type {any[]} */
207
258
  const before_jsx = [];
208
- for (const child of before) {
209
- if (is_jsx_child(child)) before_jsx.push(child);
210
- else before_non_jsx.push(child);
211
- }
212
259
  /** @type {any[]} */
213
260
  const after_non_jsx = [];
214
261
  /** @type {any[]} */
215
262
  const after_jsx = [];
216
- for (const child of after) {
217
- if (is_jsx_child(child)) after_jsx.push(child);
218
- else after_non_jsx.push(child);
219
- }
263
+ let early_capture_index = 0;
264
+
265
+ /**
266
+ * @param {any[]} nodes
267
+ * @param {any[]} outer
268
+ * @param {any[]} jsx_bucket
269
+ */
270
+ const collect = (nodes, outer, jsx_bucket) => {
271
+ for (const child of nodes) {
272
+ if (is_jsx_child(child)) {
273
+ if (early_interleaved) {
274
+ const jsx = to_jsx_child(child, transform_context);
275
+ if (is_capturable_jsx_child(jsx)) {
276
+ const { declaration, reference } = captureJsxChild(jsx, early_capture_index++);
277
+ outer.push(declaration);
278
+ jsx_bucket.push(reference);
279
+ } else {
280
+ jsx_bucket.push(jsx);
281
+ }
282
+ } else {
283
+ jsx_bucket.push(child);
284
+ }
285
+ } else {
286
+ outer.push(child);
287
+ }
288
+ }
289
+ };
290
+
291
+ collect(before, before_non_jsx, before_jsx);
292
+ collect(after, after_non_jsx, after_jsx);
293
+
220
294
  const lifted = [...before_jsx, ...after_jsx];
221
295
  if (lifted.length > 0) {
222
296
  transform_context.needs_show = true;
@@ -228,10 +302,19 @@ function component_to_function_declaration(component, transform_context) {
228
302
 
229
303
  const statements = [];
230
304
  const render_nodes = [];
305
+ const interleaved = is_interleaved_body(effective_body);
306
+ let capture_index = 0;
231
307
 
232
308
  for (const child of effective_body) {
233
309
  if (is_jsx_child(child)) {
234
- render_nodes.push(to_jsx_child(child, transform_context));
310
+ const jsx = to_jsx_child(child, transform_context);
311
+ if (interleaved && is_capturable_jsx_child(jsx)) {
312
+ const { declaration, reference } = captureJsxChild(jsx, capture_index++);
313
+ statements.push(declaration);
314
+ render_nodes.push(reference);
315
+ } else {
316
+ render_nodes.push(jsx);
317
+ }
235
318
  } else {
236
319
  statements.push(child);
237
320
  }
@@ -376,13 +459,28 @@ function to_jsx_child(node, transform_context) {
376
459
  * @returns {any}
377
460
  */
378
461
  function body_to_jsx_child(body_nodes, transform_context) {
462
+ // When non-JSX statements are interleaved with JSX children, preserve
463
+ // source order by capturing each JSX child into a const at its textual
464
+ // position. Otherwise all statements would run before any JSX is
465
+ // constructed, so every JSX child would observe the final state of
466
+ // mutable variables instead of the value at its point in the source.
467
+ const interleaved = is_interleaved_body(body_nodes);
468
+
379
469
  /** @type {any[]} */
380
470
  const statements = [];
381
471
  /** @type {any[]} */
382
472
  const children = [];
473
+ let capture_index = 0;
383
474
  for (const child of body_nodes) {
384
475
  if (is_jsx_child(child)) {
385
- children.push(to_jsx_child(child, transform_context));
476
+ const jsx = to_jsx_child(child, transform_context);
477
+ if (interleaved && is_capturable_jsx_child(jsx)) {
478
+ const { declaration, reference } = captureJsxChild(jsx, capture_index++);
479
+ statements.push(declaration);
480
+ children.push(reference);
481
+ } else {
482
+ children.push(jsx);
483
+ }
386
484
  } else {
387
485
  statements.push(child);
388
486
  }
@@ -427,6 +525,17 @@ function body_to_jsx_child(body_nodes, transform_context) {
427
525
  });
428
526
  }
429
527
 
528
+ /**
529
+ * Solid-specific binding of the core `isInterleavedBody` helper with this
530
+ * target's `is_jsx_child` predicate.
531
+ *
532
+ * @param {any[]} body_nodes
533
+ * @returns {boolean}
534
+ */
535
+ function is_interleaved_body(body_nodes) {
536
+ return is_interleaved_body_core(body_nodes, is_jsx_child);
537
+ }
538
+
430
539
  /**
431
540
  * @param {any} node
432
541
  * @returns {boolean}