@reckona/mreact-router 0.0.195 → 0.0.196

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
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import { builtinModules } from "node:module";
4
- import { dirname, extname, join, relative, sep } from "node:path";
4
+ import { basename, dirname, extname, isAbsolute, join, relative, sep } from "node:path";
5
5
  import {
6
6
  analyzeBoundaryGraph,
7
7
  collectClientRouteModuleAnalysis,
@@ -68,6 +68,7 @@ export interface ClientRouteManifestEntry {
68
68
  export interface BuildClientRouteOutputOptions {
69
69
  cacheDir?: string | undefined;
70
70
  code: string;
71
+ debugLabels?: boolean | undefined;
71
72
  clientBoundaryImports?: readonly string[] | undefined;
72
73
  clientReferenceImports?: readonly ClientReferenceImport[] | undefined;
73
74
  clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
@@ -2104,6 +2105,13 @@ export async function buildClientRouteOutput(
2104
2105
  const entry = await buildClientRouteEntrySource(options);
2105
2106
  const dropConsoleFunctions =
2106
2107
  options.dropConsoleFunctions ?? resolveClientConsolePureFunctions(options.dropClientConsole);
2108
+ const sourceRegionModulePaths =
2109
+ options.debugLabels === true ? undefined : new Set([options.filename]);
2110
+ const runtimePlugin = workspaceRuntimePlugin({
2111
+ debugLabels: options.debugLabels === true,
2112
+ routeFiles: [options.filename],
2113
+ sourceRegionModulePaths,
2114
+ });
2107
2115
  const bundled = await bundleRouterModule({
2108
2116
  code: entry.code,
2109
2117
  cacheDir: options.cacheDir,
@@ -2115,7 +2123,8 @@ export async function buildClientRouteOutput(
2115
2123
  minify: options.minify === true,
2116
2124
  platform: "browser",
2117
2125
  preserveExports: true,
2118
- plugins: [workspaceRuntimePlugin({ routeFiles: [options.filename] })],
2126
+ sourceRegionModulePaths,
2127
+ plugins: [runtimePlugin],
2119
2128
  sourceMap: options.sourceMap,
2120
2129
  vitePlugins: options.vitePlugins,
2121
2130
  });
@@ -2149,6 +2158,15 @@ export async function buildClientRouteBatchOutput(options: {
2149
2158
  }),
2150
2159
  })),
2151
2160
  );
2161
+ const debugLabels = options.routes.some((route) => route.debugLabels === true);
2162
+ const sourceRegionModulePaths = debugLabels
2163
+ ? undefined
2164
+ : new Set(entries.map((entry) => entry.filename));
2165
+ const runtimePlugin = workspaceRuntimePlugin({
2166
+ debugLabels,
2167
+ routeFiles: entries.map((entry) => entry.filename),
2168
+ sourceRegionModulePaths,
2169
+ });
2152
2170
  const bundled = await bundleRouterModules({
2153
2171
  base: options.assetBaseUrl ?? "/_mreact/client/",
2154
2172
  cacheDir: options.cacheDir,
@@ -2162,7 +2180,8 @@ export async function buildClientRouteBatchOutput(options: {
2162
2180
  })),
2163
2181
  minify: options.minify === true,
2164
2182
  platform: "browser",
2165
- plugins: [workspaceRuntimePlugin({ routeFiles: entries.map((entry) => entry.filename) })],
2183
+ sourceRegionModulePaths,
2184
+ plugins: [runtimePlugin],
2166
2185
  root: options.projectRoot,
2167
2186
  sourceMap: options.sourceMap,
2168
2187
  dropConsoleFunctions: options.dropConsoleFunctions,
@@ -2199,13 +2218,22 @@ export async function buildClientRouteEntrySource(
2199
2218
  filename: options.filename,
2200
2219
  });
2201
2220
  const routeSourceAnalysis = collectClientRouteModuleAnalysisFromContext(moduleContext);
2221
+ const compilerFilename =
2222
+ options.debugLabels === true ? basename(options.filename) : options.filename;
2223
+ const compilerModuleContext =
2224
+ compilerFilename === options.filename
2225
+ ? moduleContext
2226
+ : createCompilerModuleContext({
2227
+ code: options.code,
2228
+ filename: compilerFilename,
2229
+ });
2202
2230
  const compiled = transformCompilerModuleContext({
2203
2231
  code: options.code,
2204
2232
  clientBoundaryImports: options.clientBoundaryImports ?? [],
2205
- filename: options.filename,
2206
- moduleContext,
2233
+ filename: compilerFilename,
2234
+ moduleContext: compilerModuleContext,
2207
2235
  target: "client",
2208
- dev: options.minify !== true,
2236
+ dev: options.debugLabels === true,
2209
2237
  });
2210
2238
 
2211
2239
  if (compiled.diagnostics.length > 0) {
@@ -2238,7 +2266,12 @@ export async function buildClientRouteEntrySource(
2238
2266
  const routeId = routeIdForPath(options.routePath);
2239
2267
  const routeUsesCells = detectRouteCellStateHint(compiled.code);
2240
2268
  const routeUsesReactiveEffect = detectRouteReactiveEffectHint(compiled.code);
2241
- const routeUsesCleanupScope = routeUsesCells || routeUsesReactiveEffect;
2269
+ const routeUsesDomRefs = compiled.metadata.imports.some(
2270
+ (entry) =>
2271
+ entry.source === "@reckona/mreact-reactive-dom" &&
2272
+ entry.specifiers.includes("bindDomRef"),
2273
+ );
2274
+ const routeUsesCleanupScope = routeUsesCells || routeUsesReactiveEffect || routeUsesDomRefs;
2242
2275
  const routeExplicitlyRequiresHydration = isExplicitClientRouteSource(
2243
2276
  routeSourceAnalysis,
2244
2277
  options.filename,
@@ -2254,6 +2287,7 @@ export async function buildClientRouteEntrySource(
2254
2287
  routeExplicitlyRequiresHydration ||
2255
2288
  routeUsesCells ||
2256
2289
  routeUsesReactiveEffect ||
2290
+ routeUsesDomRefs ||
2257
2291
  routeHasEventBindings;
2258
2292
  const routeUsesOnlyClientReferenceBoundaries =
2259
2293
  !routeRequiresFullHydration &&
@@ -2276,7 +2310,7 @@ export async function buildClientRouteEntrySource(
2276
2310
  ? `import { bindCapturedEvent as __mreactBindCapturedEvent } from "@reckona/mreact-reactive-dom/internal";\n`
2277
2311
  : "";
2278
2312
  const routeReactiveDomMetadataImport = !routeUsesOnlyClientReferenceBoundaries
2279
- ? `${routeCapturedEventImport}import { withEventBindingMetadata as __mreactWithEventBindingMetadata, withPropBindingMetadata as __mreactWithPropBindingMetadata } from "@reckona/mreact-reactive-dom";\n`
2313
+ ? `${routeCapturedEventImport}import { ${routeUsesDomRefs ? "getDomRefBindings as __mreactGetDomRefBindings, " : ""}withEventBindingMetadata as __mreactWithEventBindingMetadata, withPropBindingMetadata as __mreactWithPropBindingMetadata } from "@reckona/mreact-reactive-dom";\n`
2280
2314
  : "";
2281
2315
  const navigationStateDeclaration = inlineClientNavigation
2282
2316
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
@@ -2552,9 +2586,10 @@ __mreactGlobal.__mreactRouteCell = (nativeCell, initial) => {
2552
2586
  __mreactActiveCellIndex = 0;
2553
2587
  }
2554
2588
  return () => {
2555
- for (const __mreactDispose of Array.from(__mreactRouteEffectDisposers)) {
2556
- __mreactDispose();
2557
- }
2589
+ __mreactRunLifecycleTasks(
2590
+ Array.from(__mreactRouteEffectDisposers),
2591
+ (__mreactDispose) => __mreactDispose(),
2592
+ );
2558
2593
  __mreactRouteEffectDisposers.clear();
2559
2594
  };
2560
2595
  });
@@ -2566,13 +2601,33 @@ __mreactGlobal.__mreactRouteCell = (nativeCell, initial) => {
2566
2601
  ? ` __mreactDisposeRoute(__mreactRouteId);
2567
2602
  const __mreactRouteEffectDisposers = new Set();
2568
2603
  __mreactRouteDisposers.set(__mreactRouteId, () => {
2569
- for (const __mreactDispose of Array.from(__mreactRouteEffectDisposers)) {
2570
- __mreactDispose();
2571
- }
2604
+ __mreactRunLifecycleTasks(
2605
+ Array.from(__mreactRouteEffectDisposers),
2606
+ (__mreactDispose) => __mreactDispose(),
2607
+ );
2572
2608
  __mreactRouteEffectDisposers.clear();
2573
2609
  });
2574
2610
  `
2575
2611
  : "";
2612
+ const routeLifecycleFunctions = `
2613
+ function __mreactRunLifecycleTasks(values, run) {
2614
+ let firstError;
2615
+
2616
+ for (const value of values) {
2617
+ try {
2618
+ run(value);
2619
+ } catch (error) {
2620
+ firstError ??= error;
2621
+ }
2622
+ }
2623
+
2624
+ if (firstError !== undefined) {
2625
+ queueMicrotask(() => {
2626
+ throw firstError;
2627
+ });
2628
+ }
2629
+ }
2630
+ `;
2576
2631
  const routeCellDropFunction = routeUsesCells
2577
2632
  ? `
2578
2633
  function __mreactDropMismatchedRouteState(previousState, nextState) {
@@ -2598,12 +2653,18 @@ function __mreactDisposeRoute(routeId) {
2598
2653
  }
2599
2654
  `
2600
2655
  : "";
2601
- const routeCleanupNavigationDispose = routeUsesCleanupScope
2602
- ? ` if (currentRouteId !== nextRouteId) {
2603
- __mreactDisposeRoute(currentRouteId);
2656
+ const routeCleanupNavigationDispose = ` if (currentRouteId !== nextRouteId) {
2657
+ const __mreactRegisteredRouteDisposers = __mreactGlobal.__mreactRouteDisposers;
2658
+ const __mreactRegisteredRouteDispose = __mreactRegisteredRouteDisposers?.get(currentRouteId);
2659
+ if (__mreactRegisteredRouteDispose !== undefined) {
2660
+ __mreactRegisteredRouteDisposers.delete(currentRouteId);
2661
+ __mreactRunLifecycleTasks(
2662
+ [__mreactRegisteredRouteDispose],
2663
+ (__mreactDispose) => __mreactDispose(),
2664
+ );
2665
+ }
2604
2666
  }
2605
- `
2606
- : "";
2667
+ `;
2607
2668
  const routeNodeResolver = routeUsesCells
2608
2669
  ? `
2609
2670
  function __mreactResolveRouteNode(value) {
@@ -2642,9 +2703,7 @@ function __mreactResolveRouteNode(value) {
2642
2703
  const previousDisposers = current.__mreactEventDisposers;
2643
2704
 
2644
2705
  if (Array.isArray(previousDisposers)) {
2645
- for (const dispose of previousDisposers) {
2646
- dispose();
2647
- }
2706
+ __mreactRunLifecycleTasks(previousDisposers, (dispose) => dispose());
2648
2707
  }
2649
2708
 
2650
2709
  const rawBindings = next.__mreactEventBindings;
@@ -2673,14 +2732,26 @@ function __mreactResolveRouteNode(value) {
2673
2732
  const previousDisposers = current.__mreactEventDisposers;
2674
2733
 
2675
2734
  if (Array.isArray(previousDisposers)) {
2676
- for (const dispose of previousDisposers) {
2677
- dispose();
2678
- }
2735
+ __mreactRunLifecycleTasks(previousDisposers, (dispose) => dispose());
2679
2736
  }
2680
2737
 
2681
2738
  current.__mreactEventDisposers = [];
2682
2739
  current.__mreactHasEvents = false;
2683
2740
  }
2741
+ `;
2742
+ const routeDomRefBindingSyncFunction = routeUsesDomRefs
2743
+ ? `function __mreactSyncDomRefBindings(current, next) {
2744
+ __mreactRunLifecycleTasks(
2745
+ Array.from(__mreactGetDomRefBindings(current)),
2746
+ (binding) => binding.dispose(),
2747
+ );
2748
+ __mreactRunLifecycleTasks(
2749
+ Array.from(__mreactGetDomRefBindings(next)),
2750
+ (binding) => binding.retarget(current),
2751
+ );
2752
+ }
2753
+ `
2754
+ : `function __mreactSyncDomRefBindings() {}
2684
2755
  `;
2685
2756
  const boundaryOnlyHydrationBlock = routeRequiresFullHydration
2686
2757
  ? ""
@@ -2740,6 +2811,7 @@ ${routeCellHydrationIndent}__mreactMarker.setAttribute(__mreactRouteHydratedAttr
2740
2811
  ${routeCellHydrationIndent}__mreactMarkRouteHydrated();
2741
2812
  ${routeCellHydrationEnd}}
2742
2813
  ${routeCellDropFunction}
2814
+ ${routeLifecycleFunctions}
2743
2815
  ${routeCleanupFunction}
2744
2816
 
2745
2817
  function __mreactMarkRouteHydrated() {
@@ -3327,13 +3399,14 @@ function __mreactApplyNavigationHtml(html, url) {
3327
3399
  const currentRouteId = currentMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
3328
3400
  const nextRouteId = nextMarker.getAttribute("${routeHydrationContract.routeMarkerAttribute}");
3329
3401
 
3402
+ ${routeCleanupNavigationDispose}
3330
3403
  __mreactMarkRouteHydrating();
3331
3404
  __mreactSyncHeadMetadata(template.content, html);
3332
3405
  if (!__mreactApplyNavigationShellHtml(currentMarker, nextMarker)) {
3333
3406
  __mreactUnmountCompatBoundaries(currentMarker);
3334
3407
  __mreactResumeNode(currentMarker, nextMarker);
3335
3408
  }
3336
- ${routeCleanupNavigationDispose} __mreactSyncRouteDataScripts(template.content, currentRouteId, nextRouteId);
3409
+ __mreactSyncRouteDataScripts(template.content, currentRouteId, nextRouteId);
3337
3410
 
3338
3411
  const script = template.content.querySelector('script[type="module"][src]')?.getAttribute("src");
3339
3412
  if (script !== null && script !== undefined) {
@@ -4491,6 +4564,7 @@ function __mreactResumeNode(current, next) {
4491
4564
  }
4492
4565
 
4493
4566
  __mreactSyncEventBindings(current, next);
4567
+ __mreactSyncDomRefBindings(current, next);
4494
4568
  __mreactSyncAttributes(current, next);
4495
4569
  __mreactSyncPropBindings(current, next);
4496
4570
  __mreactResumeChildren(current, next);
@@ -4513,6 +4587,7 @@ function __mreactShouldReplaceNode(current, next) {
4513
4587
  }
4514
4588
 
4515
4589
  ${routeEventBindingSyncFunction}
4590
+ ${routeDomRefBindingSyncFunction}
4516
4591
 
4517
4592
  function __mreactSyncAttributes(current, next) {
4518
4593
  for (const attribute of Array.from(current.attributes)) {
@@ -4532,9 +4607,7 @@ function __mreactSyncPropBindings(current, next) {
4532
4607
  const previousBindings = current.__mreactPropBindings;
4533
4608
 
4534
4609
  if (Array.isArray(previousBindings)) {
4535
- for (const binding of previousBindings) {
4536
- binding.dispose?.();
4537
- }
4610
+ __mreactRunLifecycleTasks(previousBindings, (binding) => binding.dispose?.());
4538
4611
  }
4539
4612
 
4540
4613
  const bindings = next.__mreactPropBindings;
@@ -4550,9 +4623,7 @@ function __mreactSyncPropBindings(current, next) {
4550
4623
  next.__mreactPropBindings = [];
4551
4624
  next.__mreactHasReactiveProps = false;
4552
4625
 
4553
- for (const binding of bindings) {
4554
- binding.retarget?.(current);
4555
- }
4626
+ __mreactRunLifecycleTasks(bindings, (binding) => binding.retarget?.(current));
4556
4627
  }
4557
4628
 
4558
4629
  function __mreactResumeChildren(current, next) {
@@ -4605,7 +4676,11 @@ function __mreactResumeChildren(current, next) {
4605
4676
  };
4606
4677
  }
4607
4678
 
4608
- function workspaceRuntimePlugin(options: { routeFiles: readonly string[] }) {
4679
+ function workspaceRuntimePlugin(options: {
4680
+ debugLabels: boolean;
4681
+ routeFiles: readonly string[];
4682
+ sourceRegionModulePaths?: Set<string> | undefined;
4683
+ }) {
4609
4684
  const routeFiles = new Set(options.routeFiles);
4610
4685
  const packageFile = (monorepoDir: string, packageName: string, entry: string): string =>
4611
4686
  workspacePackageFile({
@@ -4733,15 +4808,31 @@ export function invalidateReactiveDevtoolsCache() {}
4733
4808
  export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`,
4734
4809
  loader: "ts",
4735
4810
  }));
4811
+ if (options.sourceRegionModulePaths !== undefined) {
4812
+ buildApi.onLoad({ filter: /.*/ }, (args) => {
4813
+ if (
4814
+ isAbsolute(args.path) &&
4815
+ isRouteClientDependencySourcePath(args.path, routeFiles) &&
4816
+ !runtimePackageDirs.some(
4817
+ (runtimePackageDir) => args.path.startsWith(`${runtimePackageDir}${sep}`),
4818
+ )
4819
+ ) {
4820
+ options.sourceRegionModulePaths?.add(args.path);
4821
+ }
4822
+
4823
+ return undefined;
4824
+ });
4825
+ }
4736
4826
  buildApi.onLoad({ filter: /\.(?:mreact\.)?[cm]?[jt]sx$/ }, async (args) => {
4737
4827
  if (!isRouteClientDependencySourcePath(args.path, routeFiles)) {
4738
4828
  return undefined;
4739
4829
  }
4740
4830
 
4741
4831
  const source = await readFile(args.path, "utf8");
4832
+ const compilerFilename = options.debugLabels ? basename(args.path) : args.path;
4742
4833
  const moduleContext = createCompilerModuleContext({
4743
4834
  code: source,
4744
- filename: args.path,
4835
+ filename: compilerFilename,
4745
4836
  });
4746
4837
 
4747
4838
  if (!hasJsxSyntax(moduleContext.program)) {
@@ -4750,8 +4841,8 @@ export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`,
4750
4841
 
4751
4842
  const output = transformCompilerModuleContext({
4752
4843
  code: source,
4753
- dev: true,
4754
- filename: args.path,
4844
+ dev: options.debugLabels,
4845
+ filename: compilerFilename,
4755
4846
  mode: isCompatSourcePath(args.path) ? "compat" : "reactive",
4756
4847
  moduleContext,
4757
4848
  target: "client",
package/src/vite.ts CHANGED
@@ -848,6 +848,7 @@ async function renderAppRouterClientRouteDevModule(
848
848
  clientReferenceImports: references.clientReferenceImports,
849
849
  clientReferenceManifest: references.clientReferenceManifest,
850
850
  clientNavigation: navigation || detectAnchorElementUsage(clientSource, route.file),
851
+ debugLabels: true,
851
852
  filename: route.file,
852
853
  routeMayUseOutOfOrderFragments: true,
853
854
  routePath: route.path,