@moku-labs/web 1.13.0 → 1.13.2
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/dist/browser.mjs +66 -4
- package/dist/index.cjs +108 -27
- package/dist/index.mjs +108 -27
- package/package.json +1 -1
package/dist/browser.mjs
CHANGED
|
@@ -471,6 +471,38 @@ function dynamicSegmentCount(pattern) {
|
|
|
471
471
|
return count;
|
|
472
472
|
}
|
|
473
473
|
/**
|
|
474
|
+
* Whether a route is rendered ENTIRELY on the client in `spa` mode: a dynamic route
|
|
475
|
+
* (≥1 non-lang param) that declares no build-time `.generate()` enumerator, so its
|
|
476
|
+
* concrete param paths are unknown until runtime.
|
|
477
|
+
*
|
|
478
|
+
* The build SKIPS such a route — emitting a static page for it would write one
|
|
479
|
+
* param-less shell whose path (`/b/{id}`) matches no file (a 404) and carries no
|
|
480
|
+
* param for the islands to read. Instead the SPA client-renders it from the URL on
|
|
481
|
+
* boot and on navigation. Build and client share this ONE predicate (the same way
|
|
482
|
+
* `dynamicSegmentCount`/`bySpecificity` are shared) so the two sides can never
|
|
483
|
+
* disagree about which routes are pre-rendered vs. client-only.
|
|
484
|
+
*
|
|
485
|
+
* Static routes (`/`) and dynamic routes WITH `.generate()` are pre-rendered as
|
|
486
|
+
* usual and so are NOT client-only. In `ssg`/`hybrid` mode nothing is client-only
|
|
487
|
+
* (the build pre-renders every route), so this is always `false` outside `spa`.
|
|
488
|
+
*
|
|
489
|
+
* @param mode - The global render mode (`router.mode()`).
|
|
490
|
+
* @param route - The route to test (only its `pattern` + `.generate()` presence are read).
|
|
491
|
+
* @param route.pattern - The route's URL pattern string.
|
|
492
|
+
* @param route._handlers - The route's handler bag.
|
|
493
|
+
* @param route._handlers.generate - The build-only static-paths enumerator, if any (presence only).
|
|
494
|
+
* @returns `true` when the route is client-only (spa mode, dynamic, no `.generate()`).
|
|
495
|
+
* @example
|
|
496
|
+
* ```ts
|
|
497
|
+
* isClientOnlyRoute("spa", { pattern: "/b/{id}", _handlers: {} }); // true
|
|
498
|
+
* isClientOnlyRoute("spa", { pattern: "/", _handlers: {} }); // false (static)
|
|
499
|
+
* isClientOnlyRoute("hybrid", { pattern: "/b/{id}", _handlers: {} }); // false (not spa)
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
function isClientOnlyRoute(mode, route) {
|
|
503
|
+
return mode === "spa" && route._handlers.generate === void 0 && dynamicSegmentCount(route.pattern) > 0;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
474
506
|
* Comparator that orders two routes most-specific-first (fewest dynamic segments
|
|
475
507
|
* first). Equal specificity yields `0` so a stable sort preserves declaration
|
|
476
508
|
* order — the exact ordering the compiled matcher table uses, guaranteeing
|
|
@@ -3374,12 +3406,16 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
3374
3406
|
* const resolved = await resolveDataRender("/en/world/");
|
|
3375
3407
|
*/
|
|
3376
3408
|
const resolveDataRender = async (pathname) => {
|
|
3377
|
-
if (!deps.dataAt) return false;
|
|
3378
3409
|
const matchPath = pathname.split("?")[0] ?? pathname;
|
|
3379
3410
|
const hit = deps.router.match(matchPath);
|
|
3380
3411
|
if (!hit?.route._handlers.render) return false;
|
|
3381
|
-
|
|
3382
|
-
if (
|
|
3412
|
+
let data = {};
|
|
3413
|
+
if (!isClientOnlyRoute(deps.router.mode(), hit.route)) {
|
|
3414
|
+
if (!deps.dataAt) return false;
|
|
3415
|
+
const persisted = await deps.dataAt(pathname);
|
|
3416
|
+
if (persisted === null) return false;
|
|
3417
|
+
data = persisted;
|
|
3418
|
+
}
|
|
3383
3419
|
const locale = hit.params.lang ?? document.documentElement.lang ?? "";
|
|
3384
3420
|
const routeContext = {
|
|
3385
3421
|
params: hit.params,
|
|
@@ -3461,6 +3497,29 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
3461
3497
|
}
|
|
3462
3498
|
};
|
|
3463
3499
|
/**
|
|
3500
|
+
* Initial-load render for a spa client-only route (dynamic, no `.generate()`): the build emitted
|
|
3501
|
+
* no static HTML for it, so the host served a fallback shell. Client-render the matched route into
|
|
3502
|
+
* the swap region from the URL, then mount its islands — the deep-link / refresh paint. Unlike a
|
|
3503
|
+
* navigation there is nothing to unmount and no `spa:navigated` to emit. If the route cannot be
|
|
3504
|
+
* resolved (defensive — a matched client-only route always resolves), fall back to mounting the
|
|
3505
|
+
* served body so boot still wires up whatever islands the shell does carry.
|
|
3506
|
+
*
|
|
3507
|
+
* @param pathname - The current document path (pathname + search).
|
|
3508
|
+
* @example
|
|
3509
|
+
* await bootRender("/b/abc123");
|
|
3510
|
+
*/
|
|
3511
|
+
const bootRender = async (pathname) => {
|
|
3512
|
+
const resolvedRender = await resolveDataRender(pathname);
|
|
3513
|
+
if (resolvedRender === false) {
|
|
3514
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
const { vnode, region } = resolvedRender;
|
|
3518
|
+
const { renderVNode } = await import("./render-BNe0s7fr.mjs");
|
|
3519
|
+
renderVNode(vnode, region);
|
|
3520
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
3521
|
+
};
|
|
3522
|
+
/**
|
|
3464
3523
|
* Unified navigation: try the client DATA path first (only when the `data`
|
|
3465
3524
|
* plugin is composed), then fall back to HTML-over-fetch (which itself falls
|
|
3466
3525
|
* back to a full `location.href` reload). Injected into the router so every
|
|
@@ -3505,7 +3564,10 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
3505
3564
|
progress = createProgressBar(resolved.progressBar);
|
|
3506
3565
|
state.currentUrl = currentLocationUrl();
|
|
3507
3566
|
state.destroyRouter = attachRouter(handlers, navigate);
|
|
3508
|
-
|
|
3567
|
+
const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
|
|
3568
|
+
const hit = deps.router.match(matchPath);
|
|
3569
|
+
if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
|
|
3570
|
+
else scanAndMount(state, emit, resolved.swapSelector);
|
|
3509
3571
|
state.started = true;
|
|
3510
3572
|
},
|
|
3511
3573
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -1084,6 +1084,38 @@ function dynamicSegmentCount(pattern) {
|
|
|
1084
1084
|
return count;
|
|
1085
1085
|
}
|
|
1086
1086
|
/**
|
|
1087
|
+
* Whether a route is rendered ENTIRELY on the client in `spa` mode: a dynamic route
|
|
1088
|
+
* (≥1 non-lang param) that declares no build-time `.generate()` enumerator, so its
|
|
1089
|
+
* concrete param paths are unknown until runtime.
|
|
1090
|
+
*
|
|
1091
|
+
* The build SKIPS such a route — emitting a static page for it would write one
|
|
1092
|
+
* param-less shell whose path (`/b/{id}`) matches no file (a 404) and carries no
|
|
1093
|
+
* param for the islands to read. Instead the SPA client-renders it from the URL on
|
|
1094
|
+
* boot and on navigation. Build and client share this ONE predicate (the same way
|
|
1095
|
+
* `dynamicSegmentCount`/`bySpecificity` are shared) so the two sides can never
|
|
1096
|
+
* disagree about which routes are pre-rendered vs. client-only.
|
|
1097
|
+
*
|
|
1098
|
+
* Static routes (`/`) and dynamic routes WITH `.generate()` are pre-rendered as
|
|
1099
|
+
* usual and so are NOT client-only. In `ssg`/`hybrid` mode nothing is client-only
|
|
1100
|
+
* (the build pre-renders every route), so this is always `false` outside `spa`.
|
|
1101
|
+
*
|
|
1102
|
+
* @param mode - The global render mode (`router.mode()`).
|
|
1103
|
+
* @param route - The route to test (only its `pattern` + `.generate()` presence are read).
|
|
1104
|
+
* @param route.pattern - The route's URL pattern string.
|
|
1105
|
+
* @param route._handlers - The route's handler bag.
|
|
1106
|
+
* @param route._handlers.generate - The build-only static-paths enumerator, if any (presence only).
|
|
1107
|
+
* @returns `true` when the route is client-only (spa mode, dynamic, no `.generate()`).
|
|
1108
|
+
* @example
|
|
1109
|
+
* ```ts
|
|
1110
|
+
* isClientOnlyRoute("spa", { pattern: "/b/{id}", _handlers: {} }); // true
|
|
1111
|
+
* isClientOnlyRoute("spa", { pattern: "/", _handlers: {} }); // false (static)
|
|
1112
|
+
* isClientOnlyRoute("hybrid", { pattern: "/b/{id}", _handlers: {} }); // false (not spa)
|
|
1113
|
+
* ```
|
|
1114
|
+
*/
|
|
1115
|
+
function isClientOnlyRoute(mode, route) {
|
|
1116
|
+
return mode === "spa" && route._handlers.generate === void 0 && dynamicSegmentCount(route.pattern) > 0;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1087
1119
|
* Comparator that orders two routes most-specific-first (fewest dynamic segments
|
|
1088
1120
|
* first). Equal specificity yields `0` so a stable sort preserves declaration
|
|
1089
1121
|
* order — the exact ordering the compiled matcher table uses, guaranteeing
|
|
@@ -4528,16 +4560,23 @@ function resolveEntry(byPattern, definition) {
|
|
|
4528
4560
|
* generate context is the spec `{ locale, require, has }`, so a `.generate()` handler
|
|
4529
4561
|
* pulls sibling APIs the spec way.
|
|
4530
4562
|
*
|
|
4563
|
+
* In `spa` mode a client-only route (dynamic, no `.generate()`) is SKIPPED entirely
|
|
4564
|
+
* (`[]`) — it is rendered on the client from the URL, so emitting a static param-less
|
|
4565
|
+
* shell here would only write a file at the wrong path (a 404 for any real param path)
|
|
4566
|
+
* carrying no param. See {@link isClientOnlyRoute}.
|
|
4567
|
+
*
|
|
4531
4568
|
* @param definition - The route definition from the manifest.
|
|
4532
4569
|
* @param locale - The active locale to generate param sets for.
|
|
4570
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4533
4571
|
* @param ctx - Plugin context (provides `require`/`has` for the generate context).
|
|
4534
|
-
* @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`).
|
|
4572
|
+
* @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`; `[]` when client-only).
|
|
4535
4573
|
* @example
|
|
4536
4574
|
* ```ts
|
|
4537
|
-
* const paramSets = await generateParamSets(def, "en", ctx);
|
|
4575
|
+
* const paramSets = await generateParamSets(def, "en", "hybrid", ctx);
|
|
4538
4576
|
* ```
|
|
4539
4577
|
*/
|
|
4540
|
-
async function generateParameterSets(definition, locale, ctx) {
|
|
4578
|
+
async function generateParameterSets(definition, locale, mode, ctx) {
|
|
4579
|
+
if (isClientOnlyRoute(mode, definition)) return [];
|
|
4541
4580
|
const generateContext = {
|
|
4542
4581
|
locale,
|
|
4543
4582
|
require: ctx.require,
|
|
@@ -4561,21 +4600,22 @@ async function generateParameterSets(definition, locale, ctx) {
|
|
|
4561
4600
|
* @param locales - Active locale codes from i18n.
|
|
4562
4601
|
* @param defaultLocale - The i18n default locale (kept when locales collapse to one file).
|
|
4563
4602
|
* @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
|
|
4603
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4564
4604
|
* @param ctx - Plugin context (provides `require`/`has` for the generate context).
|
|
4565
4605
|
* @returns The flattened, file-deduplicated list of page instances for this route.
|
|
4566
4606
|
* @example
|
|
4567
4607
|
* ```ts
|
|
4568
|
-
* await expandRoute(def, ["en"], "en", byPattern, ctx);
|
|
4608
|
+
* await expandRoute(def, ["en"], "en", byPattern, "hybrid", ctx);
|
|
4569
4609
|
* ```
|
|
4570
4610
|
*/
|
|
4571
|
-
async function expandRoute(definition, locales, defaultLocale, byPattern, ctx) {
|
|
4611
|
+
async function expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx) {
|
|
4572
4612
|
const entry = resolveEntry(byPattern, definition);
|
|
4573
4613
|
const { name } = entry;
|
|
4574
4614
|
const orderedLocales = [defaultLocale, ...locales.filter((locale) => locale !== defaultLocale)];
|
|
4575
4615
|
const instances = [];
|
|
4576
4616
|
const claimedFiles = /* @__PURE__ */ new Set();
|
|
4577
4617
|
for (const locale of orderedLocales) {
|
|
4578
|
-
const parameterSets = await generateParameterSets(definition, locale, ctx);
|
|
4618
|
+
const parameterSets = await generateParameterSets(definition, locale, mode, ctx);
|
|
4579
4619
|
for (const raw of parameterSets) {
|
|
4580
4620
|
const params = raw ?? {};
|
|
4581
4621
|
const file = entry.toFile(params);
|
|
@@ -4909,15 +4949,16 @@ async function prepareShell(ctx) {
|
|
|
4909
4949
|
* @param locales - Active locale codes from i18n.
|
|
4910
4950
|
* @param defaultLocale - The i18n default locale (kept when a route's locales collapse).
|
|
4911
4951
|
* @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
|
|
4952
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4912
4953
|
* @param ctx - Plugin context (provides `require`/`has` for generate contexts).
|
|
4913
4954
|
* @returns The flattened list of page instances to render.
|
|
4914
4955
|
* @example
|
|
4915
4956
|
* ```ts
|
|
4916
|
-
* const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, ctx);
|
|
4957
|
+
* const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, "hybrid", ctx);
|
|
4917
4958
|
* ```
|
|
4918
4959
|
*/
|
|
4919
|
-
async function expandAllInstances(manifest, locales, defaultLocale, byPattern, ctx) {
|
|
4920
|
-
return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, ctx)))).flat();
|
|
4960
|
+
async function expandAllInstances(manifest, locales, defaultLocale, byPattern, mode, ctx) {
|
|
4961
|
+
return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx)))).flat();
|
|
4921
4962
|
}
|
|
4922
4963
|
/**
|
|
4923
4964
|
* Persist per-page client-data sidecars when the app opts into client navigation
|
|
@@ -5027,14 +5068,15 @@ async function renderInBatches(items, batchSize, worker) {
|
|
|
5027
5068
|
async function renderPages(ctx, options) {
|
|
5028
5069
|
const reuse = options?.reuse === true;
|
|
5029
5070
|
const router = ctx.require(routerPlugin);
|
|
5071
|
+
const mode = router.mode();
|
|
5030
5072
|
const manifest = router.manifest();
|
|
5031
5073
|
ctx.state.manifest = [...manifest];
|
|
5032
5074
|
const locales = ctx.require(i18nPlugin).locales();
|
|
5033
5075
|
const byPattern = makeEntryMap(router);
|
|
5034
5076
|
if (!reuse) ctx.state.renderCache.clear();
|
|
5035
5077
|
const shell = await prepareShell(ctx);
|
|
5036
|
-
const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
|
|
5037
|
-
await writeDataSidecars(ctx, rendered,
|
|
5078
|
+
const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, mode, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
|
|
5079
|
+
await writeDataSidecars(ctx, rendered, mode);
|
|
5038
5080
|
ctx.log.debug("build:pages", { count: rendered.length });
|
|
5039
5081
|
return {
|
|
5040
5082
|
pageCount: rendered.length,
|
|
@@ -6108,7 +6150,7 @@ async function readWranglerConfig(cwd) {
|
|
|
6108
6150
|
/** Relative path of the generated wrangler config. */
|
|
6109
6151
|
const WRANGLER_PATH = "wrangler.jsonc";
|
|
6110
6152
|
/** Relative path of the generated GitHub Actions workflow. */
|
|
6111
|
-
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6153
|
+
const WORKFLOW_PATH$1 = ".github/workflows/deploy.yml";
|
|
6112
6154
|
/** Wrangler `compatibility_date` used when the deploy config does not pin one. */
|
|
6113
6155
|
const DEFAULT_COMPATIBILITY_DATE = "2024-01-01";
|
|
6114
6156
|
/**
|
|
@@ -6166,12 +6208,12 @@ async function writeScaffolding(input) {
|
|
|
6166
6208
|
result
|
|
6167
6209
|
});
|
|
6168
6210
|
if (ci) await reconcile({
|
|
6169
|
-
relativePath: WORKFLOW_PATH,
|
|
6211
|
+
relativePath: WORKFLOW_PATH$1,
|
|
6170
6212
|
expected: generateGithubWorkflow({
|
|
6171
6213
|
slug,
|
|
6172
6214
|
...options.workflowTrigger ? { trigger: options.workflowTrigger } : {}
|
|
6173
6215
|
}),
|
|
6174
|
-
existing: await readMaybe(cwd, WORKFLOW_PATH),
|
|
6216
|
+
existing: await readMaybe(cwd, WORKFLOW_PATH$1),
|
|
6175
6217
|
cwd,
|
|
6176
6218
|
check,
|
|
6177
6219
|
result
|
|
@@ -6869,30 +6911,39 @@ async function resolveTrigger(ctx, choice) {
|
|
|
6869
6911
|
if (choice === 0) return "auto";
|
|
6870
6912
|
return await ctx.state.select("How should the versioned deploy be triggered?", ["On a version tag push (v*) + the manual Run-workflow button", "Manual Run-workflow button only (workflow_dispatch)"]) === 0 ? "versioned-tag" : "dispatch";
|
|
6871
6913
|
}
|
|
6914
|
+
/** Relative path of the GitHub Actions workflow the deploy plugin scaffolds. */
|
|
6915
|
+
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6872
6916
|
/**
|
|
6873
6917
|
* Offer to scaffold a GitHub Actions deploy workflow, letting the user choose how it is
|
|
6874
|
-
* triggered, then remind them which repo secrets to add.
|
|
6918
|
+
* triggered, then remind them which repo secrets to add. Short-circuits WITHOUT prompting
|
|
6919
|
+
* when {@link WORKFLOW_PATH} already exists — CI is already wired and the scaffold is
|
|
6920
|
+
* idempotent (a second setup would only no-op), so there is nothing to ask; it just
|
|
6921
|
+
* confirms the file and re-shows the secrets reminder. A no-op past a "skip" choice.
|
|
6875
6922
|
*
|
|
6876
6923
|
* @param ctx - The cli plugin context.
|
|
6924
|
+
* @param cwd - The project root (where `.github/workflows/deploy.yml` lives).
|
|
6877
6925
|
* @returns Resolves once any chosen workflow has been scaffolded.
|
|
6878
6926
|
* @example
|
|
6879
|
-
* await offerWorkflowSetup(ctx);
|
|
6927
|
+
* await offerWorkflowSetup(ctx, process.cwd());
|
|
6880
6928
|
*/
|
|
6881
|
-
async function offerWorkflowSetup(ctx) {
|
|
6929
|
+
async function offerWorkflowSetup(ctx, cwd) {
|
|
6882
6930
|
ctx.state.render.heading("Automate future deploys (GitHub Actions)");
|
|
6931
|
+
if ((0, node_fs.existsSync)(node_path$1.default.join(cwd, WORKFLOW_PATH))) {
|
|
6932
|
+
ctx.state.render.check(true, `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6933
|
+
ctx.state.render.info(SECRETS_HELP);
|
|
6934
|
+
return;
|
|
6935
|
+
}
|
|
6883
6936
|
const trigger = await resolveTrigger(ctx, await ctx.state.select("Set up a deploy workflow?", [
|
|
6884
6937
|
"Auto-deploy on every push to main",
|
|
6885
6938
|
"Manual / versioned deploy (choose trigger)",
|
|
6886
6939
|
"Skip for now"
|
|
6887
6940
|
]));
|
|
6888
6941
|
if (trigger === null) return;
|
|
6889
|
-
const
|
|
6942
|
+
const wrote = (await ctx.require(deployPlugin).init({
|
|
6890
6943
|
ci: true,
|
|
6891
6944
|
workflowTrigger: trigger
|
|
6892
|
-
});
|
|
6893
|
-
|
|
6894
|
-
const wrote = result.written.includes(workflowPath);
|
|
6895
|
-
ctx.state.render.check(true, wrote ? `wrote ${workflowPath}` : `${workflowPath} already exists (left unchanged)`);
|
|
6945
|
+
})).written.includes(WORKFLOW_PATH);
|
|
6946
|
+
ctx.state.render.check(true, wrote ? `wrote ${WORKFLOW_PATH}` : `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6896
6947
|
ctx.state.render.info(SECRETS_HELP);
|
|
6897
6948
|
}
|
|
6898
6949
|
/**
|
|
@@ -7088,7 +7139,7 @@ async function runDeployWizard(ctx, options) {
|
|
|
7088
7139
|
ctx.state.render.check(notFoundOk, `${ctx.config.notFoundFile} present`, notFoundOk ? void 0 : "Set build.notFound so the SSG emits it (CF Pages else flips to SPA mode).");
|
|
7089
7140
|
ctx.state.render.info("Tip: run `bun run preview` to eyeball the built site before deploying.");
|
|
7090
7141
|
const outcome = await runDeployStep(ctx, options);
|
|
7091
|
-
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx);
|
|
7142
|
+
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx, cwd);
|
|
7092
7143
|
return outcome;
|
|
7093
7144
|
}
|
|
7094
7145
|
//#endregion
|
|
@@ -9980,12 +10031,16 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
9980
10031
|
* const resolved = await resolveDataRender("/en/world/");
|
|
9981
10032
|
*/
|
|
9982
10033
|
const resolveDataRender = async (pathname) => {
|
|
9983
|
-
if (!deps.dataAt) return false;
|
|
9984
10034
|
const matchPath = pathname.split("?")[0] ?? pathname;
|
|
9985
10035
|
const hit = deps.router.match(matchPath);
|
|
9986
10036
|
if (!hit?.route._handlers.render) return false;
|
|
9987
|
-
|
|
9988
|
-
if (
|
|
10037
|
+
let data = {};
|
|
10038
|
+
if (!isClientOnlyRoute(deps.router.mode(), hit.route)) {
|
|
10039
|
+
if (!deps.dataAt) return false;
|
|
10040
|
+
const persisted = await deps.dataAt(pathname);
|
|
10041
|
+
if (persisted === null) return false;
|
|
10042
|
+
data = persisted;
|
|
10043
|
+
}
|
|
9989
10044
|
const locale = hit.params.lang ?? document.documentElement.lang ?? "";
|
|
9990
10045
|
const routeContext = {
|
|
9991
10046
|
params: hit.params,
|
|
@@ -10067,6 +10122,29 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
10067
10122
|
}
|
|
10068
10123
|
};
|
|
10069
10124
|
/**
|
|
10125
|
+
* Initial-load render for a spa client-only route (dynamic, no `.generate()`): the build emitted
|
|
10126
|
+
* no static HTML for it, so the host served a fallback shell. Client-render the matched route into
|
|
10127
|
+
* the swap region from the URL, then mount its islands — the deep-link / refresh paint. Unlike a
|
|
10128
|
+
* navigation there is nothing to unmount and no `spa:navigated` to emit. If the route cannot be
|
|
10129
|
+
* resolved (defensive — a matched client-only route always resolves), fall back to mounting the
|
|
10130
|
+
* served body so boot still wires up whatever islands the shell does carry.
|
|
10131
|
+
*
|
|
10132
|
+
* @param pathname - The current document path (pathname + search).
|
|
10133
|
+
* @example
|
|
10134
|
+
* await bootRender("/b/abc123");
|
|
10135
|
+
*/
|
|
10136
|
+
const bootRender = async (pathname) => {
|
|
10137
|
+
const resolvedRender = await resolveDataRender(pathname);
|
|
10138
|
+
if (resolvedRender === false) {
|
|
10139
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
const { vnode, region } = resolvedRender;
|
|
10143
|
+
const { renderVNode } = await Promise.resolve().then(() => require("./render-DLZEOe4M.cjs"));
|
|
10144
|
+
renderVNode(vnode, region);
|
|
10145
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
10146
|
+
};
|
|
10147
|
+
/**
|
|
10070
10148
|
* Unified navigation: try the client DATA path first (only when the `data`
|
|
10071
10149
|
* plugin is composed), then fall back to HTML-over-fetch (which itself falls
|
|
10072
10150
|
* back to a full `location.href` reload). Injected into the router so every
|
|
@@ -10111,7 +10189,10 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
10111
10189
|
progress = createProgressBar(resolved.progressBar);
|
|
10112
10190
|
state.currentUrl = currentLocationUrl();
|
|
10113
10191
|
state.destroyRouter = attachRouter(handlers, navigate);
|
|
10114
|
-
|
|
10192
|
+
const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
|
|
10193
|
+
const hit = deps.router.match(matchPath);
|
|
10194
|
+
if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
|
|
10195
|
+
else scanAndMount(state, emit, resolved.swapSelector);
|
|
10115
10196
|
state.started = true;
|
|
10116
10197
|
},
|
|
10117
10198
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1071,6 +1071,38 @@ function dynamicSegmentCount(pattern) {
|
|
|
1071
1071
|
return count;
|
|
1072
1072
|
}
|
|
1073
1073
|
/**
|
|
1074
|
+
* Whether a route is rendered ENTIRELY on the client in `spa` mode: a dynamic route
|
|
1075
|
+
* (≥1 non-lang param) that declares no build-time `.generate()` enumerator, so its
|
|
1076
|
+
* concrete param paths are unknown until runtime.
|
|
1077
|
+
*
|
|
1078
|
+
* The build SKIPS such a route — emitting a static page for it would write one
|
|
1079
|
+
* param-less shell whose path (`/b/{id}`) matches no file (a 404) and carries no
|
|
1080
|
+
* param for the islands to read. Instead the SPA client-renders it from the URL on
|
|
1081
|
+
* boot and on navigation. Build and client share this ONE predicate (the same way
|
|
1082
|
+
* `dynamicSegmentCount`/`bySpecificity` are shared) so the two sides can never
|
|
1083
|
+
* disagree about which routes are pre-rendered vs. client-only.
|
|
1084
|
+
*
|
|
1085
|
+
* Static routes (`/`) and dynamic routes WITH `.generate()` are pre-rendered as
|
|
1086
|
+
* usual and so are NOT client-only. In `ssg`/`hybrid` mode nothing is client-only
|
|
1087
|
+
* (the build pre-renders every route), so this is always `false` outside `spa`.
|
|
1088
|
+
*
|
|
1089
|
+
* @param mode - The global render mode (`router.mode()`).
|
|
1090
|
+
* @param route - The route to test (only its `pattern` + `.generate()` presence are read).
|
|
1091
|
+
* @param route.pattern - The route's URL pattern string.
|
|
1092
|
+
* @param route._handlers - The route's handler bag.
|
|
1093
|
+
* @param route._handlers.generate - The build-only static-paths enumerator, if any (presence only).
|
|
1094
|
+
* @returns `true` when the route is client-only (spa mode, dynamic, no `.generate()`).
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```ts
|
|
1097
|
+
* isClientOnlyRoute("spa", { pattern: "/b/{id}", _handlers: {} }); // true
|
|
1098
|
+
* isClientOnlyRoute("spa", { pattern: "/", _handlers: {} }); // false (static)
|
|
1099
|
+
* isClientOnlyRoute("hybrid", { pattern: "/b/{id}", _handlers: {} }); // false (not spa)
|
|
1100
|
+
* ```
|
|
1101
|
+
*/
|
|
1102
|
+
function isClientOnlyRoute(mode, route) {
|
|
1103
|
+
return mode === "spa" && route._handlers.generate === void 0 && dynamicSegmentCount(route.pattern) > 0;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1074
1106
|
* Comparator that orders two routes most-specific-first (fewest dynamic segments
|
|
1075
1107
|
* first). Equal specificity yields `0` so a stable sort preserves declaration
|
|
1076
1108
|
* order — the exact ordering the compiled matcher table uses, guaranteeing
|
|
@@ -4515,16 +4547,23 @@ function resolveEntry(byPattern, definition) {
|
|
|
4515
4547
|
* generate context is the spec `{ locale, require, has }`, so a `.generate()` handler
|
|
4516
4548
|
* pulls sibling APIs the spec way.
|
|
4517
4549
|
*
|
|
4550
|
+
* In `spa` mode a client-only route (dynamic, no `.generate()`) is SKIPPED entirely
|
|
4551
|
+
* (`[]`) — it is rendered on the client from the URL, so emitting a static param-less
|
|
4552
|
+
* shell here would only write a file at the wrong path (a 404 for any real param path)
|
|
4553
|
+
* carrying no param. See {@link isClientOnlyRoute}.
|
|
4554
|
+
*
|
|
4518
4555
|
* @param definition - The route definition from the manifest.
|
|
4519
4556
|
* @param locale - The active locale to generate param sets for.
|
|
4557
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4520
4558
|
* @param ctx - Plugin context (provides `require`/`has` for the generate context).
|
|
4521
|
-
* @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`).
|
|
4559
|
+
* @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`; `[]` when client-only).
|
|
4522
4560
|
* @example
|
|
4523
4561
|
* ```ts
|
|
4524
|
-
* const paramSets = await generateParamSets(def, "en", ctx);
|
|
4562
|
+
* const paramSets = await generateParamSets(def, "en", "hybrid", ctx);
|
|
4525
4563
|
* ```
|
|
4526
4564
|
*/
|
|
4527
|
-
async function generateParameterSets(definition, locale, ctx) {
|
|
4565
|
+
async function generateParameterSets(definition, locale, mode, ctx) {
|
|
4566
|
+
if (isClientOnlyRoute(mode, definition)) return [];
|
|
4528
4567
|
const generateContext = {
|
|
4529
4568
|
locale,
|
|
4530
4569
|
require: ctx.require,
|
|
@@ -4548,21 +4587,22 @@ async function generateParameterSets(definition, locale, ctx) {
|
|
|
4548
4587
|
* @param locales - Active locale codes from i18n.
|
|
4549
4588
|
* @param defaultLocale - The i18n default locale (kept when locales collapse to one file).
|
|
4550
4589
|
* @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
|
|
4590
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4551
4591
|
* @param ctx - Plugin context (provides `require`/`has` for the generate context).
|
|
4552
4592
|
* @returns The flattened, file-deduplicated list of page instances for this route.
|
|
4553
4593
|
* @example
|
|
4554
4594
|
* ```ts
|
|
4555
|
-
* await expandRoute(def, ["en"], "en", byPattern, ctx);
|
|
4595
|
+
* await expandRoute(def, ["en"], "en", byPattern, "hybrid", ctx);
|
|
4556
4596
|
* ```
|
|
4557
4597
|
*/
|
|
4558
|
-
async function expandRoute(definition, locales, defaultLocale, byPattern, ctx) {
|
|
4598
|
+
async function expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx) {
|
|
4559
4599
|
const entry = resolveEntry(byPattern, definition);
|
|
4560
4600
|
const { name } = entry;
|
|
4561
4601
|
const orderedLocales = [defaultLocale, ...locales.filter((locale) => locale !== defaultLocale)];
|
|
4562
4602
|
const instances = [];
|
|
4563
4603
|
const claimedFiles = /* @__PURE__ */ new Set();
|
|
4564
4604
|
for (const locale of orderedLocales) {
|
|
4565
|
-
const parameterSets = await generateParameterSets(definition, locale, ctx);
|
|
4605
|
+
const parameterSets = await generateParameterSets(definition, locale, mode, ctx);
|
|
4566
4606
|
for (const raw of parameterSets) {
|
|
4567
4607
|
const params = raw ?? {};
|
|
4568
4608
|
const file = entry.toFile(params);
|
|
@@ -4896,15 +4936,16 @@ async function prepareShell(ctx) {
|
|
|
4896
4936
|
* @param locales - Active locale codes from i18n.
|
|
4897
4937
|
* @param defaultLocale - The i18n default locale (kept when a route's locales collapse).
|
|
4898
4938
|
* @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
|
|
4939
|
+
* @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
|
|
4899
4940
|
* @param ctx - Plugin context (provides `require`/`has` for generate contexts).
|
|
4900
4941
|
* @returns The flattened list of page instances to render.
|
|
4901
4942
|
* @example
|
|
4902
4943
|
* ```ts
|
|
4903
|
-
* const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, ctx);
|
|
4944
|
+
* const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, "hybrid", ctx);
|
|
4904
4945
|
* ```
|
|
4905
4946
|
*/
|
|
4906
|
-
async function expandAllInstances(manifest, locales, defaultLocale, byPattern, ctx) {
|
|
4907
|
-
return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, ctx)))).flat();
|
|
4947
|
+
async function expandAllInstances(manifest, locales, defaultLocale, byPattern, mode, ctx) {
|
|
4948
|
+
return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx)))).flat();
|
|
4908
4949
|
}
|
|
4909
4950
|
/**
|
|
4910
4951
|
* Persist per-page client-data sidecars when the app opts into client navigation
|
|
@@ -5014,14 +5055,15 @@ async function renderInBatches(items, batchSize, worker) {
|
|
|
5014
5055
|
async function renderPages(ctx, options) {
|
|
5015
5056
|
const reuse = options?.reuse === true;
|
|
5016
5057
|
const router = ctx.require(routerPlugin);
|
|
5058
|
+
const mode = router.mode();
|
|
5017
5059
|
const manifest = router.manifest();
|
|
5018
5060
|
ctx.state.manifest = [...manifest];
|
|
5019
5061
|
const locales = ctx.require(i18nPlugin).locales();
|
|
5020
5062
|
const byPattern = makeEntryMap(router);
|
|
5021
5063
|
if (!reuse) ctx.state.renderCache.clear();
|
|
5022
5064
|
const shell = await prepareShell(ctx);
|
|
5023
|
-
const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
|
|
5024
|
-
await writeDataSidecars(ctx, rendered,
|
|
5065
|
+
const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, mode, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
|
|
5066
|
+
await writeDataSidecars(ctx, rendered, mode);
|
|
5025
5067
|
ctx.log.debug("build:pages", { count: rendered.length });
|
|
5026
5068
|
return {
|
|
5027
5069
|
pageCount: rendered.length,
|
|
@@ -6095,7 +6137,7 @@ async function readWranglerConfig(cwd) {
|
|
|
6095
6137
|
/** Relative path of the generated wrangler config. */
|
|
6096
6138
|
const WRANGLER_PATH = "wrangler.jsonc";
|
|
6097
6139
|
/** Relative path of the generated GitHub Actions workflow. */
|
|
6098
|
-
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6140
|
+
const WORKFLOW_PATH$1 = ".github/workflows/deploy.yml";
|
|
6099
6141
|
/** Wrangler `compatibility_date` used when the deploy config does not pin one. */
|
|
6100
6142
|
const DEFAULT_COMPATIBILITY_DATE = "2024-01-01";
|
|
6101
6143
|
/**
|
|
@@ -6153,12 +6195,12 @@ async function writeScaffolding(input) {
|
|
|
6153
6195
|
result
|
|
6154
6196
|
});
|
|
6155
6197
|
if (ci) await reconcile({
|
|
6156
|
-
relativePath: WORKFLOW_PATH,
|
|
6198
|
+
relativePath: WORKFLOW_PATH$1,
|
|
6157
6199
|
expected: generateGithubWorkflow({
|
|
6158
6200
|
slug,
|
|
6159
6201
|
...options.workflowTrigger ? { trigger: options.workflowTrigger } : {}
|
|
6160
6202
|
}),
|
|
6161
|
-
existing: await readMaybe(cwd, WORKFLOW_PATH),
|
|
6203
|
+
existing: await readMaybe(cwd, WORKFLOW_PATH$1),
|
|
6162
6204
|
cwd,
|
|
6163
6205
|
check,
|
|
6164
6206
|
result
|
|
@@ -6856,30 +6898,39 @@ async function resolveTrigger(ctx, choice) {
|
|
|
6856
6898
|
if (choice === 0) return "auto";
|
|
6857
6899
|
return await ctx.state.select("How should the versioned deploy be triggered?", ["On a version tag push (v*) + the manual Run-workflow button", "Manual Run-workflow button only (workflow_dispatch)"]) === 0 ? "versioned-tag" : "dispatch";
|
|
6858
6900
|
}
|
|
6901
|
+
/** Relative path of the GitHub Actions workflow the deploy plugin scaffolds. */
|
|
6902
|
+
const WORKFLOW_PATH = ".github/workflows/deploy.yml";
|
|
6859
6903
|
/**
|
|
6860
6904
|
* Offer to scaffold a GitHub Actions deploy workflow, letting the user choose how it is
|
|
6861
|
-
* triggered, then remind them which repo secrets to add.
|
|
6905
|
+
* triggered, then remind them which repo secrets to add. Short-circuits WITHOUT prompting
|
|
6906
|
+
* when {@link WORKFLOW_PATH} already exists — CI is already wired and the scaffold is
|
|
6907
|
+
* idempotent (a second setup would only no-op), so there is nothing to ask; it just
|
|
6908
|
+
* confirms the file and re-shows the secrets reminder. A no-op past a "skip" choice.
|
|
6862
6909
|
*
|
|
6863
6910
|
* @param ctx - The cli plugin context.
|
|
6911
|
+
* @param cwd - The project root (where `.github/workflows/deploy.yml` lives).
|
|
6864
6912
|
* @returns Resolves once any chosen workflow has been scaffolded.
|
|
6865
6913
|
* @example
|
|
6866
|
-
* await offerWorkflowSetup(ctx);
|
|
6914
|
+
* await offerWorkflowSetup(ctx, process.cwd());
|
|
6867
6915
|
*/
|
|
6868
|
-
async function offerWorkflowSetup(ctx) {
|
|
6916
|
+
async function offerWorkflowSetup(ctx, cwd) {
|
|
6869
6917
|
ctx.state.render.heading("Automate future deploys (GitHub Actions)");
|
|
6918
|
+
if (existsSync(path.join(cwd, WORKFLOW_PATH))) {
|
|
6919
|
+
ctx.state.render.check(true, `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6920
|
+
ctx.state.render.info(SECRETS_HELP);
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6870
6923
|
const trigger = await resolveTrigger(ctx, await ctx.state.select("Set up a deploy workflow?", [
|
|
6871
6924
|
"Auto-deploy on every push to main",
|
|
6872
6925
|
"Manual / versioned deploy (choose trigger)",
|
|
6873
6926
|
"Skip for now"
|
|
6874
6927
|
]));
|
|
6875
6928
|
if (trigger === null) return;
|
|
6876
|
-
const
|
|
6929
|
+
const wrote = (await ctx.require(deployPlugin).init({
|
|
6877
6930
|
ci: true,
|
|
6878
6931
|
workflowTrigger: trigger
|
|
6879
|
-
});
|
|
6880
|
-
|
|
6881
|
-
const wrote = result.written.includes(workflowPath);
|
|
6882
|
-
ctx.state.render.check(true, wrote ? `wrote ${workflowPath}` : `${workflowPath} already exists (left unchanged)`);
|
|
6932
|
+
})).written.includes(WORKFLOW_PATH);
|
|
6933
|
+
ctx.state.render.check(true, wrote ? `wrote ${WORKFLOW_PATH}` : `${WORKFLOW_PATH} already exists (left unchanged)`);
|
|
6883
6934
|
ctx.state.render.info(SECRETS_HELP);
|
|
6884
6935
|
}
|
|
6885
6936
|
/**
|
|
@@ -7075,7 +7126,7 @@ async function runDeployWizard(ctx, options) {
|
|
|
7075
7126
|
ctx.state.render.check(notFoundOk, `${ctx.config.notFoundFile} present`, notFoundOk ? void 0 : "Set build.notFound so the SSG emits it (CF Pages else flips to SPA mode).");
|
|
7076
7127
|
ctx.state.render.info("Tip: run `bun run preview` to eyeball the built site before deploying.");
|
|
7077
7128
|
const outcome = await runDeployStep(ctx, options);
|
|
7078
|
-
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx);
|
|
7129
|
+
if (!(outcome.deployed === false && outcome.reason === "failed")) await offerWorkflowSetup(ctx, cwd);
|
|
7079
7130
|
return outcome;
|
|
7080
7131
|
}
|
|
7081
7132
|
//#endregion
|
|
@@ -9967,12 +10018,16 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
9967
10018
|
* const resolved = await resolveDataRender("/en/world/");
|
|
9968
10019
|
*/
|
|
9969
10020
|
const resolveDataRender = async (pathname) => {
|
|
9970
|
-
if (!deps.dataAt) return false;
|
|
9971
10021
|
const matchPath = pathname.split("?")[0] ?? pathname;
|
|
9972
10022
|
const hit = deps.router.match(matchPath);
|
|
9973
10023
|
if (!hit?.route._handlers.render) return false;
|
|
9974
|
-
|
|
9975
|
-
if (
|
|
10024
|
+
let data = {};
|
|
10025
|
+
if (!isClientOnlyRoute(deps.router.mode(), hit.route)) {
|
|
10026
|
+
if (!deps.dataAt) return false;
|
|
10027
|
+
const persisted = await deps.dataAt(pathname);
|
|
10028
|
+
if (persisted === null) return false;
|
|
10029
|
+
data = persisted;
|
|
10030
|
+
}
|
|
9976
10031
|
const locale = hit.params.lang ?? document.documentElement.lang ?? "";
|
|
9977
10032
|
const routeContext = {
|
|
9978
10033
|
params: hit.params,
|
|
@@ -10054,6 +10109,29 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
10054
10109
|
}
|
|
10055
10110
|
};
|
|
10056
10111
|
/**
|
|
10112
|
+
* Initial-load render for a spa client-only route (dynamic, no `.generate()`): the build emitted
|
|
10113
|
+
* no static HTML for it, so the host served a fallback shell. Client-render the matched route into
|
|
10114
|
+
* the swap region from the URL, then mount its islands — the deep-link / refresh paint. Unlike a
|
|
10115
|
+
* navigation there is nothing to unmount and no `spa:navigated` to emit. If the route cannot be
|
|
10116
|
+
* resolved (defensive — a matched client-only route always resolves), fall back to mounting the
|
|
10117
|
+
* served body so boot still wires up whatever islands the shell does carry.
|
|
10118
|
+
*
|
|
10119
|
+
* @param pathname - The current document path (pathname + search).
|
|
10120
|
+
* @example
|
|
10121
|
+
* await bootRender("/b/abc123");
|
|
10122
|
+
*/
|
|
10123
|
+
const bootRender = async (pathname) => {
|
|
10124
|
+
const resolvedRender = await resolveDataRender(pathname);
|
|
10125
|
+
if (resolvedRender === false) {
|
|
10126
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
10127
|
+
return;
|
|
10128
|
+
}
|
|
10129
|
+
const { vnode, region } = resolvedRender;
|
|
10130
|
+
const { renderVNode } = await import("./render-BNe0s7fr.mjs");
|
|
10131
|
+
renderVNode(vnode, region);
|
|
10132
|
+
scanAndMount(state, emit, resolved.swapSelector);
|
|
10133
|
+
};
|
|
10134
|
+
/**
|
|
10057
10135
|
* Unified navigation: try the client DATA path first (only when the `data`
|
|
10058
10136
|
* plugin is composed), then fall back to HTML-over-fetch (which itself falls
|
|
10059
10137
|
* back to a full `location.href` reload). Injected into the router so every
|
|
@@ -10098,7 +10176,10 @@ function createSpaKernel(state, config, emit, deps) {
|
|
|
10098
10176
|
progress = createProgressBar(resolved.progressBar);
|
|
10099
10177
|
state.currentUrl = currentLocationUrl();
|
|
10100
10178
|
state.destroyRouter = attachRouter(handlers, navigate);
|
|
10101
|
-
|
|
10179
|
+
const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
|
|
10180
|
+
const hit = deps.router.match(matchPath);
|
|
10181
|
+
if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
|
|
10182
|
+
else scanAndMount(state, emit, resolved.swapSelector);
|
|
10102
10183
|
state.started = true;
|
|
10103
10184
|
},
|
|
10104
10185
|
/**
|
package/package.json
CHANGED