@tsrx/core 0.1.10 → 0.1.11

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": "Core compiler infrastructure for TSRX syntax",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.1.10",
6
+ "version": "0.1.11",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
@@ -80,12 +80,12 @@
80
80
  "solid-js": "2.0.0-beta.7",
81
81
  "typescript": "^5.9.3",
82
82
  "vscode-languageserver-types": "^3.17.5",
83
- "vue": "3.6.0-beta.10",
84
- "vue-jsx-vapor": "^3.2.12",
85
- "@tsrx/preact": "0.1.10",
86
- "@tsrx/react": "0.2.10",
87
- "@tsrx/solid": "0.1.10",
88
- "@tsrx/vue": "0.1.10"
83
+ "vue": "3.6.0-beta.12",
84
+ "vue-jsx-vapor": "^3.2.14",
85
+ "@tsrx/preact": "0.1.11",
86
+ "@tsrx/react": "0.2.11",
87
+ "@tsrx/solid": "0.1.11",
88
+ "@tsrx/vue": "0.1.11"
89
89
  },
90
90
  "files": [
91
91
  "src",
package/src/index.js CHANGED
@@ -146,6 +146,7 @@ export {
146
146
  clone_switch_helper_invocation as cloneSwitchHelperInvocation,
147
147
  collect_param_bindings as collectParamBindings,
148
148
  collect_statement_bindings as collectStatementBindings,
149
+ create_hook_safe_helper as createHookSafeHelper,
149
150
  create_host_html_attribute as createHostHtmlAttribute,
150
151
  create_host_html_conflict_error as createHostHtmlConflictError,
151
152
  createJsxTransform,
@@ -1215,23 +1215,27 @@ function is_hook_callee(callee) {
1215
1215
 
1216
1216
  /**
1217
1217
  * @param {AST.Identifier[]} bindings
1218
+ * @param {Set<string>} [mapped_bindings]
1218
1219
  * @returns {AST.ObjectPattern}
1219
1220
  */
1220
- function create_helper_props_pattern(bindings) {
1221
+ function create_helper_props_pattern(bindings, mapped_bindings = new Set()) {
1221
1222
  return /** @type {any} */ ({
1222
1223
  type: 'ObjectPattern',
1223
- properties: bindings.map((binding) => create_helper_props_property(binding)),
1224
+ properties: bindings.map((binding) =>
1225
+ create_helper_props_property(binding, mapped_bindings.has(binding.name)),
1226
+ ),
1224
1227
  metadata: { path: [] },
1225
1228
  });
1226
1229
  }
1227
1230
 
1228
1231
  /**
1229
1232
  * @param {AST.Identifier} binding
1233
+ * @param {boolean} [map_binding]
1230
1234
  * @returns {AST.Property}
1231
1235
  */
1232
- function create_helper_props_property(binding) {
1233
- const key = create_generated_identifier(binding.name);
1234
- const value = create_generated_identifier(binding.name);
1236
+ function create_helper_props_property(binding, map_binding = false) {
1237
+ const key = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1238
+ const value = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1235
1239
 
1236
1240
  return b.prop('init', key, value, false, true);
1237
1241
  }
@@ -3411,14 +3415,16 @@ function get_hook_callee_name(callee) {
3411
3415
  * Used by the switch lift's chained-call build, which allocates ids in
3412
3416
  * source order in a forward pass and then constructs helpers in reverse so
3413
3417
  * each fall-through case can reference the next case's component element.
3418
+ * @param {{ transientBindings?: Set<string> }} [options]
3414
3419
  * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
3415
3420
  */
3416
- function create_hook_safe_helper(
3421
+ export function create_hook_safe_helper(
3417
3422
  body_nodes,
3418
3423
  key_expression,
3419
3424
  source_node,
3420
3425
  transform_context,
3421
3426
  preallocated_helper_id,
3427
+ options = {},
3422
3428
  ) {
3423
3429
  validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3424
3430
  body_nodes,
@@ -3436,9 +3442,14 @@ function create_hook_safe_helper(
3436
3442
  body_nodes,
3437
3443
  transform_context.available_bindings,
3438
3444
  );
3445
+ const transient_bindings = options.transientBindings ?? new Set();
3439
3446
  const aliases = use_module_scoped_component
3440
3447
  ? []
3441
- : helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
3448
+ : helper_bindings.map((binding) =>
3449
+ transient_bindings.has(binding.name)
3450
+ ? null
3451
+ : create_helper_type_alias_declaration(helper_id, binding),
3452
+ );
3442
3453
  const props_type =
3443
3454
  helper_bindings.length > 0 && !use_module_scoped_component
3444
3455
  ? create_helper_props_type_literal(helper_bindings, aliases)
@@ -3447,8 +3458,8 @@ function create_hook_safe_helper(
3447
3458
  helper_bindings.length > 0
3448
3459
  ? [
3449
3460
  props_type !== null
3450
- ? create_typed_helper_props_pattern(helper_bindings, props_type)
3451
- : create_helper_props_pattern(helper_bindings),
3461
+ ? create_typed_helper_props_pattern(helper_bindings, props_type, transient_bindings)
3462
+ : create_helper_props_pattern(helper_bindings, transient_bindings),
3452
3463
  ]
3453
3464
  : [];
3454
3465
 
@@ -3490,7 +3501,7 @@ function create_hook_safe_helper(
3490
3501
  if (!transform_context.helper_state) {
3491
3502
  return {
3492
3503
  setup_statements: [
3493
- ...aliases.map((alias) => alias.declaration),
3504
+ ...aliases.flatMap((alias) => (alias ? [alias.declaration] : [])),
3494
3505
  create_helper_declaration(helper_id, helper_fn, source_node, transform_context),
3495
3506
  ],
3496
3507
  component_element,
@@ -3514,7 +3525,7 @@ function create_hook_safe_helper(
3514
3525
 
3515
3526
  return {
3516
3527
  setup_statements: [
3517
- ...aliases.map((alias) => alias.declaration),
3528
+ ...aliases.flatMap((alias) => (alias ? [alias.declaration] : [])),
3518
3529
  create_cached_helper_declaration(
3519
3530
  helper_id,
3520
3531
  cache_id,
@@ -3588,7 +3599,7 @@ function create_helper_type_alias_declaration(helper_id, binding) {
3588
3599
 
3589
3600
  /**
3590
3601
  * @param {AST.Identifier[]} bindings
3591
- * @param {{ id: AST.Identifier }[]} aliases
3602
+ * @param {({ id: AST.Identifier } | null)[]} aliases
3592
3603
  * @returns {any}
3593
3604
  */
3594
3605
  function create_helper_props_type_literal(bindings, aliases) {
@@ -3596,7 +3607,13 @@ function create_helper_props_type_literal(bindings, aliases) {
3596
3607
  bindings.map((binding, i) =>
3597
3608
  b.ts_property_signature(
3598
3609
  create_generated_identifier(binding.name),
3599
- b.ts_type_annotation(b.ts_type_query(clone_identifier(aliases[i].id))),
3610
+ b.ts_type_annotation(
3611
+ aliases[i]
3612
+ ? b.ts_type_query(
3613
+ clone_identifier(/** @type {{ id: AST.Identifier }} */ (aliases[i]).id),
3614
+ )
3615
+ : b.ts_keyword_type('any'),
3616
+ ),
3600
3617
  ),
3601
3618
  ),
3602
3619
  );
@@ -3605,10 +3622,11 @@ function create_helper_props_type_literal(bindings, aliases) {
3605
3622
  /**
3606
3623
  * @param {AST.Identifier[]} bindings
3607
3624
  * @param {any} props_type
3625
+ * @param {Set<string>} [mapped_bindings]
3608
3626
  * @returns {AST.ObjectPattern}
3609
3627
  */
3610
- function create_typed_helper_props_pattern(bindings, props_type) {
3611
- const pattern = create_helper_props_pattern(bindings);
3628
+ function create_typed_helper_props_pattern(bindings, props_type, mapped_bindings = new Set()) {
3629
+ const pattern = create_helper_props_pattern(bindings, mapped_bindings);
3612
3630
  /** @type {any} */ (pattern).typeAnnotation = b.ts_type_annotation(props_type);
3613
3631
  return pattern;
3614
3632
  }
@@ -4502,18 +4520,25 @@ function try_statement_to_jsx_child(node, transform_context) {
4502
4520
  ? to_jsx_expression_container(create_null_literal())
4503
4521
  : statement_body_to_jsx_child(pending_body_nodes, transform_context);
4504
4522
 
4505
- result = create_jsx_element(
4506
- 'Suspense',
4507
- [
4508
- {
4509
- type: 'JSXAttribute',
4510
- name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
4511
- value: fallback_content,
4512
- metadata: { path: [] },
4513
- },
4514
- ],
4515
- [result],
4516
- );
4523
+ result =
4524
+ transform_context.platform.hooks?.createPendingBoundary?.(
4525
+ result,
4526
+ fallback_content,
4527
+ transform_context,
4528
+ node,
4529
+ ) ??
4530
+ create_jsx_element(
4531
+ 'Suspense',
4532
+ [
4533
+ {
4534
+ type: 'JSXAttribute',
4535
+ name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
4536
+ value: fallback_content,
4537
+ metadata: { path: [] },
4538
+ },
4539
+ ],
4540
+ [result],
4541
+ );
4517
4542
  }
4518
4543
 
4519
4544
  // Wrap in <TsrxErrorBoundary> if catch block exists
@@ -4551,19 +4576,42 @@ function try_statement_to_jsx_child(node, transform_context) {
4551
4576
 
4552
4577
  const fallback_fn = b.arrow(
4553
4578
  catch_params,
4554
- b.block(build_render_statements(catch_body_nodes, true, transform_context)),
4579
+ b.block(build_render_statements(catch_body_nodes, true, transform_context), handler.body),
4580
+ false,
4581
+ undefined,
4582
+ handler,
4555
4583
  );
4556
4584
 
4585
+ const fallback_component =
4586
+ transform_context.platform.hooks?.createErrorFallbackComponent?.(
4587
+ catch_body_nodes,
4588
+ catch_params,
4589
+ transform_context,
4590
+ node,
4591
+ ) ?? null;
4592
+
4557
4593
  transform_context.available_bindings = saved_catch_bindings;
4558
4594
 
4559
4595
  const boundary_content =
4560
4596
  transform_context.platform.hooks?.createErrorBoundaryContent?.(
4597
+ result,
4598
+ transform_context,
4599
+ node,
4600
+ ) ?? null;
4601
+
4602
+ const custom_boundary =
4603
+ transform_context.platform.hooks?.createErrorBoundary?.(
4604
+ result,
4561
4605
  try_content,
4606
+ fallback_fn,
4562
4607
  transform_context,
4563
4608
  node,
4609
+ { fallbackComponent: fallback_component },
4564
4610
  ) ?? null;
4565
4611
 
4566
- if (boundary_content && transform_context.inside_element_child) {
4612
+ if (custom_boundary) {
4613
+ result = custom_boundary;
4614
+ } else if (boundary_content && transform_context.inside_element_child) {
4567
4615
  result = to_jsx_expression_container(
4568
4616
  b.call(
4569
4617
  'TsrxErrorBoundary',
@@ -4572,21 +4620,21 @@ function try_statement_to_jsx_child(node, transform_context) {
4572
4620
  );
4573
4621
 
4574
4622
  return result;
4623
+ } else {
4624
+ result = create_jsx_element(
4625
+ 'TsrxErrorBoundary',
4626
+ [
4627
+ b.jsx_attribute(
4628
+ b.jsx_id('fallback'),
4629
+ to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4630
+ ),
4631
+ ...(boundary_content
4632
+ ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
4633
+ : []),
4634
+ ],
4635
+ boundary_content ? [] : [result],
4636
+ );
4575
4637
  }
4576
-
4577
- result = create_jsx_element(
4578
- 'TsrxErrorBoundary',
4579
- [
4580
- b.jsx_attribute(
4581
- b.jsx_id('fallback'),
4582
- to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4583
- ),
4584
- ...(boundary_content
4585
- ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
4586
- : []),
4587
- ],
4588
- boundary_content ? [] : [result],
4589
- );
4590
4638
  }
4591
4639
 
4592
4640
  // result is a JSXElement, but we need to return a JSXExpressionContainer
@@ -200,11 +200,49 @@ export interface JsxPlatformHooks {
200
200
  * the loop to the downstream Vapor JSX compiler as a typed `VaporFor` component.
201
201
  */
202
202
  renderForOf?: (node: any, loopParams: any[], bodyStatements: any[], ctx: any) => any | null;
203
+ /**
204
+ * Optionally replace the default React-style pending lowering for
205
+ * `try { ... } pending { ... }`. The default emits
206
+ * `<Suspense fallback={fallbackContent}>tryContent</Suspense>`.
207
+ * Vue Vapor uses this to provide `default` and `fallback` slots via
208
+ * `v-slots`.
209
+ */
210
+ createPendingBoundary?: (
211
+ tryContent: any,
212
+ fallbackContent: any,
213
+ ctx: any,
214
+ node: any,
215
+ ) => any | null;
216
+ /**
217
+ * Optionally create a generated component for a catch fallback body while
218
+ * the catch parameters are still in scope. Platforms can use this to reuse
219
+ * one mapped catch-body component from multiple runtime catch sites.
220
+ */
221
+ createErrorFallbackComponent?: (
222
+ catchBodyNodes: any[],
223
+ catchParams: any[],
224
+ ctx: any,
225
+ node: any,
226
+ ) => any | null;
227
+ /**
228
+ * Optionally replace the default `try/catch` boundary wrapper. The hook
229
+ * receives the current render content, the original try-body content before
230
+ * any pending wrapper, and the generated catch fallback function.
231
+ */
232
+ createErrorBoundary?: (
233
+ tryContent: any,
234
+ rawTryContent: any,
235
+ fallbackFn: any,
236
+ ctx: any,
237
+ node: any,
238
+ info?: { fallbackComponent?: any },
239
+ ) => any | null;
203
240
  /**
204
241
  * Optionally move the primary `try { ... }` render content into an explicit
205
242
  * error-boundary prop instead of rendering it as the boundary's JSX children.
206
243
  * Vue Vapor uses this because boundary content must execute lazily from a
207
- * zero-argument function.
244
+ * zero-argument function. If a `pending` block exists, `tryContent` is the
245
+ * already-created pending boundary so catch wrappers still enclose it.
208
246
  */
209
247
  createErrorBoundaryContent?: (tryContent: any, ctx: any, node: any) => any | null;
210
248
  /**