@reckona/mreact-router 0.0.87 → 0.0.89

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
@@ -29,6 +29,10 @@ import {
29
29
  type RouterBundleAssetOutput,
30
30
  type RouterBundleChunkOutput,
31
31
  } from "./bundle-pipeline.js";
32
+ import {
33
+ resolveClientConsolePureFunctions,
34
+ type AppRouterProductionOptions,
35
+ } from "./config.js";
32
36
  import type { AppRoute } from "./routes.js";
33
37
  import { existingRouteShellCandidates } from "./route-shells.js";
34
38
  import {
@@ -65,6 +69,8 @@ export interface BuildClientRouteOutputOptions {
65
69
  clientBoundaryImports?: readonly string[] | undefined;
66
70
  clientReferenceImports?: readonly ClientReferenceImport[] | undefined;
67
71
  clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
72
+ dropClientConsole?: AppRouterProductionOptions["dropClientConsole"] | undefined;
73
+ dropConsoleFunctions?: readonly string[] | undefined;
68
74
  filename: string;
69
75
  minify?: boolean | undefined;
70
76
  routePath: string;
@@ -1422,6 +1428,7 @@ export async function buildClientRouteBundle(options: {
1422
1428
 
1423
1429
  export async function buildNavigationRuntimeBundle(
1424
1430
  options: {
1431
+ dropConsoleFunctions?: readonly string[] | undefined;
1425
1432
  minify?: boolean;
1426
1433
  sourceMap?: boolean;
1427
1434
  } = {},
@@ -1431,6 +1438,9 @@ export async function buildNavigationRuntimeBundle(
1431
1438
  filename: "__mreact_navigation_runtime.tsx",
1432
1439
  routePath: "/__mreact_navigation_runtime",
1433
1440
  clientNavigation: true,
1441
+ ...(options.dropConsoleFunctions === undefined
1442
+ ? {}
1443
+ : { dropConsoleFunctions: options.dropConsoleFunctions }),
1434
1444
  ...(options.minify === undefined ? {} : { minify: options.minify }),
1435
1445
  ...(options.sourceMap === undefined ? {} : { sourceMap: options.sourceMap }),
1436
1446
  });
@@ -1440,12 +1450,15 @@ export async function buildClientRouteOutput(
1440
1450
  options: BuildClientRouteOutputOptions,
1441
1451
  ): Promise<{ code: string; map?: string }> {
1442
1452
  const entry = await buildClientRouteEntrySource(options);
1453
+ const dropConsoleFunctions =
1454
+ options.dropConsoleFunctions ?? resolveClientConsolePureFunctions(options.dropClientConsole);
1443
1455
  const bundled = await bundleRouterModule({
1444
1456
  code: entry.code,
1445
1457
  define: {
1446
1458
  __MREACT_CLIENT_DEVTOOLS__: "false",
1447
1459
  },
1448
1460
  filename: options.filename,
1461
+ dropConsoleFunctions,
1449
1462
  minify: options.minify === true,
1450
1463
  platform: "browser",
1451
1464
  preserveExports: true,
@@ -1462,6 +1475,7 @@ export async function buildClientRouteOutput(
1462
1475
 
1463
1476
  export async function buildClientRouteBatchOutput(options: {
1464
1477
  assetBaseUrl?: string | undefined;
1478
+ dropConsoleFunctions?: readonly string[] | undefined;
1465
1479
  minify?: boolean;
1466
1480
  projectRoot?: string | undefined;
1467
1481
  routes: readonly BuildClientRouteOutputOptions[];
@@ -1496,6 +1510,7 @@ export async function buildClientRouteBatchOutput(options: {
1496
1510
  plugins: [workspaceRuntimePlugin({ routeFiles: entries.map((entry) => entry.filename) })],
1497
1511
  root: options.projectRoot,
1498
1512
  sourceMap: options.sourceMap,
1513
+ dropConsoleFunctions: options.dropConsoleFunctions,
1499
1514
  vitePlugins: options.vitePlugins,
1500
1515
  });
1501
1516
  const entryChunks = new Map(
@@ -2239,7 +2254,10 @@ function __mreactApplyNavigationHtml(html, url) {
2239
2254
 
2240
2255
  __mreactMarkRouteHydrating();
2241
2256
  __mreactSyncHeadMetadata(template.content, html);
2242
- __mreactResumeNode(currentMarker, nextMarker);
2257
+ if (!__mreactApplyNavigationShellHtml(currentMarker, nextMarker)) {
2258
+ __mreactUnmountCompatBoundaries(currentMarker);
2259
+ __mreactResumeNode(currentMarker, nextMarker);
2260
+ }
2243
2261
  ${routeCleanupNavigationDispose} __mreactSyncRouteDataScripts(template.content, currentRouteId, nextRouteId);
2244
2262
 
2245
2263
  const script = template.content.querySelector('script[type="module"][src]')?.getAttribute("src");
@@ -2255,6 +2273,111 @@ ${routeCleanupNavigationDispose} __mreactSyncRouteDataScripts(template.content,
2255
2273
  return true;
2256
2274
  }
2257
2275
 
2276
+ function __mreactApplyNavigationShellHtml(currentMarker, nextMarker) {
2277
+ const target = __mreactNavigationShellSyncTarget(currentMarker, nextMarker);
2278
+
2279
+ if (target === null) {
2280
+ return false;
2281
+ }
2282
+
2283
+ __mreactUnmountCompatBoundaries(target.current);
2284
+ __mreactResumeNode(target.current, target.next);
2285
+ return true;
2286
+ }
2287
+
2288
+ function __mreactNavigationShellSyncTarget(currentMarker, nextMarker) {
2289
+ const currentShells = __mreactMarkerShellAncestors(currentMarker);
2290
+ const nextShells = __mreactMarkerShellAncestors(nextMarker);
2291
+ const commonLength = Math.min(currentShells.length, nextShells.length);
2292
+ let commonIndex = -1;
2293
+
2294
+ for (let index = 0; index < commonLength; index += 1) {
2295
+ if (!__mreactSameNavigationShellBoundary(currentShells[index], nextShells[index])) {
2296
+ break;
2297
+ }
2298
+
2299
+ commonIndex = index;
2300
+ }
2301
+
2302
+ if (commonIndex >= 0) {
2303
+ return __mreactNavigationShellDomTarget(currentShells[commonIndex], nextShells[commonIndex]);
2304
+ }
2305
+
2306
+ if (currentShells.length === 0 || nextShells.length === 0) {
2307
+ return null;
2308
+ }
2309
+
2310
+ const currentRoot = currentShells[0];
2311
+ const nextRoot = nextShells[0];
2312
+
2313
+ if (currentRoot.tagName !== nextRoot.tagName) {
2314
+ return null;
2315
+ }
2316
+
2317
+ return __mreactNavigationShellDomTarget(currentRoot, nextRoot);
2318
+ }
2319
+
2320
+ function __mreactMarkerShellAncestors(marker) {
2321
+ const shells = [];
2322
+ let current = marker.parentElement;
2323
+
2324
+ while (current !== null) {
2325
+ if (__mreactIsNavigationShellBoundary(current)) {
2326
+ shells.push(current);
2327
+ }
2328
+
2329
+ current = current.parentElement;
2330
+ }
2331
+
2332
+ shells.reverse();
2333
+ return shells;
2334
+ }
2335
+
2336
+ function __mreactIsNavigationShellBoundary(element) {
2337
+ return element.hasAttribute("data-mreact-layout-boundary") ||
2338
+ element.hasAttribute("data-mreact-template-boundary");
2339
+ }
2340
+
2341
+ function __mreactSameNavigationShellBoundary(current, next) {
2342
+ const currentKind = __mreactNavigationShellBoundaryKind(current);
2343
+ const nextKind = __mreactNavigationShellBoundaryKind(next);
2344
+
2345
+ if (currentKind === null || currentKind !== nextKind || current.tagName !== next.tagName) {
2346
+ return false;
2347
+ }
2348
+
2349
+ const attribute = currentKind === "layout"
2350
+ ? "data-mreact-layout-boundary"
2351
+ : "data-mreact-template-boundary";
2352
+
2353
+ return current.getAttribute(attribute) === next.getAttribute(attribute);
2354
+ }
2355
+
2356
+ function __mreactNavigationShellBoundaryKind(element) {
2357
+ if (element.hasAttribute("data-mreact-layout-boundary")) {
2358
+ return "layout";
2359
+ }
2360
+
2361
+ if (element.hasAttribute("data-mreact-template-boundary")) {
2362
+ return "template";
2363
+ }
2364
+
2365
+ return null;
2366
+ }
2367
+
2368
+ function __mreactNavigationShellDomTarget(currentShell, nextShell) {
2369
+ if (currentShell.tagName === "HTML" && nextShell.tagName === "HTML") {
2370
+ const currentBody = currentShell.querySelector("body");
2371
+ const nextBody = nextShell.querySelector("body");
2372
+
2373
+ if (currentBody !== null && nextBody !== null) {
2374
+ return { current: currentBody, next: nextBody };
2375
+ }
2376
+ }
2377
+
2378
+ return { current: currentShell, next: nextShell };
2379
+ }
2380
+
2258
2381
  function __mreactSyncHeadMetadata(root, html) {
2259
2382
  __mreactSyncHtmlLang(root, html);
2260
2383
 
@@ -2778,12 +2901,19 @@ function __mreactHydrateClientBoundaries(marker, references, components) {
2778
2901
  : JSON.parse(propsElement.textContent);
2779
2902
 
2780
2903
  if (compat) {
2781
- const container = document.createElement("span");
2904
+ const parentContainer = __mreactClientBoundaryParentContainer(placeholder, propsElement);
2905
+ const container = parentContainer ?? document.createElement("span");
2782
2906
  container.setAttribute("data-mreact-compat-boundary", name ?? "");
2783
- container.style.display = "contents";
2784
- placeholder.replaceWith(container);
2785
- __mreactCompatCreateRoot(container).render(__mreactCompatCreateElement(component, props));
2907
+ if (parentContainer === null) {
2908
+ container.style.display = "contents";
2909
+ placeholder.replaceWith(container);
2910
+ } else {
2911
+ placeholder.remove();
2912
+ }
2786
2913
  propsElement?.remove();
2914
+ const root = __mreactCompatCreateRoot(container);
2915
+ container.__mreactCompatRoot = root;
2916
+ root.render(__mreactCompatCreateElement(component, props));
2787
2917
  continue;
2788
2918
  }
2789
2919
 
@@ -2796,6 +2926,54 @@ function __mreactHydrateClientBoundaries(marker, references, components) {
2796
2926
  return true;
2797
2927
  }
2798
2928
 
2929
+ function __mreactClientBoundaryParentContainer(placeholder, propsElement) {
2930
+ const parent = placeholder.parentElement;
2931
+
2932
+ if (parent === null) {
2933
+ return null;
2934
+ }
2935
+
2936
+ for (const node of Array.from(parent.childNodes)) {
2937
+ if (node === placeholder || node === propsElement) {
2938
+ continue;
2939
+ }
2940
+
2941
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim() === "") {
2942
+ continue;
2943
+ }
2944
+
2945
+ return null;
2946
+ }
2947
+
2948
+ return parent;
2949
+ }
2950
+
2951
+ function __mreactUnmountCompatBoundaries(root) {
2952
+ const containers = [];
2953
+
2954
+ if (
2955
+ root.nodeType === Node.ELEMENT_NODE &&
2956
+ root.hasAttribute("data-mreact-compat-boundary")
2957
+ ) {
2958
+ containers.push(root);
2959
+ }
2960
+
2961
+ if (typeof root.querySelectorAll === "function") {
2962
+ containers.push(...root.querySelectorAll("[data-mreact-compat-boundary]"));
2963
+ }
2964
+
2965
+ for (const container of containers) {
2966
+ const compatRoot = container.__mreactCompatRoot;
2967
+
2968
+ if (compatRoot === undefined || typeof compatRoot.unmount !== "function") {
2969
+ continue;
2970
+ }
2971
+
2972
+ compatRoot.unmount();
2973
+ container.__mreactCompatRoot = undefined;
2974
+ }
2975
+ }
2976
+
2799
2977
  function __mreactHasNonSerializableClientBoundaries(marker) {
2800
2978
  return marker.querySelector(
2801
2979
  'template[data-mreact-client-boundary][data-mreact-client-boundary-nonserializable="true"]',
@@ -2902,6 +3080,7 @@ function __mreactResumeNode(current, next) {
2902
3080
  }
2903
3081
 
2904
3082
  if (__mreactShouldReplaceNode(current, next)) {
3083
+ __mreactUnmountCompatBoundaries(current);
2905
3084
  current.replaceWith(next);
2906
3085
  return;
2907
3086
  }
@@ -2914,6 +3093,7 @@ function __mreactResumeNode(current, next) {
2914
3093
  }
2915
3094
 
2916
3095
  if (current.nodeType !== Node.ELEMENT_NODE || next.nodeType !== Node.ELEMENT_NODE) {
3096
+ __mreactUnmountCompatBoundaries(current);
2917
3097
  current.replaceWith(next);
2918
3098
  return;
2919
3099
  }
@@ -3194,7 +3374,12 @@ function __mreactResumeChildren(current, next) {
3194
3374
  }
3195
3375
 
3196
3376
  while (current.childNodes.length > nextChildren.length) {
3197
- current.lastChild?.remove();
3377
+ const lastChild = current.lastChild;
3378
+ if (lastChild === null) {
3379
+ break;
3380
+ }
3381
+ __mreactUnmountCompatBoundaries(lastChild);
3382
+ lastChild.remove();
3198
3383
  }
3199
3384
  }
3200
3385
  `;
package/src/config.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  import { isAbsolute, relative, resolve } from "node:path";
2
2
 
3
3
  export type AppRouterBuildTarget = "node" | "cloudflare" | "aws-lambda";
4
+ export type AppRouterClientConsoleMethod = "debug" | "error" | "info" | "log" | "trace" | "warn";
4
5
  export type AppRouterClientSourceMapMode = "none" | "hidden" | "linked";
5
6
  export type AppRouterClientSourceMapOption = boolean | AppRouterClientSourceMapMode;
6
7
 
8
+ export interface AppRouterProductionOptions {
9
+ dropClientConsole?: boolean | readonly AppRouterClientConsoleMethod[] | undefined;
10
+ }
11
+
7
12
  export interface AppRouterProjectOptions {
8
13
  assetBaseUrl?: string | undefined;
9
14
  buildTargets?: readonly AppRouterBuildTarget[] | undefined;
@@ -19,6 +24,7 @@ export interface AppRouterProjectOptions {
19
24
  appDir?: string | undefined;
20
25
  allowedSourceDirs?: readonly string[] | undefined;
21
26
  projectRoot?: string | undefined;
27
+ production?: AppRouterProductionOptions | undefined;
22
28
  publicDir?: string | undefined;
23
29
  publicAssetBaseUrl?: string | undefined;
24
30
  routesDir?: string | undefined;
@@ -29,6 +35,7 @@ export interface ResolvedAppRouterProject {
29
35
  assetBaseUrl?: string | undefined;
30
36
  buildTargets: readonly AppRouterBuildTarget[];
31
37
  clientSourceMaps: AppRouterClientSourceMapMode;
38
+ clientConsolePureFunctions?: readonly string[] | undefined;
32
39
  projectRoot: string;
33
40
  publicAssetBaseUrl?: string | undefined;
34
41
  publicDir: string;
@@ -38,6 +45,10 @@ export interface ResolvedAppRouterProject {
38
45
  export function resolveAppRouterProjectOptions(
39
46
  options: AppRouterProjectOptions,
40
47
  ): ResolvedAppRouterProject {
48
+ const clientConsolePureFunctions = resolveClientConsolePureFunctions(
49
+ options.production?.dropClientConsole,
50
+ );
51
+
41
52
  if (
42
53
  options.appDir !== undefined &&
43
54
  options.projectRoot === undefined &&
@@ -52,6 +63,7 @@ export function resolveAppRouterProjectOptions(
52
63
  ...(options.assetBaseUrl === undefined ? {} : { assetBaseUrl: options.assetBaseUrl }),
53
64
  buildTargets: resolveBuildTargets(options.buildTargets),
54
65
  clientSourceMaps: resolveClientSourceMapMode(options.clientSourceMaps),
66
+ ...(clientConsolePureFunctions === undefined ? {} : { clientConsolePureFunctions }),
55
67
  projectRoot: appDir,
56
68
  ...(options.publicAssetBaseUrl === undefined
57
69
  ? {}
@@ -70,6 +82,7 @@ export function resolveAppRouterProjectOptions(
70
82
  ...(options.assetBaseUrl === undefined ? {} : { assetBaseUrl: options.assetBaseUrl }),
71
83
  buildTargets: resolveBuildTargets(options.buildTargets),
72
84
  clientSourceMaps: resolveClientSourceMapMode(options.clientSourceMaps),
85
+ ...(clientConsolePureFunctions === undefined ? {} : { clientConsolePureFunctions }),
73
86
  projectRoot,
74
87
  ...(options.publicAssetBaseUrl === undefined
75
88
  ? {}
@@ -79,6 +92,39 @@ export function resolveAppRouterProjectOptions(
79
92
  };
80
93
  }
81
94
 
95
+ const defaultDroppedClientConsoleMethods = ["debug", "info", "log"] as const;
96
+ const supportedClientConsoleMethods = new Set<AppRouterClientConsoleMethod>([
97
+ "debug",
98
+ "error",
99
+ "info",
100
+ "log",
101
+ "trace",
102
+ "warn",
103
+ ]);
104
+
105
+ export function resolveClientConsolePureFunctions(
106
+ value: AppRouterProductionOptions["dropClientConsole"],
107
+ ): readonly string[] | undefined {
108
+ if (value === undefined || value === false) {
109
+ return undefined;
110
+ }
111
+
112
+ const methods = value === true ? defaultDroppedClientConsoleMethods : value;
113
+ const uniqueMethods = [...new Set(methods)];
114
+
115
+ for (const method of uniqueMethods) {
116
+ if (!supportedClientConsoleMethods.has(method)) {
117
+ throw new Error(
118
+ `Unsupported mreactRouter production.dropClientConsole method ${JSON.stringify(method)}. Expected "debug", "error", "info", "log", "trace", or "warn".`,
119
+ );
120
+ }
121
+ }
122
+
123
+ return uniqueMethods.length === 0
124
+ ? undefined
125
+ : uniqueMethods.map((method) => `console.${method}`);
126
+ }
127
+
82
128
  export function resolveClientSourceMapMode(
83
129
  value: AppRouterClientSourceMapOption | undefined,
84
130
  ): AppRouterClientSourceMapMode {
package/src/index.ts CHANGED
@@ -105,8 +105,10 @@ export type {
105
105
  } from "./types.js";
106
106
  export type {
107
107
  AppRouterBuildTarget,
108
+ AppRouterClientConsoleMethod,
108
109
  AppRouterClientSourceMapMode,
109
110
  AppRouterClientSourceMapOption,
111
+ AppRouterProductionOptions,
110
112
  } from "./config.js";
111
113
  export type {
112
114
  AssetHelperOptions,