@markless/router 0.9.0 → 0.10.0
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/{create-server-entry-CVhSjR45.js → create-server-entry-BnaEfDHl.js} +288 -68
- package/dist/index.js +2 -2
- package/dist/{request-locals-BPfJaoM_.js → navigation-timing-CzH6VaBR.js} +2 -1
- package/dist/vite/runtime/create-server-entry.d.ts +1 -0
- package/dist/vite/runtime/create-server-entry.js +1 -1
- package/dist/vite/runtime/mdx-route.d.ts +188 -6
- package/dist/vite/runtime/mdx-route.js +124 -12
- package/dist/vite.js +384 -62
- package/package.json +5 -5
- package/src/vite/entries/fragment-entry.ts +433 -254
- package/src/vite/entries/prerender-wake-entry.ts +3 -1
- package/src/vite/entries/resume-entry.ts +36 -23
- package/src/vite/entries/resume-modules.ts +7 -0
package/dist/vite.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as LINK_ATTRIBUTE, h as SCROLL_ATTRIBUTE, m as REPLACE_ATTRIBUTE, p as PREFETCH_ATTRIBUTE } from "./navigation-timing-CzH6VaBR.js";
|
|
2
2
|
import { i as matchRouteManifest, l as transformRequestFileSource, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId, r as isPageModuleFile, t as assertNoEndpointConflicts } from "./route-manifest-DmoxXanW.js";
|
|
3
3
|
import { t as __marklessRouteHref } from "./route-href-CGooO_g6.js";
|
|
4
4
|
import { r as protocolIslandSegment } from "./protocol-constants-B4WxrZAS.js";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as FRAGMENT_RESPONSE_HEADER, c as documentHandlersMessage, i as FRAGMENT_PATH_PREFIX, l as listenForLinkIntent, n as routerLinkBridgeModules, o as runFragmentNavigation, r as LINK_BRIDGE_DIR, s as startFragmentNavigation } from "./create-server-entry-BnaEfDHl.js";
|
|
6
6
|
import { dirname, extname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
7
7
|
import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutBase, withoutLeadingSlash } from "ufo";
|
|
8
8
|
import { existsSync, readdirSync } from "node:fs";
|
|
9
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
10
10
|
import { nitro } from "nitro/vite";
|
|
11
|
-
import { minifySync, sortUserPlugins } from "vite";
|
|
11
|
+
import { minifySync, parseAst, sortUserPlugins } from "vite";
|
|
12
12
|
import { isClientPrimarySourceRequest, isExecutionLogChunk, isMarklessNavigationPack, isRenderDataSourceRequest, isResumeSourceRequest, isRouteNavigationSourceRequest, isSymbolOnlySourceRequest, lazyStartupPackName, marklessDeferredChunkFileNames, optimizedDepsToInclude, plannedLandingFiles, resolverVirtualModuleSourceFile, startupPackOfLazy, symbolVirtualModuleSourceFile, warmEvaluatedImports } from "@markless/bundler/preload";
|
|
13
13
|
import { MARKLESS_BUILD_PREFIX, MARKLESS_CHUNK_SPECIFIER_PREFIX, MARKLESS_IMPORT_MAP_ASSET, MARKLESS_PUBLIC_BUILD_METADATA_FILES, importMapScript, marklessBuildManifestDir } from "@markless/bundler/rolldown";
|
|
14
14
|
import { parse } from "@tsrx/yuku";
|
|
@@ -16,6 +16,9 @@ import { toHtml } from "hast-util-to-html";
|
|
|
16
16
|
import { HastReader, createMdxHastHandle, defineHastPlugin, dropHandle, materializeHastTree, resolveHastSubscriptions, serializeHandle, visitHastHandle } from "satteri";
|
|
17
17
|
import "@markless/web/render-to-string";
|
|
18
18
|
import { isEventAttribute } from "@markless/compiler";
|
|
19
|
+
import { availableParallelism } from "node:os";
|
|
20
|
+
import { promisify } from "node:util";
|
|
21
|
+
import { brotliCompress, constants } from "node:zlib";
|
|
19
22
|
//#region src/vite/tsrx-ast.ts
|
|
20
23
|
/** A `.tsrx` module's tree; throws a `SyntaxError` naming the first error, as a module that does not parse has no tree to read. */
|
|
21
24
|
function parseTsrxModule(source) {
|
|
@@ -195,6 +198,7 @@ function isNotFoundError(error) {
|
|
|
195
198
|
//#region src/vite/anchor-transform.ts
|
|
196
199
|
const ROUTE_HREF_HELPER_ID = "virtual:markless-router/route-href";
|
|
197
200
|
const JSX_FILE_FILTER = /\.(?:[jt]sx|tsrx)(?:$|\?)/;
|
|
201
|
+
const VIRTUAL_MODULE_ID = /^\0/;
|
|
198
202
|
const REWRITE_CACHE_LIMIT = 2048;
|
|
199
203
|
const ROUTER_IMPORT_SOURCES = /* @__PURE__ */ new Set(["@markless/router", "@markless/core/router"]);
|
|
200
204
|
function anchorTransformPlugin() {
|
|
@@ -214,7 +218,10 @@ function anchorTransformPlugin() {
|
|
|
214
218
|
name: "markless-router:anchors",
|
|
215
219
|
transform: {
|
|
216
220
|
order: "pre",
|
|
217
|
-
filter: { id:
|
|
221
|
+
filter: { id: {
|
|
222
|
+
include: JSX_FILE_FILTER,
|
|
223
|
+
exclude: VIRTUAL_MODULE_ID
|
|
224
|
+
} },
|
|
218
225
|
async handler(code, id) {
|
|
219
226
|
if (!isTransformCandidateCode(code)) return;
|
|
220
227
|
if (!routesLoaded) await refreshRoutes(this.fs);
|
|
@@ -622,6 +629,7 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
622
629
|
};
|
|
623
630
|
assertMatchingRouteKeys(routes);
|
|
624
631
|
const importMap = input.importMap === void 0 ? void 0 : importMapImports(input.importMap);
|
|
632
|
+
const resumeModules = input.resumeModules === void 0 ? void 0 : routeResumeModules(input.resumeModules);
|
|
625
633
|
const hrefs = /* @__PURE__ */ new Set([
|
|
626
634
|
entries.resume,
|
|
627
635
|
...entries.prerenderWake !== void 0 ? [entries.prerenderWake] : [],
|
|
@@ -631,7 +639,8 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
631
639
|
...Object.values(routes.navigation).flat(),
|
|
632
640
|
...Object.values(routes.ssr).flat(),
|
|
633
641
|
...Object.values(routes.styles).flat(),
|
|
634
|
-
...Object.values(importMap?.imports ?? {})
|
|
642
|
+
...Object.values(importMap?.imports ?? {}),
|
|
643
|
+
...Object.values(resumeModules ?? {}).map(([href]) => href)
|
|
635
644
|
]);
|
|
636
645
|
for (const href of hrefs) {
|
|
637
646
|
const fileName = clientAssetFileName(href, base);
|
|
@@ -649,9 +658,15 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
649
658
|
base,
|
|
650
659
|
entries,
|
|
651
660
|
routes,
|
|
661
|
+
...resumeModules ? { resumeModules } : {},
|
|
652
662
|
...importMap ? { importMap } : {}
|
|
653
663
|
};
|
|
654
664
|
}
|
|
665
|
+
function routeResumeModules(input) {
|
|
666
|
+
const modules = routeMap(input, "resumeModules");
|
|
667
|
+
for (const [routeFile, [href, ...exports]] of Object.entries(modules)) if (!href || exports.length > 2 || !exports.every((name) => /^[\w$]+$/.test(name))) throw clientAssetsManifestError(`has an invalid resume module for route ${JSON.stringify(routeFile)}`);
|
|
668
|
+
return modules;
|
|
669
|
+
}
|
|
655
670
|
function requiredStrings(input, label) {
|
|
656
671
|
if (!Array.isArray(input)) throw clientAssetsManifestError(`has invalid ${label}`);
|
|
657
672
|
return input.map((item, index) => requiredString(item, `${label}[${index}]`));
|
|
@@ -859,21 +874,21 @@ function emitQueuedResumeContainerEvent(source, { censusRepair = true } = {}) {
|
|
|
859
874
|
" if (input.eventIndex >= 0) input = { ...input, eventRecord: (root.__mE ||= JSON.parse(root.querySelector('script[type=\"markless/view\"]').textContent).events)[input.eventIndex] };",
|
|
860
875
|
censusRepair ? " if (root.__marklessShifted) return import('@markless/web/fns/served-census-repair').then((census) => (census.marklessRepairCensus(root), resumeContainerEvent(input)));" : "",
|
|
861
876
|
" if (!root.__marklessDispatch) {",
|
|
862
|
-
" let handler = marklessResumeContainerEvent, tail = Promise.resolve(), done = tail, lastEvent = null, lastEventTime = undefined, seen = new WeakMap();",
|
|
877
|
+
" let handler = marklessResumeContainerEvent, tail = Promise.resolve(), done = tail, lastEvent = null, lastEventTime = undefined, seen = new WeakMap(), life = root.__mL;",
|
|
863
878
|
" root.__marklessRegisterDispatch = next => {",
|
|
864
879
|
" const fallback = handler;",
|
|
865
880
|
" handler = input => next(input, fallback);",
|
|
866
881
|
" };",
|
|
867
882
|
" root.__marklessDispatch = next => {",
|
|
868
883
|
" const event = next.event, eventTime = event?.timeStamp;",
|
|
869
|
-
" if (event && next.eventRecord && next.element) event.
|
|
884
|
+
" if (event && next.eventRecord && next.element) event.__marklessPressPath ??= [next.element];",
|
|
870
885
|
" if ((event === lastEvent && eventTime === lastEventTime) || (seen.has(event) && seen.get(event) === eventTime)) return done;",
|
|
871
886
|
" if (Object(event) === event) seen.set(event, eventTime);",
|
|
872
887
|
" lastEvent = event;",
|
|
873
888
|
" lastEventTime = eventTime;",
|
|
874
889
|
" let release;",
|
|
875
890
|
" const started = new Promise(resolve => release = resolve);",
|
|
876
|
-
" const run = () => (root.__marklessReleaseDispatch = release, handler(next));",
|
|
891
|
+
" const run = () => root.__mL === life ? (root.__marklessReleaseDispatch = release, handler(next)) : release();",
|
|
877
892
|
" done = tail.then(run, run);",
|
|
878
893
|
" done.then(release, release);",
|
|
879
894
|
" tail = started;",
|
|
@@ -885,6 +900,13 @@ function emitQueuedResumeContainerEvent(source, { censusRepair = true } = {}) {
|
|
|
885
900
|
].join("\n");
|
|
886
901
|
}
|
|
887
902
|
//#endregion
|
|
903
|
+
//#region ../bundler/src/served-render-data.ts
|
|
904
|
+
const MARKLESS_SERVED_RENDER_DATA_QUERY = "markless-served";
|
|
905
|
+
/** The served spelling of a render-data source request. */
|
|
906
|
+
function servedRenderDataSourceReference(source) {
|
|
907
|
+
return withQuery(source, { [MARKLESS_SERVED_RENDER_DATA_QUERY]: null });
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
888
910
|
//#region src/vite/mdx.ts
|
|
889
911
|
const MDX_ROUTE_RUNTIME_SPECIFIER = "@markless/router/vite/runtime/mdx-route";
|
|
890
912
|
function mdxTransformPlugin() {
|
|
@@ -898,6 +920,7 @@ function mdxTransformPlugin() {
|
|
|
898
920
|
},
|
|
899
921
|
transform: {
|
|
900
922
|
order: "pre",
|
|
923
|
+
filter: { id: /\.mdx(?:$|[?#])/ },
|
|
901
924
|
async handler(code, id) {
|
|
902
925
|
if (!isMdxFile(id)) return;
|
|
903
926
|
return {
|
|
@@ -933,7 +956,7 @@ function emitMdxClientRoute(route, id, resume) {
|
|
|
933
956
|
return [
|
|
934
957
|
`import { createMdxRenderDataSurface, loadMdxSymbol${resume ? ", tryResumeMdxScalar" : ""} } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
935
958
|
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
936
|
-
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id, route.parts)};`,
|
|
959
|
+
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id, route.parts, resume)};`,
|
|
937
960
|
renderOverlayLoader(),
|
|
938
961
|
emitRosterResumeLoaderInstall(),
|
|
939
962
|
"const marklessMdxPage = {",
|
|
@@ -1334,17 +1357,16 @@ function renderHeadInjections(components) {
|
|
|
1334
1357
|
function renderSymbolLoaders(components) {
|
|
1335
1358
|
return `[${components.map((component) => `(() => { let module; const loadModule = () => module ??= import(${JSON.stringify(scalarPlanSourceReference(component.specifier))}).catch(error => { module = undefined; throw error; }); return { prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return loadModule().then((mod) => mod.loadSymbol(symbolId.slice(${component.prefix.length}))); }, loadScalarActionPlan(symbolId) { return loadModule().then(mod => mod.loadScalarActionPlan?.(symbolId.slice(${component.prefix.length}))).then(action => action && ({ ...action, scope: ${JSON.stringify(component.prefix)} + action.scope })); } }; })()`).join(", ")}]`;
|
|
1336
1359
|
}
|
|
1337
|
-
function renderMdxRenderDataLoader(components, id, parts) {
|
|
1360
|
+
function renderMdxRenderDataLoader(components, id, parts, served = false) {
|
|
1338
1361
|
const reachedFrom = mdxMaterializedReachRoot(id);
|
|
1339
1362
|
const imports = components.map((component) => {
|
|
1340
1363
|
const source = withQuery(component.specifier, {
|
|
1341
1364
|
"markless-render-data": null,
|
|
1342
1365
|
...reachedFrom ? { "markless-reached-from": reachedFrom } : {}
|
|
1343
1366
|
});
|
|
1344
|
-
return `import(${JSON.stringify(source)})`;
|
|
1367
|
+
return `import(${JSON.stringify(served ? servedRenderDataSourceReference(source) : source)})`;
|
|
1345
1368
|
});
|
|
1346
|
-
const
|
|
1347
|
-
const children = components.map((component, index) => `{ componentIndex: ${index}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}, props: ${componentPropsExpression(component)}, surface: modules[${index}].${exportName} }`);
|
|
1369
|
+
const children = components.map((component, index) => `{ componentIndex: ${index}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}, props: ${componentPropsExpression(component)}, surface: modules[${index}].marklessPrerenderData }`);
|
|
1348
1370
|
const load = `Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
|
|
1349
1371
|
return parts ? `() => {\nconst marklessMdxParts = ${JSON.stringify(parts)};\nreturn ${load};\n}` : `() => ${load}`;
|
|
1350
1372
|
}
|
|
@@ -1511,6 +1533,8 @@ function fragmentRequestModule() {
|
|
|
1511
1533
|
await mkdir(dir, { recursive: true });
|
|
1512
1534
|
await writeFile(plugin, [
|
|
1513
1535
|
`const prefix = ${JSON.stringify(`${FRAGMENT_PATH_PREFIX}/`)};`,
|
|
1536
|
+
`const marker = ${JSON.stringify(FRAGMENT_RESPONSE_HEADER)};`,
|
|
1537
|
+
"const fragments = new WeakSet();",
|
|
1514
1538
|
"export default function fragmentRequests(nitroApp) {",
|
|
1515
1539
|
" nitroApp.hooks?.hook('request', (event) => {",
|
|
1516
1540
|
" const { pathname } = event.url;",
|
|
@@ -1518,6 +1542,19 @@ function fragmentRequestModule() {
|
|
|
1518
1542
|
" const page = new URL(event.url.href);",
|
|
1519
1543
|
" page.pathname = pathname.slice(prefix.length - 1);",
|
|
1520
1544
|
" event.url = page;",
|
|
1545
|
+
" fragments.add(event);",
|
|
1546
|
+
" event.res?.headers?.set(marker, '1');",
|
|
1547
|
+
" });",
|
|
1548
|
+
" nitroApp.hooks?.hook('response', (response, event) => {",
|
|
1549
|
+
" if (!fragments.has(event) || response.status < 300 || response.status > 399) return;",
|
|
1550
|
+
" const location = response.headers.get('location');",
|
|
1551
|
+
" if (!location) return;",
|
|
1552
|
+
" const target = new URL(location, event.url);",
|
|
1553
|
+
" if (target.origin !== event.url.origin || target.pathname.startsWith(prefix)) return;",
|
|
1554
|
+
" target.pathname = prefix.slice(0, -1) + target.pathname;",
|
|
1555
|
+
" try {",
|
|
1556
|
+
" response.headers.set('location', target.href);",
|
|
1557
|
+
" } catch {}",
|
|
1521
1558
|
" });",
|
|
1522
1559
|
"}",
|
|
1523
1560
|
""
|
|
@@ -1526,6 +1563,147 @@ function fragmentRequestModule() {
|
|
|
1526
1563
|
}
|
|
1527
1564
|
};
|
|
1528
1565
|
}
|
|
1566
|
+
const compressRenderedSource = `import { constants, createBrotliCompress, createGzip } from 'node:zlib';
|
|
1567
|
+
const accepts = (header, name) =>
|
|
1568
|
+
header.split(',').some((part) => {
|
|
1569
|
+
const [coding, ...params] = part.trim().toLowerCase().split(';');
|
|
1570
|
+
const q = params.map((param) => param.trim()).find((param) => param.startsWith('q='));
|
|
1571
|
+
return coding === name && (!q || Number(q.slice(2)) > 0);
|
|
1572
|
+
});
|
|
1573
|
+
const compressed = (source, zip) =>
|
|
1574
|
+
new ReadableStream({
|
|
1575
|
+
start(controller) {
|
|
1576
|
+
zip.on('data', (chunk) => controller.enqueue(new Uint8Array(chunk)));
|
|
1577
|
+
zip.on('end', () => controller.close());
|
|
1578
|
+
zip.on('error', (error) => controller.error(error));
|
|
1579
|
+
void (async () => {
|
|
1580
|
+
const reader = source.getReader();
|
|
1581
|
+
try {
|
|
1582
|
+
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read())
|
|
1583
|
+
if (!zip.write(chunk.value)) await new Promise((done) => zip.once('drain', done));
|
|
1584
|
+
zip.end();
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
zip.destroy(error);
|
|
1587
|
+
}
|
|
1588
|
+
})();
|
|
1589
|
+
},
|
|
1590
|
+
cancel(reason) {
|
|
1591
|
+
zip.destroy();
|
|
1592
|
+
return source.cancel(reason);
|
|
1593
|
+
},
|
|
1594
|
+
});
|
|
1595
|
+
export default async function compressRendered(event, next) {
|
|
1596
|
+
const response = await next();
|
|
1597
|
+
if (!(response instanceof Response) || !response.body) return response;
|
|
1598
|
+
const headers = response.headers;
|
|
1599
|
+
if (headers.has('content-encoding') || !/^text\\/html\\b/i.test(headers.get('content-type') ?? '')) return response;
|
|
1600
|
+
if (/\\bno-transform\\b/i.test(headers.get('cache-control') ?? '')) return response;
|
|
1601
|
+
const accepted = event.req.headers.get('accept-encoding') ?? '';
|
|
1602
|
+
const encoding = accepts(accepted, 'br') ? 'br' : accepts(accepted, 'gzip') ? 'gzip' : undefined;
|
|
1603
|
+
const sent = new Headers(headers);
|
|
1604
|
+
sent.append('vary', 'accept-encoding');
|
|
1605
|
+
if (!encoding) return new Response(response.body, { status: response.status, statusText: response.statusText, headers: sent });
|
|
1606
|
+
sent.set('content-encoding', encoding);
|
|
1607
|
+
sent.delete('content-length');
|
|
1608
|
+
const zip =
|
|
1609
|
+
encoding === 'br'
|
|
1610
|
+
? createBrotliCompress({
|
|
1611
|
+
flush: constants.BROTLI_OPERATION_FLUSH,
|
|
1612
|
+
params: { [constants.BROTLI_PARAM_QUALITY]: 5 },
|
|
1613
|
+
})
|
|
1614
|
+
: createGzip({ flush: constants.Z_SYNC_FLUSH });
|
|
1615
|
+
return new Response(compressed(response.body, zip), { status: response.status, statusText: response.statusText, headers: sent });
|
|
1616
|
+
}
|
|
1617
|
+
`;
|
|
1618
|
+
const EDGE_CONTENT_TYPES = {
|
|
1619
|
+
js: "text/javascript; charset=utf-8",
|
|
1620
|
+
css: "text/css; charset=utf-8"
|
|
1621
|
+
};
|
|
1622
|
+
function compressedAssetsState() {
|
|
1623
|
+
return { brotliDir: void 0 };
|
|
1624
|
+
}
|
|
1625
|
+
function compressedAssetsModule(configured, state = compressedAssetsState()) {
|
|
1626
|
+
return {
|
|
1627
|
+
name: "markless-router:compressed-assets",
|
|
1628
|
+
async setup(nitro) {
|
|
1629
|
+
const { preset, serveStatic } = nitro.options;
|
|
1630
|
+
if (preset === "nitro-prerender") return;
|
|
1631
|
+
if (preset?.startsWith("vercel")) {
|
|
1632
|
+
if (configured) return;
|
|
1633
|
+
const base = nitro.options.baseURL ?? "/";
|
|
1634
|
+
nitro.options.vercel ??= {};
|
|
1635
|
+
nitro.options.vercel.config ??= {};
|
|
1636
|
+
nitro.options.vercel.config.routes = [...Object.entries(EDGE_CONTENT_TYPES).map(([extension, type]) => ({
|
|
1637
|
+
src: `^${escapeRegExp$1(`${base}${MARKLESS_BUILD_PREFIX}`)}(.+\\.${extension})$`,
|
|
1638
|
+
has: [{
|
|
1639
|
+
type: "header",
|
|
1640
|
+
key: "accept-encoding",
|
|
1641
|
+
value: ".*\\bbr\\b.*"
|
|
1642
|
+
}],
|
|
1643
|
+
dest: `${base}${MARKLESS_BUILD_PREFIX}$1.br`,
|
|
1644
|
+
headers: {
|
|
1645
|
+
"content-encoding": "br",
|
|
1646
|
+
"content-type": type,
|
|
1647
|
+
vary: "Accept-Encoding",
|
|
1648
|
+
"cache-control": "public, max-age=31536000, immutable"
|
|
1649
|
+
}
|
|
1650
|
+
})), ...nitro.options.vercel.config.routes ?? []];
|
|
1651
|
+
state.edgeBrotliDir = join(nitro.options.output.publicDir, MARKLESS_BUILD_PREFIX);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (!serveStatic || serveStatic === "inline") return;
|
|
1655
|
+
if (!nitro.options.dev && nitro.options.buildDir && nitro.options.handlers) {
|
|
1656
|
+
const dir = join(nitro.options.buildDir, "markless-router");
|
|
1657
|
+
const handler = join(dir, "compress-rendered.mjs");
|
|
1658
|
+
await mkdir(dir, { recursive: true });
|
|
1659
|
+
await writeFile(handler, compressRenderedSource);
|
|
1660
|
+
nitro.options.handlers.push({
|
|
1661
|
+
route: "/**",
|
|
1662
|
+
middleware: true,
|
|
1663
|
+
handler
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
if (configured) return;
|
|
1667
|
+
nitro.options.compressPublicAssets = { gzip: true };
|
|
1668
|
+
state.brotliDir = nitro.options.output.publicDir;
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
const brotli = promisify(brotliCompress);
|
|
1673
|
+
const exists = (file) => access(file).then(() => true, () => false);
|
|
1674
|
+
function brotliCopiesPlugin(state) {
|
|
1675
|
+
return {
|
|
1676
|
+
name: "markless-router:brotli-copies",
|
|
1677
|
+
apply: "build",
|
|
1678
|
+
applyToEnvironment: (environment) => environment.name === "nitro",
|
|
1679
|
+
async buildStart() {
|
|
1680
|
+
if (state.brotliDir) {
|
|
1681
|
+
const dir = state.brotliDir;
|
|
1682
|
+
await writeBrotliCopies((await readdir(dir, { recursive: true })).filter((file) => file.endsWith(".gz")).map((file) => join(dir, file.slice(0, -3))), 9);
|
|
1683
|
+
}
|
|
1684
|
+
if (state.edgeBrotliDir) {
|
|
1685
|
+
const dir = state.edgeBrotliDir;
|
|
1686
|
+
const extensions = Object.keys(EDGE_CONTENT_TYPES);
|
|
1687
|
+
await writeBrotliCopies((await readdir(dir, { recursive: true }).catch(() => [])).filter((file) => extensions.includes(file.slice(file.lastIndexOf(".") + 1))).map((file) => join(dir, file)), 11);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
async function writeBrotliCopies(files, quality) {
|
|
1693
|
+
const next = async () => {
|
|
1694
|
+
const file = files.pop();
|
|
1695
|
+
if (file === void 0) return;
|
|
1696
|
+
if (!await exists(`${file}.br`)) {
|
|
1697
|
+
const source = await readFile(file).catch(() => void 0);
|
|
1698
|
+
if (source) await writeFile(`${file}.br`, await brotli(source, { params: { [constants.BROTLI_PARAM_QUALITY]: quality } }));
|
|
1699
|
+
}
|
|
1700
|
+
return next();
|
|
1701
|
+
};
|
|
1702
|
+
await Promise.all(Array.from({ length: availableParallelism() }, next));
|
|
1703
|
+
}
|
|
1704
|
+
function escapeRegExp$1(text) {
|
|
1705
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1706
|
+
}
|
|
1529
1707
|
//#endregion
|
|
1530
1708
|
//#region src/vite/nitro-dev-close.ts
|
|
1531
1709
|
function devNitroState() {
|
|
@@ -1561,11 +1739,19 @@ function recordServerEntry(state, outDir, bundle, isServerEntry) {
|
|
|
1561
1739
|
const entry = entries.find((chunk) => chunk.facadeModuleId && isServerEntry(chunk.facadeModuleId)) ?? (entries.length === 1 ? entries[0] : void 0);
|
|
1562
1740
|
if (entry) state.entry = resolve(outDir, entry.fileName);
|
|
1563
1741
|
}
|
|
1564
|
-
function prerenderRendererModule(state) {
|
|
1742
|
+
function prerenderRendererModule(state, clientDirs) {
|
|
1565
1743
|
return {
|
|
1566
1744
|
name: "markless-router:prerender-renderer",
|
|
1567
1745
|
setup(nitro) {
|
|
1746
|
+
nitro.hooks.hook("prerender:done", async ({ prerenderedRoutes = [] }) => {
|
|
1747
|
+
const missing = await missingPrerenderedFiles(nitro.options.output.publicDir, nitro.options.baseURL, clientDirs, prerenderedRoutes.flatMap((route) => route.fileName ? [route.fileName] : []));
|
|
1748
|
+
if (missing.length > 0) throw new Error(missingPrerenderedFilesMessage(missing));
|
|
1749
|
+
});
|
|
1568
1750
|
nitro.hooks.hook("prerender:config", async (config) => {
|
|
1751
|
+
config.output = {
|
|
1752
|
+
...config.output,
|
|
1753
|
+
publicDir: nitro.options.output.publicDir
|
|
1754
|
+
};
|
|
1569
1755
|
if (!state.entry) return;
|
|
1570
1756
|
const dir = join(nitro.options.buildDir, "markless-router");
|
|
1571
1757
|
const handler = join(dir, "prerender-renderer.mjs");
|
|
@@ -1581,6 +1767,45 @@ function prerenderRendererModule(state) {
|
|
|
1581
1767
|
}
|
|
1582
1768
|
};
|
|
1583
1769
|
}
|
|
1770
|
+
const IMPORT_MAP = /<script\b[^>]*\btype=["']importmap["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
1771
|
+
const LOADED_URL = /<(?:script|link)\b[^>]*?\b(?:src|href)=["']([^"']+)["']/gi;
|
|
1772
|
+
/** URLs a prerendered page loads from the client build folders: script and link targets plus import map targets. */
|
|
1773
|
+
function prerenderedClientUrls(html, baseURL, clientDirs) {
|
|
1774
|
+
const urls = [...html.matchAll(LOADED_URL)].map((match) => match[1]);
|
|
1775
|
+
for (const [, source] of html.matchAll(IMPORT_MAP)) {
|
|
1776
|
+
let map;
|
|
1777
|
+
try {
|
|
1778
|
+
map = JSON.parse(source);
|
|
1779
|
+
} catch {
|
|
1780
|
+
throw new Error(`Markless Router prerendered an import map that is not JSON: ${source}`);
|
|
1781
|
+
}
|
|
1782
|
+
for (const target of [...Object.values(map.imports ?? {}), ...Object.values(map.scopes ?? {}).flatMap((scope) => Object.values(scope))]) if (typeof target === "string") urls.push(target);
|
|
1783
|
+
}
|
|
1784
|
+
const prefixes = clientDirs.map((dir) => joinURL(baseURL, dir, "/"));
|
|
1785
|
+
return [...new Set(urls)].filter((url) => prefixes.some((prefix) => url.startsWith(prefix)));
|
|
1786
|
+
}
|
|
1787
|
+
async function missingPrerenderedFiles(publicDir, baseURL, clientDirs, pageFiles) {
|
|
1788
|
+
const missing = [];
|
|
1789
|
+
const exists = /* @__PURE__ */ new Map();
|
|
1790
|
+
for (const page of pageFiles) {
|
|
1791
|
+
const html = await readFile(join(publicDir, page), "utf8").catch(() => void 0);
|
|
1792
|
+
if (html === void 0) continue;
|
|
1793
|
+
for (const url of prerenderedClientUrls(html, baseURL, clientDirs)) {
|
|
1794
|
+
const file = join(publicDir, decodeURIComponent(withoutBase(parseURL(url).pathname, baseURL)));
|
|
1795
|
+
if (!exists.has(file)) exists.set(file, stat(file).then((entry) => entry.isFile(), () => false));
|
|
1796
|
+
if (!await exists.get(file)) missing.push({
|
|
1797
|
+
page,
|
|
1798
|
+
url
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
return missing;
|
|
1803
|
+
}
|
|
1804
|
+
function missingPrerenderedFilesMessage(missing) {
|
|
1805
|
+
const shown = missing.slice(0, 20).map(({ page, url }) => `- ${page} loads ${url}`);
|
|
1806
|
+
if (missing.length > shown.length) shown.push(`- and ${missing.length - shown.length} more`);
|
|
1807
|
+
return `Markless Router prerendered pages that load client files this build did not emit, so a browser would fail to load them:\n${shown.join("\n")}\nAn earlier build's output or a stale cache reached the prerendered pages; delete the build output folders and build again.`;
|
|
1808
|
+
}
|
|
1584
1809
|
//#endregion
|
|
1585
1810
|
//#region src/vite/index.ts
|
|
1586
1811
|
const PAGE_SERVER_ONLY_PROPS = [{
|
|
@@ -1593,6 +1818,9 @@ const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
|
1593
1818
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
1594
1819
|
const RESUME_ENTRY_ORIGIN = "/entries/resume-entry.ts";
|
|
1595
1820
|
const RESUME_ENTRY_PATH_ID = "virtual:markless-router/resume-entry-path";
|
|
1821
|
+
const RESUME_MODULES_ID = "virtual:markless-router/resume-modules";
|
|
1822
|
+
const RESUME_MODULES_ORIGIN = "/entries/resume-modules.ts";
|
|
1823
|
+
const DEV_RESUME_MODULES_ID = "virtual:markless-router/dev-resume-modules";
|
|
1596
1824
|
const PRERENDER_WAKE_ENTRY_ID = "virtual:markless-router/prerender-wake-entry";
|
|
1597
1825
|
const PRERENDER_WAKE_ENTRY_ORIGIN = "/entries/prerender-wake-entry.ts";
|
|
1598
1826
|
const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-entry-path";
|
|
@@ -1609,7 +1837,7 @@ const LINK_BRIDGE_PATHS_PLACEHOLDER = "__MARKLESS_ROUTER_LINK_BRIDGE_PATHS__";
|
|
|
1609
1837
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1610
1838
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1611
1839
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
1612
|
-
const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|prerender-wake-entry|prerender-wake-entry-path|navigation-entry|navigation-entry-path|fragment-entry|fragment-entry-path|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
|
|
1840
|
+
const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|resume-modules|dev-resume-modules|prerender-wake-entry|prerender-wake-entry-path|navigation-entry|navigation-entry-path|fragment-entry|fragment-entry-path|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
|
|
1613
1841
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1614
1842
|
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "src/vite/entries");
|
|
1615
1843
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1622,6 +1850,7 @@ const virtualEntryFiles = {
|
|
|
1622
1850
|
[ROUTE_DISCOVERY_ID]: "route-discovery.ts",
|
|
1623
1851
|
[CLIENT_ENTRY_ID]: "client-entry.ts",
|
|
1624
1852
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1853
|
+
[RESUME_MODULES_ID]: "resume-modules.ts",
|
|
1625
1854
|
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1626
1855
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1627
1856
|
[FRAGMENT_ENTRY_ID]: "fragment-entry.ts",
|
|
@@ -1647,8 +1876,9 @@ function router(options = {}) {
|
|
|
1647
1876
|
const routePreloads = routePreloadState();
|
|
1648
1877
|
const serverBuild = serverBuildState();
|
|
1649
1878
|
const devNitro = devNitroState();
|
|
1879
|
+
const compressedAssets = compressedAssetsState();
|
|
1650
1880
|
return [
|
|
1651
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry, serverBuild, devNitro),
|
|
1881
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry, serverBuild, devNitro, compressedAssets),
|
|
1652
1882
|
devSourceModuleRequestPlugin(),
|
|
1653
1883
|
devPageForUrlPlugin(options),
|
|
1654
1884
|
pageServerOnlyPropsPlugin(),
|
|
@@ -1660,6 +1890,7 @@ function router(options = {}) {
|
|
|
1660
1890
|
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, options, fragmentEntry),
|
|
1661
1891
|
linkBridgeFilesPlugin(navigationEntry, fragmentEntry, options),
|
|
1662
1892
|
devNitroClosePlugin(devNitro),
|
|
1893
|
+
brotliCopiesPlugin(compressedAssets),
|
|
1663
1894
|
nitroPlugins
|
|
1664
1895
|
];
|
|
1665
1896
|
}
|
|
@@ -1724,7 +1955,7 @@ function devSourceModuleRequestPlugin() {
|
|
|
1724
1955
|
}
|
|
1725
1956
|
};
|
|
1726
1957
|
}
|
|
1727
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState(), serverBuild = serverBuildState(), devNitro = devNitroState()) {
|
|
1958
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState(), serverBuild = serverBuildState(), devNitro = devNitroState(), compressedAssets = compressedAssetsState()) {
|
|
1728
1959
|
let clientOutDir = "";
|
|
1729
1960
|
let clientEnvironmentName = "";
|
|
1730
1961
|
let clientEnvironmentConfig;
|
|
@@ -1757,12 +1988,10 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1757
1988
|
ssrEnvironment.build.rolldownOptions ??= {};
|
|
1758
1989
|
ssrEnvironment.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, config.root);
|
|
1759
1990
|
const serverWatch = withWatchIgnores(config.server?.watch);
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
server
|
|
1763
|
-
|
|
1764
|
-
watch: serverWatch
|
|
1765
|
-
}
|
|
1991
|
+
config.nitro = createNitroConfig(config.nitro, config.root, serverWatch.ignored, env?.command === "build", serverBuild, devNitro, compressedAssets);
|
|
1992
|
+
config.server = {
|
|
1993
|
+
...config.server,
|
|
1994
|
+
watch: serverWatch
|
|
1766
1995
|
};
|
|
1767
1996
|
},
|
|
1768
1997
|
configResolved(config) {
|
|
@@ -1808,6 +2037,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1808
2037
|
ssr: {},
|
|
1809
2038
|
styles: {}
|
|
1810
2039
|
};
|
|
2040
|
+
routePreloads.resumeModules = {};
|
|
1811
2041
|
routePreloads.persisted = false;
|
|
1812
2042
|
routePreloads.importMap = void 0;
|
|
1813
2043
|
await removeClientAssetsManifest(clientOutDir);
|
|
@@ -1821,6 +2051,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1821
2051
|
fragmentEntry.fileName = manifest.entries.fragment ? clientAssetFileName(manifest.entries.fragment, manifest.base) : void 0;
|
|
1822
2052
|
fragmentEntry.imports = manifest.entries.fragmentImports;
|
|
1823
2053
|
routePreloads.routes = manifest.routes;
|
|
2054
|
+
routePreloads.resumeModules = manifest.resumeModules ?? {};
|
|
1824
2055
|
routePreloads.persisted = true;
|
|
1825
2056
|
routePreloads.importMap = manifest.importMap;
|
|
1826
2057
|
},
|
|
@@ -1833,6 +2064,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1833
2064
|
}
|
|
1834
2065
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1835
2066
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
2067
|
+
const resumeModulesChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_MODULES_ORIGIN));
|
|
1836
2068
|
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1837
2069
|
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1838
2070
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
@@ -1856,6 +2088,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1856
2088
|
navigationPolyfillIds,
|
|
1857
2089
|
prerenderWakeChunk,
|
|
1858
2090
|
resumeChunk,
|
|
2091
|
+
resumeModulesChunk,
|
|
1859
2092
|
root: routePreloads.root
|
|
1860
2093
|
});
|
|
1861
2094
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
@@ -1900,6 +2133,12 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1900
2133
|
...pendingClientAssets,
|
|
1901
2134
|
importMap: JSON.parse(String(importMap.source))
|
|
1902
2135
|
};
|
|
2136
|
+
const resumeModules = routeResumeModulesFromBundle(bundle ?? {}, routePreloads.base, pendingClientAssets.importMap?.imports ?? {}, pendingClientAssets.routes.ssr);
|
|
2137
|
+
routePreloads.resumeModules = resumeModules;
|
|
2138
|
+
pendingClientAssets = {
|
|
2139
|
+
...pendingClientAssets,
|
|
2140
|
+
resumeModules
|
|
2141
|
+
};
|
|
1903
2142
|
await writeClientAssetsManifest(clientOutDir, pendingClientAssets);
|
|
1904
2143
|
}
|
|
1905
2144
|
};
|
|
@@ -1920,17 +2159,21 @@ function requestFileTransformPlugin() {
|
|
|
1920
2159
|
configResolved(config) {
|
|
1921
2160
|
root = config.root;
|
|
1922
2161
|
},
|
|
1923
|
-
transform
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2162
|
+
transform: {
|
|
2163
|
+
filter: { id: REQUEST_FILE_ID_FILTER },
|
|
2164
|
+
handler(code, id) {
|
|
2165
|
+
const fileId = requestFileIdForTransform(root, id);
|
|
2166
|
+
if (!fileId) return;
|
|
2167
|
+
const transform = transformRequestFileSource(fileId, code);
|
|
2168
|
+
return transform ? {
|
|
2169
|
+
code: transform.code,
|
|
2170
|
+
map: null
|
|
2171
|
+
} : void 0;
|
|
2172
|
+
}
|
|
1931
2173
|
}
|
|
1932
2174
|
};
|
|
1933
2175
|
}
|
|
2176
|
+
const REQUEST_FILE_ID_FILTER = /[\\/](?:api|middleware)[\\/]/;
|
|
1934
2177
|
function requestFileIdForTransform(root, id) {
|
|
1935
2178
|
if (id.startsWith("\0")) return;
|
|
1936
2179
|
const pathname = decodePath(parseURL(id).pathname);
|
|
@@ -1943,18 +2186,21 @@ function requestFileIdForTransform(root, id) {
|
|
|
1943
2186
|
function requestFileBuildTransformPlugin(root) {
|
|
1944
2187
|
return {
|
|
1945
2188
|
name: "markless-router:nitro-request-files",
|
|
1946
|
-
transform
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2189
|
+
transform: {
|
|
2190
|
+
filter: { id: REQUEST_FILE_ID_FILTER },
|
|
2191
|
+
handler(code, id) {
|
|
2192
|
+
const fileId = requestFileIdForTransform(root, id);
|
|
2193
|
+
if (!fileId) return;
|
|
2194
|
+
const transform = transformRequestFileSource(fileId, code);
|
|
2195
|
+
return transform ? {
|
|
2196
|
+
code: transform.code,
|
|
2197
|
+
map: null
|
|
2198
|
+
} : void 0;
|
|
2199
|
+
}
|
|
1954
2200
|
}
|
|
1955
2201
|
};
|
|
1956
2202
|
}
|
|
1957
|
-
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false, serverBuild = serverBuildState(), devNitro = devNitroState()) {
|
|
2203
|
+
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false, serverBuild = serverBuildState(), devNitro = devNitroState(), compressedAssets = compressedAssetsState()) {
|
|
1958
2204
|
const scanDirs = Array.isArray(nitroConfig?.scanDirs) ? nitroConfig.scanDirs.filter((dir) => typeof dir === "string") : [];
|
|
1959
2205
|
const watchOptions = withWatchIgnores(nitroConfig?.watchOptions, serverWatchIgnored);
|
|
1960
2206
|
const publicAssets = Array.isArray(nitroConfig?.publicAssets) ? nitroConfig.publicAssets : [];
|
|
@@ -1977,7 +2223,8 @@ function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isB
|
|
|
1977
2223
|
modules: [
|
|
1978
2224
|
...nitroConfig?.modules ?? [],
|
|
1979
2225
|
fragmentRequestModule(),
|
|
1980
|
-
|
|
2226
|
+
compressedAssetsModule(nitroConfig?.compressPublicAssets !== void 0, compressedAssets),
|
|
2227
|
+
...isBuild ? [prerenderRendererModule(serverBuild, [MARKLESS_BUILD_PREFIX, LINK_BRIDGE_DIR])] : [devNitroModule(devNitro)]
|
|
1981
2228
|
],
|
|
1982
2229
|
...isBuild && { routeRules: {
|
|
1983
2230
|
[`/${MARKLESS_BUILD_PREFIX}**`]: { headers: { "cache-control": "public, max-age=31536000, immutable" } },
|
|
@@ -2041,7 +2288,8 @@ function routePreloadState() {
|
|
|
2041
2288
|
navigation: {},
|
|
2042
2289
|
ssr: {},
|
|
2043
2290
|
styles: {}
|
|
2044
|
-
}
|
|
2291
|
+
},
|
|
2292
|
+
resumeModules: {}
|
|
2045
2293
|
};
|
|
2046
2294
|
}
|
|
2047
2295
|
let prerenderWakeChannelCaptured = false;
|
|
@@ -2050,18 +2298,21 @@ function prerenderWakeChannelEnabled() {
|
|
|
2050
2298
|
}
|
|
2051
2299
|
function routerClientInput(input, root) {
|
|
2052
2300
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
2301
|
+
const resumeModulesId = scopedVirtualEntryId(RESUME_MODULES_ID, root);
|
|
2053
2302
|
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
2054
2303
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
2055
2304
|
const fragmentEntryId = scopedVirtualEntryId(FRAGMENT_ENTRY_ID, root);
|
|
2056
2305
|
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
2057
2306
|
if (input === void 0) return [
|
|
2058
2307
|
resumeEntryId,
|
|
2308
|
+
resumeModulesId,
|
|
2059
2309
|
...wakeEntries,
|
|
2060
2310
|
navigationEntryId,
|
|
2061
2311
|
fragmentEntryId
|
|
2062
2312
|
];
|
|
2063
2313
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
2064
2314
|
resumeEntryId,
|
|
2315
|
+
resumeModulesId,
|
|
2065
2316
|
...wakeEntries,
|
|
2066
2317
|
navigationEntryId,
|
|
2067
2318
|
fragmentEntryId,
|
|
@@ -2070,6 +2321,7 @@ function routerClientInput(input, root) {
|
|
|
2070
2321
|
if (isRecord(input)) return {
|
|
2071
2322
|
...input,
|
|
2072
2323
|
"markless-router-resume": resumeEntryId,
|
|
2324
|
+
"markless-router-resume-modules": resumeModulesId,
|
|
2073
2325
|
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
2074
2326
|
"markless-router-navigation": navigationEntryId,
|
|
2075
2327
|
"markless-router-fragment": fragmentEntryId
|
|
@@ -2081,6 +2333,7 @@ function isVirtualEntryChunk(chunk, origin) {
|
|
|
2081
2333
|
}
|
|
2082
2334
|
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState(), options = {}, fragmentEntry = resumeEntryState()) {
|
|
2083
2335
|
let root = "";
|
|
2336
|
+
let serving = false;
|
|
2084
2337
|
return {
|
|
2085
2338
|
name: "markless-router:routes",
|
|
2086
2339
|
config() {
|
|
@@ -2089,6 +2342,7 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
2089
2342
|
sharedDuringBuild: true,
|
|
2090
2343
|
configResolved(config) {
|
|
2091
2344
|
root = config.root;
|
|
2345
|
+
serving = config.command === "serve";
|
|
2092
2346
|
},
|
|
2093
2347
|
resolveId: {
|
|
2094
2348
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
@@ -2098,27 +2352,31 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
2098
2352
|
id: `\0${baseId}${rootScopeQuery(root, id)}`,
|
|
2099
2353
|
moduleSideEffects: false
|
|
2100
2354
|
};
|
|
2101
|
-
if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === PRERENDER_WAKE_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === FRAGMENT_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
|
|
2355
|
+
if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === PRERENDER_WAKE_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === FRAGMENT_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID || baseId === DEV_RESUME_MODULES_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
|
|
2102
2356
|
const entryFile = virtualEntryFiles[baseId];
|
|
2103
2357
|
if (!entryFile) return;
|
|
2104
2358
|
if (baseId === ROUTE_HREF_ID) return join(VIRTUAL_ENTRY_DIR, entryFile);
|
|
2105
2359
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
2106
2360
|
}
|
|
2107
2361
|
},
|
|
2108
|
-
load
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
if (
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
const
|
|
2120
|
-
this?.
|
|
2121
|
-
|
|
2362
|
+
load: {
|
|
2363
|
+
filter: { id: /^\0/ },
|
|
2364
|
+
handler(id) {
|
|
2365
|
+
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root, options);
|
|
2366
|
+
if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
|
|
2367
|
+
if (id.startsWith(`\0${PRERENDER_WAKE_ENTRY_PATH_ID}`)) {
|
|
2368
|
+
if (!prerenderWakeChannelEnabled() || !prerenderWakeEntry.fileName) return "export const prerenderWakeEntryPath = undefined;";
|
|
2369
|
+
return entryPathSource("prerenderWakeEntryPath", prerenderWakeEntry, PRERENDER_WAKE_ENTRY_ID, root);
|
|
2370
|
+
}
|
|
2371
|
+
if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
|
|
2372
|
+
if (id.startsWith(`\0${FRAGMENT_ENTRY_PATH_ID}`)) return entryPathSource("fragmentEntryPath", fragmentEntry, FRAGMENT_ENTRY_ID, root);
|
|
2373
|
+
if (id.startsWith(`\0${DEV_RESUME_MODULES_ID}`)) return serving ? `export * from ${JSON.stringify(scopedVirtualEntryId(RESUME_MODULES_ID, root))};` : "export const mdxResumeModules = undefined, tsrxResumeModules = undefined;";
|
|
2374
|
+
if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads, this?.environment?.config.consumer === "client");
|
|
2375
|
+
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) {
|
|
2376
|
+
const documentFile = join(root, "document.tsrx");
|
|
2377
|
+
this?.addWatchFile?.(documentFile);
|
|
2378
|
+
return routerOptionsSource(options, documentFile);
|
|
2379
|
+
}
|
|
2122
2380
|
}
|
|
2123
2381
|
}
|
|
2124
2382
|
};
|
|
@@ -2215,7 +2473,7 @@ function serverEntrySource(root, options) {
|
|
|
2215
2473
|
`import { prerenderWakeEntryPath } from ${JSON.stringify(`${PRERENDER_WAKE_ENTRY_PATH_ID}${query}`)};`,
|
|
2216
2474
|
`import { navigationEntryPath } from ${JSON.stringify(`${NAVIGATION_ENTRY_PATH_ID}${query}`)};`,
|
|
2217
2475
|
`import { fragmentEntryPath } from ${JSON.stringify(`${FRAGMENT_ENTRY_PATH_ID}${query}`)};`,
|
|
2218
|
-
`import { documentStylesheets, routeModulePreloads, routeSsrModulePreloads, routeStylesheets, documentImportMap } from ${JSON.stringify(`${ROUTE_PRELOADS_ID}${query}`)};`,
|
|
2476
|
+
`import { documentStylesheets, routeModulePreloads, routeSsrModulePreloads, routeStylesheets, documentImportMap, routeResumeModules } from ${JSON.stringify(`${ROUTE_PRELOADS_ID}${query}`)};`,
|
|
2219
2477
|
`import { pageModuleLoaders, routeFileIds } from ${JSON.stringify(`${ROUTE_DISCOVERY_ID}${query}`)};`,
|
|
2220
2478
|
`import { documentNavigation } from ${JSON.stringify(`${ROUTER_OPTIONS_ID}${query}`)};`,
|
|
2221
2479
|
`const documentModuleLoaders = import.meta.glob(['/document.tsrx']);`,
|
|
@@ -2233,6 +2491,7 @@ function serverEntrySource(root, options) {
|
|
|
2233
2491
|
...options.prefetch === false ? [" prefetch: false,"] : [],
|
|
2234
2492
|
` routeModulePreloads,`,
|
|
2235
2493
|
` routeSsrModulePreloads,`,
|
|
2494
|
+
` routeResumeModules,`,
|
|
2236
2495
|
` routeStylesheets,`,
|
|
2237
2496
|
` documentStylesheets,`,
|
|
2238
2497
|
` importMap: documentImportMap,`,
|
|
@@ -2273,7 +2532,8 @@ function routePreloadsSource(state, client) {
|
|
|
2273
2532
|
`export const routeStylesheets = ${state.persisted ? "routePreloadData.styles ?? {}" : "undefined"};`,
|
|
2274
2533
|
`const documentStylesheetsJson = globalThis.__marklessRouterDocumentStylesheetsJson ?? ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)};`,
|
|
2275
2534
|
`export const documentStylesheets = documentStylesheetsJson === ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)} ? [] : JSON.parse(documentStylesheetsJson);`,
|
|
2276
|
-
`export const documentImportMap = ${JSON.stringify(state.persisted && state.importMap ? importMapScript(state.importMap) : "")}
|
|
2535
|
+
`export const documentImportMap = ${JSON.stringify(state.persisted && state.importMap ? importMapScript(state.importMap) : "")};`,
|
|
2536
|
+
`export const routeResumeModules = ${JSON.stringify(state.persisted ? state.resumeModules : {})};`
|
|
2277
2537
|
];
|
|
2278
2538
|
return [
|
|
2279
2539
|
`const routePreloadsJson = globalThis.__marklessRouterRoutePreloadsJson ?? ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)};`,
|
|
@@ -2403,7 +2663,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
2403
2663
|
const ssrFileNames = /* @__PURE__ */ new Set();
|
|
2404
2664
|
if (input.resumeChunk) {
|
|
2405
2665
|
includeChunk(ssrFileNames, landingScope, input.resumeChunk.fileName);
|
|
2406
|
-
for (const
|
|
2666
|
+
for (const table of [input.resumeChunk, input.resumeModulesChunk]) for (const fileName of table ? routeScopedDynamicImports(table, routeFile) : []) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
2407
2667
|
}
|
|
2408
2668
|
if (input.prerenderWakeChunk) {
|
|
2409
2669
|
includeChunk(ssrFileNames, landingScope, input.prerenderWakeChunk.fileName);
|
|
@@ -2433,7 +2693,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
2433
2693
|
for (const entry of lazySiblings.length ? [input.resumeChunk, input.prerenderWakeChunk] : []) {
|
|
2434
2694
|
if (!entry) continue;
|
|
2435
2695
|
includeChunk(firstEventPath, landingScope, entry.fileName);
|
|
2436
|
-
for (const fileName of routeScopedDynamicImports(
|
|
2696
|
+
for (const table of [entry, entry === input.resumeChunk && input.resumeModulesChunk]) for (const fileName of table ? routeScopedDynamicImports(table, routeFile) : []) includeChunk(firstEventPath, landingScope, fileName);
|
|
2437
2697
|
}
|
|
2438
2698
|
for (const fileName of lazySiblings) includeChunk(firstEventPath, landingScope, fileName);
|
|
2439
2699
|
ssr[routeFile] = !routeHasSymbols && [...routeSourceFiles].some((file) => resolverSourceFiles.has(file)) ? [] : [...[...firstEventPath].filter((fileName) => ssrFileNames.has(fileName)), ...[...ssrFileNames].filter((fileName) => !firstEventPath.has(fileName))].map((fileName) => joinURL(input.base, fileName));
|
|
@@ -2509,6 +2769,68 @@ function narrowToPlanned(fileNames, planned, scope, entries) {
|
|
|
2509
2769
|
fileNames.clear();
|
|
2510
2770
|
for (const fileName of [...ordered, ...kept]) fileNames.add(fileName);
|
|
2511
2771
|
}
|
|
2772
|
+
function routeResumeModulesFromBundle(bundle, base, importMap, landing) {
|
|
2773
|
+
const table = outputChunks(bundle).find((chunk) => isVirtualEntryChunk(chunk, RESUME_MODULES_ORIGIN));
|
|
2774
|
+
if (!table?.code) return {};
|
|
2775
|
+
const modules = {};
|
|
2776
|
+
const loaders = routeResumeLoaders(table.code);
|
|
2777
|
+
for (const { routeFile, specifier, evaluate } of loaders.found) {
|
|
2778
|
+
if (!evaluate) throw new Error(`Markless Router could not read how ${table.fileName} evaluates the resume module of ${routeFile}.`);
|
|
2779
|
+
const url = /^\.\.?\//.test(specifier) ? joinURL(base, normalize(join(dirname(table.fileName), specifier))) : importMap[specifier];
|
|
2780
|
+
if (!url) throw new Error(`Markless Router could not resolve the resume module of ${routeFile} (${specifier}).`);
|
|
2781
|
+
if (landing[routeFile]?.length && !landing[routeFile].includes(url)) throw new Error(`Markless Router's landing plan for ${routeFile} does not preload its resume module ${url}: the first event would fetch it.`);
|
|
2782
|
+
modules[routeFile] = [url, ...evaluate];
|
|
2783
|
+
}
|
|
2784
|
+
if (loaders.found.length !== loaders.imports) throw new Error(`Markless Router read ${loaders.found.length} of the ${loaders.imports} route resume loaders in ${table.fileName}.`);
|
|
2785
|
+
return modules;
|
|
2786
|
+
}
|
|
2787
|
+
function routeResumeLoaders(code) {
|
|
2788
|
+
const found = [];
|
|
2789
|
+
const evaluations = /* @__PURE__ */ new Map();
|
|
2790
|
+
let imports = 0;
|
|
2791
|
+
const visit = (node, route) => {
|
|
2792
|
+
if (Array.isArray(node)) return node.forEach((child) => visit(child, route));
|
|
2793
|
+
if (!node || typeof node !== "object" || typeof node.type !== "string") return;
|
|
2794
|
+
const current = node;
|
|
2795
|
+
const key = current.type === "Property" ? current.key : void 0;
|
|
2796
|
+
const routeFile = typeof key?.value === "string" && /^\/pages\/.+\.(?:tsrx|mdx)$/.test(key.value) ? key.value.slice(1) : route;
|
|
2797
|
+
const then = current.type === "CallExpression" ? current.callee : void 0;
|
|
2798
|
+
if (then?.type === "MemberExpression" && then.object.type === "ImportExpression" && then.property.name === "then") evaluations.set(then.object, packEvaluation(current));
|
|
2799
|
+
if (current.type === "ImportExpression") {
|
|
2800
|
+
imports++;
|
|
2801
|
+
const specifier = staticString(current.source);
|
|
2802
|
+
if (routeFile && specifier) found.push({
|
|
2803
|
+
routeFile,
|
|
2804
|
+
specifier,
|
|
2805
|
+
evaluate: evaluations.has(current) ? evaluations.get(current) : []
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
for (const [field, child] of Object.entries(current)) if (field !== "type") visit(child, routeFile);
|
|
2809
|
+
};
|
|
2810
|
+
visit(parseAst(code));
|
|
2811
|
+
return {
|
|
2812
|
+
found,
|
|
2813
|
+
imports
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function staticString(node) {
|
|
2817
|
+
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
|
|
2818
|
+
const quasis = node?.type === "TemplateLiteral" ? node.quasis : [];
|
|
2819
|
+
return quasis.length === 1 ? quasis[0].value.cooked : void 0;
|
|
2820
|
+
}
|
|
2821
|
+
function packEvaluation(then) {
|
|
2822
|
+
const [callback] = then.arguments;
|
|
2823
|
+
const [param] = callback?.params ?? [];
|
|
2824
|
+
if (callback?.type !== "ArrowFunctionExpression" || param?.type !== "Identifier") return;
|
|
2825
|
+
const member = (node) => node?.type === "MemberExpression" && !node.computed && node.object.name === param.name ? node.property.name : void 0;
|
|
2826
|
+
const call = (node) => node?.type === "CallExpression" && node.arguments.length === 0 ? member(node.callee) : void 0;
|
|
2827
|
+
const body = callback.body;
|
|
2828
|
+
const [first, second, ...rest] = body.expressions ?? [];
|
|
2829
|
+
const init = body.type === "SequenceExpression" ? call(first) : call(body);
|
|
2830
|
+
const read = body.type === "SequenceExpression" && !rest.length ? member(second) : void 0;
|
|
2831
|
+
if (body.type === "SequenceExpression") return init && read ? [init, read] : void 0;
|
|
2832
|
+
return init ? [init] : void 0;
|
|
2833
|
+
}
|
|
2512
2834
|
function routeScopedDynamicImports(chunk, routeFile) {
|
|
2513
2835
|
if (!chunk.code) return [];
|
|
2514
2836
|
const imports = /* @__PURE__ */ new Set();
|