@reckona/mreact-router 0.0.182 → 0.0.184

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
@@ -79,6 +79,7 @@ export interface BuildClientRouteOutputOptions {
79
79
  sourceMap?: boolean | undefined;
80
80
  vitePlugins?: readonly PluginOption[] | undefined;
81
81
  clientNavigation?: boolean | undefined;
82
+ forceInlineNavigationRuntime?: boolean | undefined;
82
83
  }
83
84
 
84
85
  export interface BuildClientRouteBatchOutput {
@@ -2085,6 +2086,7 @@ export async function buildNavigationRuntimeBundle(
2085
2086
  filename: "__mreact_navigation_runtime.tsx",
2086
2087
  routePath: "/__mreact_navigation_runtime",
2087
2088
  clientNavigation: true,
2089
+ forceInlineNavigationRuntime: true,
2088
2090
  ...(options.dropConsoleFunctions === undefined
2089
2091
  ? {}
2090
2092
  : { dropConsoleFunctions: options.dropConsoleFunctions }),
@@ -2212,6 +2214,9 @@ export async function buildClientRouteEntrySource(
2212
2214
  }
2213
2215
 
2214
2216
  const clientNavigation = options.clientNavigation ?? detectClientNavigationHint(options.code);
2217
+ const inlineClientNavigation =
2218
+ clientNavigation && (options.forceInlineNavigationRuntime === true || options.minify !== true);
2219
+ const deferredClientNavigation = clientNavigation && !inlineClientNavigation;
2215
2220
  const clientReferenceManifest =
2216
2221
  options.clientReferenceManifest ?? (await inferClientReferenceManifestForBundle(options));
2217
2222
  const compatClientReferenceNames = compatClientReferenceComponentNames(clientReferenceManifest);
@@ -2259,7 +2264,7 @@ export async function buildClientRouteEntrySource(
2259
2264
  const routeReactiveDomMetadataImport = !routeUsesOnlyClientReferenceBoundaries
2260
2265
  ? `import { withEventBindingMetadata as __mreactWithEventBindingMetadata, withPropBindingMetadata as __mreactWithPropBindingMetadata } from "@reckona/mreact-reactive-dom";\n`
2261
2266
  : "";
2262
- const navigationStateDeclaration = clientNavigation
2267
+ const navigationStateDeclaration = inlineClientNavigation
2263
2268
  ? `const __mreactNavigationState = __mreactGlobal.__mreactNavigationState ??= {
2264
2269
  cache: new Map(),
2265
2270
  current: {
@@ -2280,6 +2285,188 @@ export async function buildClientRouteEntrySource(
2280
2285
  viewportObserver: undefined,
2281
2286
  };`
2282
2287
  : "";
2288
+ const deferredNavigationRuntime = deferredClientNavigation
2289
+ ? `
2290
+ let __mreactDeferredNavigationRuntime = undefined;
2291
+
2292
+ function __mreactNavigationRuntimeScript() {
2293
+ if (typeof document === "undefined") {
2294
+ return undefined;
2295
+ }
2296
+
2297
+ const element = document.getElementById("mreact-navigation-runtime");
2298
+ const text = element?.textContent;
2299
+
2300
+ if (text === undefined || text === "") {
2301
+ return undefined;
2302
+ }
2303
+
2304
+ try {
2305
+ const parsed = JSON.parse(text);
2306
+ return typeof parsed?.script === "string" ? parsed.script : undefined;
2307
+ } catch {
2308
+ return undefined;
2309
+ }
2310
+ }
2311
+
2312
+ function __mreactLoadNavigationRuntime() {
2313
+ const script = __mreactNavigationRuntimeScript();
2314
+ if (script === undefined) {
2315
+ return Promise.resolve(undefined);
2316
+ }
2317
+
2318
+ if (__mreactDeferredNavigationRuntime !== undefined) {
2319
+ return __mreactDeferredNavigationRuntime;
2320
+ }
2321
+
2322
+ __mreactDeferredNavigationRuntime = import(/* @vite-ignore */ script).catch(() => undefined);
2323
+ return __mreactDeferredNavigationRuntime;
2324
+ }
2325
+
2326
+ function __mreactDeferredAnchorFromEvent(event) {
2327
+ const target = event.target;
2328
+ const anchor = target instanceof Element ? target.closest("a[href]") : null;
2329
+
2330
+ return anchor instanceof HTMLAnchorElement ? anchor : null;
2331
+ }
2332
+
2333
+ function __mreactDeferredAnchorScrollMode(anchor) {
2334
+ return anchor.dataset.mreactScroll === "preserve" ? false : true;
2335
+ }
2336
+
2337
+ function __mreactDeferredAnchorTransitionMode(anchor) {
2338
+ return anchor.dataset.mreactTransition === "auto" ? "auto" : false;
2339
+ }
2340
+
2341
+ function __mreactDeferredIsHashOnlyNavigation(nextUrl) {
2342
+ return nextUrl.origin === location.origin &&
2343
+ nextUrl.pathname === location.pathname &&
2344
+ nextUrl.search === location.search &&
2345
+ nextUrl.hash !== "" &&
2346
+ nextUrl.hash !== location.hash;
2347
+ }
2348
+
2349
+ function __mreactDeferredIsCurrentLocationNavigation(nextUrl) {
2350
+ return nextUrl.origin === location.origin &&
2351
+ nextUrl.pathname === location.pathname &&
2352
+ nextUrl.search === location.search;
2353
+ }
2354
+
2355
+ function __mreactDeferredHandleClick(event) {
2356
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
2357
+ return;
2358
+ }
2359
+
2360
+ const anchor = __mreactDeferredAnchorFromEvent(event);
2361
+
2362
+ if (anchor === null || anchor.dataset.mreactReload === "true") {
2363
+ return;
2364
+ }
2365
+
2366
+ const nextUrl = new URL(anchor.href, location.href);
2367
+
2368
+ if (nextUrl.origin !== location.origin || __mreactDeferredIsHashOnlyNavigation(nextUrl)) {
2369
+ return;
2370
+ }
2371
+
2372
+ if (__mreactNavigationRuntimeScript() === undefined) {
2373
+ return;
2374
+ }
2375
+
2376
+ if (__mreactDeferredIsCurrentLocationNavigation(nextUrl)) {
2377
+ event.preventDefault();
2378
+
2379
+ if (__mreactDeferredAnchorScrollMode(anchor) !== false && nextUrl.hash === "") {
2380
+ scrollTo(0, 0);
2381
+ }
2382
+
2383
+ return;
2384
+ }
2385
+
2386
+ event.preventDefault();
2387
+ void __mreactLoadNavigationRuntime()
2388
+ .then((runtime) =>
2389
+ typeof runtime?.__mreactNavigate === "function"
2390
+ ? runtime.__mreactNavigate(nextUrl.href, {
2391
+ scroll: __mreactDeferredAnchorScrollMode(anchor),
2392
+ transition: __mreactDeferredAnchorTransitionMode(anchor),
2393
+ })
2394
+ : false,
2395
+ )
2396
+ .then((navigated) => {
2397
+ if (!navigated) {
2398
+ location.href = nextUrl.href;
2399
+ }
2400
+ })
2401
+ .catch(() => {
2402
+ location.href = nextUrl.href;
2403
+ });
2404
+ }
2405
+
2406
+ function __mreactInstallNavigation() {
2407
+ if (typeof document === "undefined") {
2408
+ return;
2409
+ }
2410
+
2411
+ const load = () => {
2412
+ void __mreactLoadNavigationRuntime();
2413
+ };
2414
+ const loadFromAnchorEvent = (event) => {
2415
+ const target = event.target;
2416
+ const anchor = target instanceof Element ? target.closest("a[href]") : null;
2417
+
2418
+ if (anchor instanceof HTMLAnchorElement && anchor.origin === location.origin) {
2419
+ load();
2420
+ }
2421
+ };
2422
+
2423
+ if (typeof requestIdleCallback === "function") {
2424
+ requestIdleCallback(load);
2425
+ } else {
2426
+ setTimeout(load, 0);
2427
+ }
2428
+
2429
+ addEventListener("popstate", load);
2430
+ document.addEventListener("pointerover", loadFromAnchorEvent, true);
2431
+ document.addEventListener("pointerdown", loadFromAnchorEvent, true);
2432
+ document.addEventListener("click", __mreactDeferredHandleClick, true);
2433
+ document.addEventListener("focusin", loadFromAnchorEvent);
2434
+ }
2435
+
2436
+ export async function __mreactNavigate(url, options = {}) {
2437
+ const runtime = await __mreactLoadNavigationRuntime();
2438
+ return typeof runtime?.__mreactNavigate === "function"
2439
+ ? runtime.__mreactNavigate(url, options)
2440
+ : false;
2441
+ }
2442
+
2443
+ export async function __mreactPrefetch(url) {
2444
+ const runtime = await __mreactLoadNavigationRuntime();
2445
+ return typeof runtime?.__mreactPrefetch === "function"
2446
+ ? runtime.__mreactPrefetch(url)
2447
+ : false;
2448
+ }
2449
+
2450
+ export async function __mreactInvalidateNavigationCache(path) {
2451
+ const runtime = await __mreactLoadNavigationRuntime();
2452
+ runtime?.__mreactInvalidateNavigationCache?.(path);
2453
+ }
2454
+
2455
+ export async function __mreactRestoreHistoryState(state) {
2456
+ const runtime = await __mreactLoadNavigationRuntime();
2457
+ return typeof runtime?.__mreactRestoreHistoryState === "function"
2458
+ ? runtime.__mreactRestoreHistoryState(state)
2459
+ : false;
2460
+ }
2461
+
2462
+ export async function __mreactGetNavigationState() {
2463
+ const runtime = await __mreactLoadNavigationRuntime();
2464
+ return typeof runtime?.__mreactGetNavigationState === "function"
2465
+ ? runtime.__mreactGetNavigationState()
2466
+ : { from: null, pending: false, to: null, type: null };
2467
+ }
2468
+ `
2469
+ : "";
2283
2470
  const routeCellStateDeclaration = routeUsesCells
2284
2471
  ? `const __mreactRouteStates = __mreactGlobal.__mreactRouteStates ??= new Map();
2285
2472
  let __mreactActiveCellRecords = undefined;
@@ -2451,6 +2638,7 @@ const __mreactRouteId = ${JSON.stringify(routeId)};
2451
2638
  const __mreactPropsScriptPrefix = ${JSON.stringify(routeHydrationContract.propsScriptPrefix)};
2452
2639
  const __mreactClientReferencesScriptPrefix = ${JSON.stringify(routeHydrationContract.clientReferencesScriptPrefix)};
2453
2640
  const __mreactGlobal = globalThis;
2641
+ __mreactGlobal.__mreactHydrateRoute;
2454
2642
  ${navigationStateDeclaration}
2455
2643
  ${routeCellStateDeclaration}
2456
2644
  ${routeCleanupStateDeclaration}
@@ -2548,11 +2736,12 @@ function __mreactRunRouteHydration(factory) {
2548
2736
  }
2549
2737
  }
2550
2738
 
2739
+ ${deferredNavigationRuntime}
2551
2740
  __mreactRunRouteHydration(() => __mreactHydrateRoute());
2552
2741
  ${clientNavigation ? "__mreactInstallNavigation();" : ""}
2553
2742
 
2554
2743
  ${
2555
- clientNavigation
2744
+ inlineClientNavigation
2556
2745
  ? `export function __mreactNavigateToHtml(html, url, options = {}) {
2557
2746
  __mreactSaveCurrentHistoryState();
2558
2747
  const applied = __mreactApplyNavigationHtml(html, url);
@@ -4492,6 +4681,10 @@ function workspaceRuntimePlugin(options: { routeFiles: readonly string[] }) {
4492
4681
  "@reckona/mreact-compat/scheduler",
4493
4682
  packageFile("react-compat", "@reckona/mreact-compat", "scheduler"),
4494
4683
  ],
4684
+ [
4685
+ "@reckona/mreact-compat/server",
4686
+ packageFile("react-compat", "@reckona/mreact-compat", "server"),
4687
+ ],
4495
4688
  ["@reckona/mreact-reactive-dom", reactiveDomPath],
4496
4689
  ]);
4497
4690
 
package/src/config.ts CHANGED
@@ -26,6 +26,7 @@ export interface AppRouterProductionOptions {
26
26
 
27
27
  export interface AppRouterProjectOptions {
28
28
  assetBaseUrl?: string | undefined;
29
+ buildConcurrency?: number | undefined;
29
30
  buildTargets?: readonly AppRouterBuildTarget[] | undefined;
30
31
  clientSourceMaps?: AppRouterClientSourceMapOption | undefined;
31
32
  /**
@@ -48,6 +49,7 @@ export interface AppRouterProjectOptions {
48
49
  export interface ResolvedAppRouterProject {
49
50
  allowedSourceDirs: readonly string[];
50
51
  assetBaseUrl?: string | undefined;
52
+ buildConcurrency?: number | undefined;
51
53
  buildTargets: readonly AppRouterBuildTarget[];
52
54
  clientSourceMaps: AppRouterClientSourceMapMode;
53
55
  clientConsolePureFunctions?: readonly string[] | undefined;
@@ -76,6 +78,9 @@ export function resolveAppRouterProjectOptions(
76
78
  resolveProjectPath(appDir, directory, "allowedSourceDirs"),
77
79
  ),
78
80
  ...(options.assetBaseUrl === undefined ? {} : { assetBaseUrl: options.assetBaseUrl }),
81
+ ...(options.buildConcurrency === undefined
82
+ ? {}
83
+ : { buildConcurrency: options.buildConcurrency }),
79
84
  buildTargets: resolveBuildTargets(options.buildTargets),
80
85
  clientSourceMaps: resolveClientSourceMapMode(options.clientSourceMaps),
81
86
  ...(clientConsolePureFunctions === undefined ? {} : { clientConsolePureFunctions }),
@@ -98,6 +103,9 @@ export function resolveAppRouterProjectOptions(
98
103
  resolveProjectPath(projectRoot, directory, "allowedSourceDirs"),
99
104
  ),
100
105
  ...(options.assetBaseUrl === undefined ? {} : { assetBaseUrl: options.assetBaseUrl }),
106
+ ...(options.buildConcurrency === undefined
107
+ ? {}
108
+ : { buildConcurrency: options.buildConcurrency }),
101
109
  buildTargets: resolveBuildTargets(options.buildTargets),
102
110
  clientSourceMaps: resolveClientSourceMapMode(options.clientSourceMaps),
103
111
  ...(clientConsolePureFunctions === undefined ? {} : { clientConsolePureFunctions }),
@@ -296,6 +296,7 @@ const compatVendorSpecifierEntries = new Map<string, string>([
296
296
  ["@reckona/mreact-compat/jsx-dev-runtime", "jsx-dev-runtime"],
297
297
  ["@reckona/mreact-compat/jsx-runtime", "jsx-runtime"],
298
298
  ["@reckona/mreact-compat/scheduler", "scheduler"],
299
+ ["@reckona/mreact-compat/server", "server"],
299
300
  ]);
300
301
 
301
302
  const compatVendorEntrySpecifiers = new Map<string, string>([
@@ -307,6 +308,7 @@ const compatVendorEntrySpecifiers = new Map<string, string>([
307
308
  ["event-priority", "@reckona/mreact-compat/event-priority"],
308
309
  ["flight", "@reckona/mreact-compat/flight"],
309
310
  ["internal", "@reckona/mreact-compat/internal"],
311
+ ["server", "@reckona/mreact-compat/server"],
310
312
  ]);
311
313
 
312
314
  export function compatVendorEntryNames(): readonly string[] {
@@ -580,7 +582,185 @@ function withFileImportMetaUrl(source: string, filename: string): string {
580
582
  return source;
581
583
  }
582
584
 
583
- return source.replaceAll("import.meta.url", JSON.stringify(pathToFileURL(filename).href));
585
+ return replaceImportMetaUrlExpressions(source, JSON.stringify(pathToFileURL(filename).href));
586
+ }
587
+
588
+ function replaceImportMetaUrlExpressions(source: string, replacement: string): string {
589
+ return replaceImportMetaUrlInCode(source, 0, source.length, replacement, false).text;
590
+ }
591
+
592
+ function replaceImportMetaUrlInCode(
593
+ source: string,
594
+ start: number,
595
+ end: number,
596
+ replacement: string,
597
+ stopAtTemplateExpressionEnd: boolean,
598
+ ): { next: number; text: string } {
599
+ let output = "";
600
+ let copyStart = start;
601
+ let index = start;
602
+ let braceDepth = 0;
603
+
604
+ while (index < end) {
605
+ const char = source[index];
606
+
607
+ if (stopAtTemplateExpressionEnd && char === "}") {
608
+ if (braceDepth === 0) {
609
+ break;
610
+ }
611
+ braceDepth -= 1;
612
+ index += 1;
613
+ continue;
614
+ }
615
+
616
+ if (isImportMetaUrlToken(source, index)) {
617
+ output += source.slice(copyStart, index);
618
+ output += replacement;
619
+ index += "import.meta.url".length;
620
+ copyStart = index;
621
+ continue;
622
+ }
623
+
624
+ if (char === '"' || char === "'") {
625
+ index = skipQuotedString(source, index, char);
626
+ continue;
627
+ }
628
+
629
+ if (char === "`") {
630
+ const template = replaceImportMetaUrlInTemplate(source, index, replacement);
631
+ output += source.slice(copyStart, index);
632
+ output += template.text;
633
+ index = template.next;
634
+ copyStart = index;
635
+ continue;
636
+ }
637
+
638
+ if (char === "/" && source[index + 1] === "/") {
639
+ index = skipLineComment(source, index + 2);
640
+ continue;
641
+ }
642
+
643
+ if (char === "/" && source[index + 1] === "*") {
644
+ index = skipBlockComment(source, index + 2);
645
+ continue;
646
+ }
647
+
648
+ if (stopAtTemplateExpressionEnd && char === "{") {
649
+ braceDepth += 1;
650
+ }
651
+
652
+ index += 1;
653
+ }
654
+
655
+ return {
656
+ next: index,
657
+ text: output + source.slice(copyStart, index),
658
+ };
659
+ }
660
+
661
+ function replaceImportMetaUrlInTemplate(
662
+ source: string,
663
+ start: number,
664
+ replacement: string,
665
+ ): { next: number; text: string } {
666
+ let output = "`";
667
+ let copyStart = start + 1;
668
+ let index = start + 1;
669
+
670
+ while (index < source.length) {
671
+ const char = source[index];
672
+
673
+ if (char === "\\") {
674
+ index += 2;
675
+ continue;
676
+ }
677
+
678
+ if (char === "`") {
679
+ return {
680
+ next: index + 1,
681
+ text: output + source.slice(copyStart, index + 1),
682
+ };
683
+ }
684
+
685
+ if (char === "$" && source[index + 1] === "{") {
686
+ output += source.slice(copyStart, index + 2);
687
+ const expression = replaceImportMetaUrlInCode(
688
+ source,
689
+ index + 2,
690
+ source.length,
691
+ replacement,
692
+ true,
693
+ );
694
+ output += expression.text;
695
+ index = expression.next;
696
+
697
+ if (source[index] === "}") {
698
+ output += "}";
699
+ index += 1;
700
+ }
701
+
702
+ copyStart = index;
703
+ continue;
704
+ }
705
+
706
+ index += 1;
707
+ }
708
+
709
+ return {
710
+ next: index,
711
+ text: output + source.slice(copyStart, index),
712
+ };
713
+ }
714
+
715
+ function isImportMetaUrlToken(source: string, index: number): boolean {
716
+ return (
717
+ source.startsWith("import.meta.url", index) &&
718
+ !isIdentifierPartCode(source.charCodeAt(index - 1)) &&
719
+ !isIdentifierPartCode(source.charCodeAt(index + "import.meta.url".length))
720
+ );
721
+ }
722
+
723
+ function isIdentifierPartCode(code: number): boolean {
724
+ return (
725
+ (code >= 48 && code <= 57) ||
726
+ (code >= 65 && code <= 90) ||
727
+ (code >= 97 && code <= 122) ||
728
+ code === 36 ||
729
+ code === 95
730
+ );
731
+ }
732
+
733
+ function skipQuotedString(source: string, start: number, quote: string): number {
734
+ let index = start + 1;
735
+
736
+ while (index < source.length) {
737
+ const char = source[index];
738
+
739
+ if (char === "\\") {
740
+ index += 2;
741
+ continue;
742
+ }
743
+
744
+ if (char === quote) {
745
+ return index + 1;
746
+ }
747
+
748
+ index += 1;
749
+ }
750
+
751
+ return index;
752
+ }
753
+
754
+ function skipLineComment(source: string, start: number): number {
755
+ const end = source.indexOf("\n", start);
756
+
757
+ return end === -1 ? source.length : end + 1;
758
+ }
759
+
760
+ function skipBlockComment(source: string, start: number): number {
761
+ const end = source.indexOf("*/", start);
762
+
763
+ return end === -1 ? source.length : end + 2;
584
764
  }
585
765
 
586
766
  function withNodeRequireShimForEsmBundle(options: {
@@ -1181,6 +1361,10 @@ function workspacePackageResolutionPlugin() {
1181
1361
  "@reckona/mreact-compat/scheduler",
1182
1362
  { entry: "scheduler", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
1183
1363
  ],
1364
+ [
1365
+ "@reckona/mreact-compat/server",
1366
+ { entry: "server", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
1367
+ ],
1184
1368
  [
1185
1369
  "@reckona/mreact-reactive-core",
1186
1370
  {