@reckona/mreact-router 0.0.186 → 0.0.187

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/client.ts CHANGED
@@ -80,6 +80,7 @@ export interface BuildClientRouteOutputOptions {
80
80
  vitePlugins?: readonly PluginOption[] | undefined;
81
81
  clientNavigation?: boolean | undefined;
82
82
  forceInlineNavigationRuntime?: boolean | undefined;
83
+ routeMayUseOutOfOrderFragments?: boolean | undefined;
83
84
  }
84
85
 
85
86
  export interface BuildClientRouteBatchOutput {
@@ -2067,7 +2068,9 @@ export async function buildClientRouteBundle(options: {
2067
2068
  clientBoundaryImports?: readonly string[] | undefined;
2068
2069
  clientReferenceImports?: readonly ClientReferenceImport[] | undefined;
2069
2070
  clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
2071
+ clientNavigation?: boolean | undefined;
2070
2072
  filename: string;
2073
+ routeMayUseOutOfOrderFragments?: boolean | undefined;
2071
2074
  routePath: string;
2072
2075
  vitePlugins?: readonly PluginOption[] | undefined;
2073
2076
  }): Promise<string> {
@@ -2241,6 +2244,12 @@ export async function buildClientRouteEntrySource(
2241
2244
  options.filename,
2242
2245
  );
2243
2246
  const routeHasEventBindings = (compiled.metadata.eventHydrationManifest?.events.length ?? 0) > 0;
2247
+ const routeCapturesEventBindings = compiled.code.includes("__mreactEventBindings");
2248
+ const routeMayCaptureEventBindings =
2249
+ routeHasEventBindings ||
2250
+ routeCapturesEventBindings ||
2251
+ /\bon[A-Z][\w$]*\s*=/.test(options.code) ||
2252
+ routeSourceAnalysis.staticImports.length > 0;
2244
2253
  const routeRequiresFullHydration =
2245
2254
  routeExplicitlyRequiresHydration ||
2246
2255
  routeUsesCells ||
@@ -2261,8 +2270,13 @@ export async function buildClientRouteEntrySource(
2261
2270
  const routeCleanupScopeImport = routeUsesCleanupScope
2262
2271
  ? `import { withCleanupScope as __mreactWithCleanupScope } from "@reckona/mreact-reactive-core/internal";\n`
2263
2272
  : "";
2273
+ const routeUsesEventBindingSync =
2274
+ !routeUsesOnlyClientReferenceBoundaries && routeMayCaptureEventBindings;
2275
+ const routeCapturedEventImport = routeUsesEventBindingSync
2276
+ ? `import { bindCapturedEvent as __mreactBindCapturedEvent } from "@reckona/mreact-reactive-dom/internal";\n`
2277
+ : "";
2264
2278
  const routeReactiveDomMetadataImport = !routeUsesOnlyClientReferenceBoundaries
2265
- ? `import { withEventBindingMetadata as __mreactWithEventBindingMetadata, withPropBindingMetadata as __mreactWithPropBindingMetadata } from "@reckona/mreact-reactive-dom";\n`
2279
+ ? `${routeCapturedEventImport}import { withEventBindingMetadata as __mreactWithEventBindingMetadata, withPropBindingMetadata as __mreactWithPropBindingMetadata } from "@reckona/mreact-reactive-dom";\n`
2266
2280
  : "";
2267
2281
  const navigationStateDeclaration = inlineClientNavigation
2268
2282
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
@@ -2420,10 +2434,16 @@ function __mreactInstallNavigation() {
2420
2434
  }
2421
2435
  };
2422
2436
 
2423
- if (typeof requestIdleCallback === "function") {
2424
- requestIdleCallback(load);
2425
- } else {
2426
- setTimeout(load, 0);
2437
+ const hasSameOriginAnchor = Array.from(document.querySelectorAll("a[href]")).some((anchor) =>
2438
+ anchor instanceof HTMLAnchorElement && anchor.origin === location.origin,
2439
+ );
2440
+
2441
+ if (hasSameOriginAnchor) {
2442
+ if (typeof requestIdleCallback === "function") {
2443
+ requestIdleCallback(load);
2444
+ } else {
2445
+ setTimeout(load, 0);
2446
+ }
2427
2447
  }
2428
2448
 
2429
2449
  addEventListener("popstate", load);
@@ -2617,6 +2637,51 @@ function __mreactResolveRouteNode(value) {
2617
2637
  const routeHydrationNodeExpression = !routeUsesOnlyClientReferenceBoundaries
2618
2638
  ? `__mreactWithPropBindingMetadata(() => __mreactWithEventBindingMetadata(() => __mreactEvaluateHydrationNode(() => ${routeNodeExpression})))`
2619
2639
  : `__mreactEvaluateHydrationNode(() => ${routeNodeExpression})`;
2640
+ const routeEventBindingSyncFunction = routeUsesEventBindingSync
2641
+ ? `function __mreactSyncEventBindings(current, next) {
2642
+ const previousDisposers = current.__mreactEventDisposers;
2643
+
2644
+ if (Array.isArray(previousDisposers)) {
2645
+ for (const dispose of previousDisposers) {
2646
+ dispose();
2647
+ }
2648
+ }
2649
+
2650
+ const rawBindings = next.__mreactEventBindings;
2651
+ const bindings =
2652
+ rawBindings === undefined ? [] : Array.isArray(rawBindings) ? rawBindings : [rawBindings];
2653
+
2654
+ if (bindings.length === 0) {
2655
+ current.__mreactEventDisposers = [];
2656
+ current.__mreactHasEvents = false;
2657
+ return;
2658
+ }
2659
+
2660
+ const disposers = [];
2661
+
2662
+ for (const binding of bindings) {
2663
+ disposers.push(
2664
+ __mreactBindCapturedEvent(current, binding.type, binding.listener, binding.delegated === true),
2665
+ );
2666
+ }
2667
+
2668
+ current.__mreactEventDisposers = disposers;
2669
+ current.__mreactHasEvents = true;
2670
+ }
2671
+ `
2672
+ : `function __mreactSyncEventBindings(current) {
2673
+ const previousDisposers = current.__mreactEventDisposers;
2674
+
2675
+ if (Array.isArray(previousDisposers)) {
2676
+ for (const dispose of previousDisposers) {
2677
+ dispose();
2678
+ }
2679
+ }
2680
+
2681
+ current.__mreactEventDisposers = [];
2682
+ current.__mreactHasEvents = false;
2683
+ }
2684
+ `;
2620
2685
  const boundaryOnlyHydrationBlock = routeRequiresFullHydration
2621
2686
  ? ""
2622
2687
  : `${routeCellHydrationIndent}if (!__mreactHasNonSerializableClientBoundaries(__mreactMarker) && __mreactHydrateClientBoundaries(document, __mreactClientReferences, __mreactClientReferenceComponents)) {
@@ -2625,6 +2690,10 @@ ${routeCellHydrationIndent} __mreactMarkRouteHydrated();
2625
2690
  ${routeCellHydrationIndent} return;
2626
2691
  ${routeCellHydrationIndent}}
2627
2692
  `;
2693
+ const routeOutOfOrderFragmentHydration =
2694
+ options.routeMayUseOutOfOrderFragments === true
2695
+ ? " __mreactApplyOutOfOrderFragments(document);\n"
2696
+ : "";
2628
2697
  const routeComponentGuard = `${routeCellHydrationIndent}if (__mreactComponent === undefined) {
2629
2698
  ${routeCellHydrationIndent} return;
2630
2699
  ${routeCellHydrationIndent}}
@@ -2647,8 +2716,7 @@ ${clientReferenceRegistry}
2647
2716
  ${routeNodeResolver}
2648
2717
 
2649
2718
  export function __mreactHydrateRoute() {
2650
- __mreactApplyOutOfOrderFragments(document);
2651
- const __mreactMarker = document.querySelector(\`[\${__mreactRouteMarkerAttribute}="\${__mreactRouteId}"]\`);
2719
+ ${routeOutOfOrderFragmentHydration} const __mreactMarker = document.querySelector(\`[\${__mreactRouteMarkerAttribute}="\${__mreactRouteId}"]\`);
2652
2720
  const __mreactPropsElement = document.getElementById(\`\${__mreactPropsScriptPrefix}\${__mreactRouteId}\`);
2653
2721
  const __mreactClientReferencesElement = document.getElementById(\`\${__mreactClientReferencesScriptPrefix}\${__mreactRouteId}\`);
2654
2722
  const __mreactPropsText = __mreactPropsElement?.textContent;
@@ -4347,184 +4415,7 @@ function __mreactShouldReplaceNode(current, next) {
4347
4415
  current.tagName !== next.tagName;
4348
4416
  }
4349
4417
 
4350
- function __mreactSyncEventBindings(current, next) {
4351
- const previousDisposers = current.__mreactEventDisposers;
4352
-
4353
- if (Array.isArray(previousDisposers)) {
4354
- for (const dispose of previousDisposers) {
4355
- dispose();
4356
- }
4357
- }
4358
-
4359
- const rawBindings = next.__mreactEventBindings;
4360
- const bindings =
4361
- rawBindings === undefined ? [] : Array.isArray(rawBindings) ? rawBindings : [rawBindings];
4362
-
4363
- if (bindings.length === 0) {
4364
- current.__mreactEventDisposers = [];
4365
- current.__mreactHasEvents = false;
4366
- return;
4367
- }
4368
-
4369
- const disposers = [];
4370
-
4371
- for (const binding of bindings) {
4372
- if (binding.delegated === true && __mreactIsDelegatedEventType(binding.type)) {
4373
- disposers.push(__mreactAddDelegatedEventListener(current, binding.type, binding.listener));
4374
- } else {
4375
- current.addEventListener(binding.type, binding.listener);
4376
- disposers.push(() => current.removeEventListener(binding.type, binding.listener));
4377
- }
4378
- }
4379
-
4380
- current.__mreactEventDisposers = disposers;
4381
- current.__mreactHasEvents = true;
4382
- }
4383
-
4384
- function __mreactIsDelegatedEventType(type) {
4385
- return type === "change" ||
4386
- type === "click" ||
4387
- type === "input" ||
4388
- type === "keydown" ||
4389
- type === "keyup" ||
4390
- type === "pointerdown" ||
4391
- type === "pointermove" ||
4392
- type === "pointerup" ||
4393
- type === "submit";
4394
- }
4395
-
4396
- function __mreactDelegatedEventState() {
4397
- globalThis.__mreactDelegatedEventState ??= {
4398
- elements: new WeakMap(),
4399
- roots: new WeakMap(),
4400
- };
4401
- return globalThis.__mreactDelegatedEventState;
4402
- }
4403
-
4404
- function __mreactAddDelegatedEventListener(element, type, listener) {
4405
- const root = element.ownerDocument;
4406
- const state = __mreactDelegatedEventState();
4407
- let listenersByType = state.elements.get(element);
4408
-
4409
- if (listenersByType === undefined) {
4410
- listenersByType = new Map();
4411
- state.elements.set(element, listenersByType);
4412
- }
4413
-
4414
- let listeners = listenersByType.get(type);
4415
-
4416
- if (listeners === undefined) {
4417
- listeners = [];
4418
- listenersByType.set(type, listeners);
4419
- }
4420
-
4421
- listeners.push(listener);
4422
- __mreactRetainDelegatedEventRoot(root, type);
4423
-
4424
- return () => {
4425
- const state = __mreactDelegatedEventState();
4426
- const currentListeners = state.elements.get(element)?.get(type);
4427
- const index = currentListeners?.indexOf(listener) ?? -1;
4428
-
4429
- if (index !== -1) {
4430
- currentListeners?.splice(index, 1);
4431
- }
4432
-
4433
- if (currentListeners?.length === 0) {
4434
- state.elements.get(element)?.delete(type);
4435
- }
4436
-
4437
- __mreactReleaseDelegatedEventRoot(root, type);
4438
- };
4439
- }
4440
-
4441
- function __mreactRetainDelegatedEventRoot(root, type) {
4442
- const state = __mreactDelegatedEventState();
4443
- let rootsByType = state.roots.get(root);
4444
-
4445
- if (rootsByType === undefined) {
4446
- rootsByType = new Map();
4447
- state.roots.set(root, rootsByType);
4448
- }
4449
-
4450
- const current = rootsByType.get(type);
4451
-
4452
- if (current !== undefined) {
4453
- current.count += 1;
4454
- return;
4455
- }
4456
-
4457
- const listener = (event) => __mreactDispatchDelegatedEvent(root, type, event);
4458
- rootsByType.set(type, { count: 1, listener });
4459
- root.addEventListener(type, listener);
4460
- }
4461
-
4462
- function __mreactReleaseDelegatedEventRoot(root, type) {
4463
- const rootsByType = __mreactDelegatedEventState().roots.get(root);
4464
- const current = rootsByType?.get(type);
4465
-
4466
- if (rootsByType === undefined || current === undefined) {
4467
- return;
4468
- }
4469
-
4470
- current.count -= 1;
4471
-
4472
- if (current.count > 0) {
4473
- return;
4474
- }
4475
-
4476
- root.removeEventListener(type, current.listener);
4477
- rootsByType.delete(type);
4478
- }
4479
-
4480
- function __mreactDispatchDelegatedEvent(root, type, event) {
4481
- const state = __mreactDelegatedEventState();
4482
-
4483
- for (const target of event.composedPath()) {
4484
- if (target === root) {
4485
- break;
4486
- }
4487
-
4488
- if (!(target instanceof HTMLElement)) {
4489
- continue;
4490
- }
4491
-
4492
- const listeners = state.elements.get(target)?.get(type);
4493
-
4494
- if (listeners === undefined || listeners.length === 0) {
4495
- continue;
4496
- }
4497
-
4498
- const activeListeners = listeners.slice();
4499
-
4500
- for (const listener of activeListeners) {
4501
- __mreactCallWithCurrentTarget(listener, event, target);
4502
- }
4503
-
4504
- if (event.cancelBubble) {
4505
- break;
4506
- }
4507
- }
4508
- }
4509
-
4510
- function __mreactCallWithCurrentTarget(listener, event, currentTarget) {
4511
- const descriptor = Object.getOwnPropertyDescriptor(event, "currentTarget");
4512
-
4513
- Object.defineProperty(event, "currentTarget", {
4514
- configurable: true,
4515
- value: currentTarget,
4516
- });
4517
-
4518
- try {
4519
- listener.call(currentTarget, event);
4520
- } finally {
4521
- if (descriptor === undefined) {
4522
- delete event.currentTarget;
4523
- } else {
4524
- Object.defineProperty(event, "currentTarget", descriptor);
4525
- }
4526
- }
4527
- }
4418
+ ${routeEventBindingSyncFunction}
4528
4419
 
4529
4420
  function __mreactSyncAttributes(current, next) {
4530
4421
  for (const attribute of Array.from(current.attributes)) {
@@ -4653,6 +4544,10 @@ function workspaceRuntimePlugin(options: { routeFiles: readonly string[] }) {
4653
4544
  packageFile("reactive-core", "@reckona/mreact-reactive-core", "internal"),
4654
4545
  ],
4655
4546
  ["@reckona/mreact-compat", reactCompatPath],
4547
+ [
4548
+ "@reckona/mreact-reactive-dom/internal",
4549
+ packageFile("reactive-dom", "@reckona/mreact-reactive-dom", "internal"),
4550
+ ],
4656
4551
  [
4657
4552
  "@reckona/mreact-compat/event-priority",
4658
4553
  packageFile("react-compat", "@reckona/mreact-compat", "event-priority"),
@@ -4733,9 +4628,12 @@ export function cell(initial) {
4733
4628
  }));
4734
4629
  buildApi.onLoad({ filter: /^devtools$/, namespace: "mreact-devtools-stub" }, () => ({
4735
4630
  contents: `export function emitReactiveDevtoolsEvent() {}
4631
+ export function emitReactiveEffectRunDevtoolsEvent() {}
4736
4632
  export function hasReactiveDevtoolsEmitter() { return false; }
4737
4633
  export function currentDevtoolsEmitter() { return undefined; }
4738
- export function currentReactiveDevtools() { return undefined; }`,
4634
+ export function currentReactiveDevtools() { return undefined; }
4635
+ export function invalidateReactiveDevtoolsCache() {}
4636
+ export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`,
4739
4637
  loader: "ts",
4740
4638
  }));
4741
4639
  buildApi.onLoad({ filter: /\.(?:mreact\.)?[cm]?[jt]sx$/ }, async (args) => {
@@ -4813,7 +4711,56 @@ function importerInRuntimePackage(
4813
4711
  * not mistaken for a real export.
4814
4712
  */
4815
4713
  export function detectClientNavigationHint(source: string): boolean {
4816
- return readTopLevelBooleanExport({ code: source, name: "clientNavigation" }) ?? true;
4714
+ return detectClientNavigationOverride(source) ?? true;
4715
+ }
4716
+
4717
+ export function detectClientNavigationOverride(source: string): boolean | undefined {
4718
+ return readTopLevelBooleanExport({ code: source, name: "clientNavigation" });
4719
+ }
4720
+
4721
+ export function detectAnchorElementUsage(source: string, filename?: string | undefined): boolean {
4722
+ const context = createCompilerModuleContext({ code: source, filename });
4723
+ return containsAnchorElementWithHref(context.program);
4724
+ }
4725
+
4726
+ function containsAnchorElementWithHref(value: unknown): boolean {
4727
+ if (value === null || typeof value !== "object") {
4728
+ return false;
4729
+ }
4730
+
4731
+ if (Array.isArray(value)) {
4732
+ return value.some((entry) => containsAnchorElementWithHref(entry));
4733
+ }
4734
+
4735
+ const node = value as {
4736
+ attributes?: unknown;
4737
+ name?: { name?: unknown } | undefined;
4738
+ openingElement?:
4739
+ | {
4740
+ attributes?: unknown;
4741
+ name?: { name?: unknown } | undefined;
4742
+ }
4743
+ | undefined;
4744
+ type?: unknown;
4745
+ };
4746
+
4747
+ if (node.type === "JSXElement" && node.openingElement !== undefined) {
4748
+ const opening = node.openingElement;
4749
+ const tagName = opening?.name?.name;
4750
+
4751
+ if (
4752
+ tagName === "a" &&
4753
+ Array.isArray(opening.attributes) &&
4754
+ opening.attributes.some((attribute) => {
4755
+ const jsxAttribute = attribute as { name?: { name?: unknown }; type?: unknown };
4756
+ return jsxAttribute.type === "JSXAttribute" && jsxAttribute.name?.name === "href";
4757
+ })
4758
+ ) {
4759
+ return true;
4760
+ }
4761
+ }
4762
+
4763
+ return Object.values(value).some((entry) => containsAnchorElementWithHref(entry));
4817
4764
  }
4818
4765
 
4819
4766
  // `Link` ships from the dedicated `/link` subpath and is also re-exported from
package/src/render.ts CHANGED
@@ -2658,6 +2658,7 @@ export async function bundleMiddlewareModuleCode(options: {
2658
2658
  code: options.code,
2659
2659
  define: options.define,
2660
2660
  filename: options.file,
2661
+ externalizeMreactRuntimeAliases: true,
2661
2662
  platform: "node",
2662
2663
  root: options.importPolicy?.projectRoot,
2663
2664
  plugins: [
package/src/vite.ts CHANGED
@@ -25,6 +25,7 @@ import type { AppRouterImportPolicy } from "./import-policy.js";
25
25
  import {
26
26
  collectClientRouteReferences,
27
27
  createClientRouteInferenceCache,
28
+ detectAnchorElementUsage,
28
29
  formatClientRouteInferenceDiagnostic,
29
30
  isClientRouteSource,
30
31
  navigationRuntimeLinkDisabledDiagnostic,
@@ -149,6 +150,10 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
149
150
  packageFile("reactive-core", "@reckona/mreact-reactive-core", "internal"),
150
151
  ],
151
152
  ["@reckona/mreact-reactive-dom", reactiveDomPath],
153
+ [
154
+ "@reckona/mreact-reactive-dom/internal",
155
+ packageFile("reactive-dom", "@reckona/mreact-reactive-dom", "internal"),
156
+ ],
152
157
  ["@reckona/mreact-compat", reactCompatPath],
153
158
  [
154
159
  "@reckona/mreact-compat/event-priority",
@@ -699,12 +704,21 @@ export async function renderAppRouterClientAsset(
699
704
  let bundle: string;
700
705
 
701
706
  try {
707
+ const navigation = await resolveNavigationRuntime({
708
+ appDir,
709
+ code,
710
+ filename: route.file,
711
+ references,
712
+ vitePlugins: options.vitePlugins,
713
+ });
702
714
  bundle = await buildClientRouteBundle({
703
715
  code: clientSource,
704
716
  clientBoundaryImports: references.clientBoundaryImports,
705
717
  clientReferenceImports: references.clientReferenceImports,
706
718
  clientReferenceManifest: references.clientReferenceManifest,
719
+ clientNavigation: navigation || detectAnchorElementUsage(clientSource, route.file),
707
720
  filename: route.file,
721
+ routeMayUseOutOfOrderFragments: true,
708
722
  routePath: route.path,
709
723
  vitePlugins: options.vitePlugins,
710
724
  });
@@ -749,12 +763,21 @@ async function renderAppRouterClientRouteDevModule(
749
763
  );
750
764
  }
751
765
 
766
+ const navigation = await resolveNavigationRuntime({
767
+ appDir,
768
+ code,
769
+ filename: route.file,
770
+ references,
771
+ vitePlugins: options.vitePlugins,
772
+ });
752
773
  const entry = await buildClientRouteEntrySource({
753
774
  code: clientSource,
754
775
  clientBoundaryImports: references.clientBoundaryImports,
755
776
  clientReferenceImports: references.clientReferenceImports,
756
777
  clientReferenceManifest: references.clientReferenceManifest,
778
+ clientNavigation: navigation || detectAnchorElementUsage(clientSource, route.file),
757
779
  filename: route.file,
780
+ routeMayUseOutOfOrderFragments: true,
758
781
  routePath: route.path,
759
782
  vitePlugins: options.vitePlugins,
760
783
  });