@markless/router 0.8.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-BsYTWdxi.js → create-server-entry-BnaEfDHl.js} +311 -66
- package/dist/index.js +2 -2
- package/dist/{request-locals-BDAhNpEa.js → navigation-timing-CzH6VaBR.js} +4 -2
- 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 +190 -7
- package/dist/vite/runtime/mdx-route.js +125 -13
- package/dist/vite.js +411 -62
- package/package.json +5 -5
- package/src/vite/entries/fragment-entry.ts +570 -238
- 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,171 @@ 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
|
+
}
|
|
1707
|
+
//#endregion
|
|
1708
|
+
//#region src/vite/nitro-dev-close.ts
|
|
1709
|
+
function devNitroState() {
|
|
1710
|
+
return { nitro: void 0 };
|
|
1711
|
+
}
|
|
1712
|
+
function devNitroModule(state) {
|
|
1713
|
+
return {
|
|
1714
|
+
name: "markless-router:dev-nitro",
|
|
1715
|
+
setup(nitro) {
|
|
1716
|
+
state.nitro = nitro;
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
function devNitroClosePlugin(state) {
|
|
1721
|
+
return {
|
|
1722
|
+
name: "markless-router:dev-nitro-close",
|
|
1723
|
+
apply: "serve",
|
|
1724
|
+
async closeBundle() {
|
|
1725
|
+
const nitro = state.nitro;
|
|
1726
|
+
state.nitro = void 0;
|
|
1727
|
+
await nitro?.close();
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1529
1731
|
//#endregion
|
|
1530
1732
|
//#region src/vite/nitro-prerender.ts
|
|
1531
1733
|
function serverBuildState() {
|
|
@@ -1537,11 +1739,19 @@ function recordServerEntry(state, outDir, bundle, isServerEntry) {
|
|
|
1537
1739
|
const entry = entries.find((chunk) => chunk.facadeModuleId && isServerEntry(chunk.facadeModuleId)) ?? (entries.length === 1 ? entries[0] : void 0);
|
|
1538
1740
|
if (entry) state.entry = resolve(outDir, entry.fileName);
|
|
1539
1741
|
}
|
|
1540
|
-
function prerenderRendererModule(state) {
|
|
1742
|
+
function prerenderRendererModule(state, clientDirs) {
|
|
1541
1743
|
return {
|
|
1542
1744
|
name: "markless-router:prerender-renderer",
|
|
1543
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
|
+
});
|
|
1544
1750
|
nitro.hooks.hook("prerender:config", async (config) => {
|
|
1751
|
+
config.output = {
|
|
1752
|
+
...config.output,
|
|
1753
|
+
publicDir: nitro.options.output.publicDir
|
|
1754
|
+
};
|
|
1545
1755
|
if (!state.entry) return;
|
|
1546
1756
|
const dir = join(nitro.options.buildDir, "markless-router");
|
|
1547
1757
|
const handler = join(dir, "prerender-renderer.mjs");
|
|
@@ -1557,6 +1767,45 @@ function prerenderRendererModule(state) {
|
|
|
1557
1767
|
}
|
|
1558
1768
|
};
|
|
1559
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
|
+
}
|
|
1560
1809
|
//#endregion
|
|
1561
1810
|
//#region src/vite/index.ts
|
|
1562
1811
|
const PAGE_SERVER_ONLY_PROPS = [{
|
|
@@ -1569,6 +1818,9 @@ const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
|
1569
1818
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
1570
1819
|
const RESUME_ENTRY_ORIGIN = "/entries/resume-entry.ts";
|
|
1571
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";
|
|
1572
1824
|
const PRERENDER_WAKE_ENTRY_ID = "virtual:markless-router/prerender-wake-entry";
|
|
1573
1825
|
const PRERENDER_WAKE_ENTRY_ORIGIN = "/entries/prerender-wake-entry.ts";
|
|
1574
1826
|
const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-entry-path";
|
|
@@ -1585,7 +1837,7 @@ const LINK_BRIDGE_PATHS_PLACEHOLDER = "__MARKLESS_ROUTER_LINK_BRIDGE_PATHS__";
|
|
|
1585
1837
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1586
1838
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1587
1839
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
1588
|
-
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)(?:\?.*)?$/;
|
|
1589
1841
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1590
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");
|
|
1591
1843
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1598,6 +1850,7 @@ const virtualEntryFiles = {
|
|
|
1598
1850
|
[ROUTE_DISCOVERY_ID]: "route-discovery.ts",
|
|
1599
1851
|
[CLIENT_ENTRY_ID]: "client-entry.ts",
|
|
1600
1852
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1853
|
+
[RESUME_MODULES_ID]: "resume-modules.ts",
|
|
1601
1854
|
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1602
1855
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1603
1856
|
[FRAGMENT_ENTRY_ID]: "fragment-entry.ts",
|
|
@@ -1621,8 +1874,11 @@ function router(options = {}) {
|
|
|
1621
1874
|
const navigationEntry = resumeEntryState();
|
|
1622
1875
|
const fragmentEntry = resumeEntryState();
|
|
1623
1876
|
const routePreloads = routePreloadState();
|
|
1877
|
+
const serverBuild = serverBuildState();
|
|
1878
|
+
const devNitro = devNitroState();
|
|
1879
|
+
const compressedAssets = compressedAssetsState();
|
|
1624
1880
|
return [
|
|
1625
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry,
|
|
1881
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry, serverBuild, devNitro, compressedAssets),
|
|
1626
1882
|
devSourceModuleRequestPlugin(),
|
|
1627
1883
|
devPageForUrlPlugin(options),
|
|
1628
1884
|
pageServerOnlyPropsPlugin(),
|
|
@@ -1633,6 +1889,8 @@ function router(options = {}) {
|
|
|
1633
1889
|
htmlTransformPlugin(),
|
|
1634
1890
|
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, options, fragmentEntry),
|
|
1635
1891
|
linkBridgeFilesPlugin(navigationEntry, fragmentEntry, options),
|
|
1892
|
+
devNitroClosePlugin(devNitro),
|
|
1893
|
+
brotliCopiesPlugin(compressedAssets),
|
|
1636
1894
|
nitroPlugins
|
|
1637
1895
|
];
|
|
1638
1896
|
}
|
|
@@ -1697,7 +1955,7 @@ function devSourceModuleRequestPlugin() {
|
|
|
1697
1955
|
}
|
|
1698
1956
|
};
|
|
1699
1957
|
}
|
|
1700
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState(), serverBuild = serverBuildState()) {
|
|
1958
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState(), serverBuild = serverBuildState(), devNitro = devNitroState(), compressedAssets = compressedAssetsState()) {
|
|
1701
1959
|
let clientOutDir = "";
|
|
1702
1960
|
let clientEnvironmentName = "";
|
|
1703
1961
|
let clientEnvironmentConfig;
|
|
@@ -1730,12 +1988,10 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1730
1988
|
ssrEnvironment.build.rolldownOptions ??= {};
|
|
1731
1989
|
ssrEnvironment.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, config.root);
|
|
1732
1990
|
const serverWatch = withWatchIgnores(config.server?.watch);
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
server
|
|
1736
|
-
|
|
1737
|
-
watch: serverWatch
|
|
1738
|
-
}
|
|
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
|
|
1739
1995
|
};
|
|
1740
1996
|
},
|
|
1741
1997
|
configResolved(config) {
|
|
@@ -1781,6 +2037,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1781
2037
|
ssr: {},
|
|
1782
2038
|
styles: {}
|
|
1783
2039
|
};
|
|
2040
|
+
routePreloads.resumeModules = {};
|
|
1784
2041
|
routePreloads.persisted = false;
|
|
1785
2042
|
routePreloads.importMap = void 0;
|
|
1786
2043
|
await removeClientAssetsManifest(clientOutDir);
|
|
@@ -1794,6 +2051,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1794
2051
|
fragmentEntry.fileName = manifest.entries.fragment ? clientAssetFileName(manifest.entries.fragment, manifest.base) : void 0;
|
|
1795
2052
|
fragmentEntry.imports = manifest.entries.fragmentImports;
|
|
1796
2053
|
routePreloads.routes = manifest.routes;
|
|
2054
|
+
routePreloads.resumeModules = manifest.resumeModules ?? {};
|
|
1797
2055
|
routePreloads.persisted = true;
|
|
1798
2056
|
routePreloads.importMap = manifest.importMap;
|
|
1799
2057
|
},
|
|
@@ -1806,6 +2064,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1806
2064
|
}
|
|
1807
2065
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1808
2066
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
2067
|
+
const resumeModulesChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_MODULES_ORIGIN));
|
|
1809
2068
|
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1810
2069
|
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1811
2070
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
@@ -1829,6 +2088,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1829
2088
|
navigationPolyfillIds,
|
|
1830
2089
|
prerenderWakeChunk,
|
|
1831
2090
|
resumeChunk,
|
|
2091
|
+
resumeModulesChunk,
|
|
1832
2092
|
root: routePreloads.root
|
|
1833
2093
|
});
|
|
1834
2094
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
@@ -1873,6 +2133,12 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1873
2133
|
...pendingClientAssets,
|
|
1874
2134
|
importMap: JSON.parse(String(importMap.source))
|
|
1875
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
|
+
};
|
|
1876
2142
|
await writeClientAssetsManifest(clientOutDir, pendingClientAssets);
|
|
1877
2143
|
}
|
|
1878
2144
|
};
|
|
@@ -1893,17 +2159,21 @@ function requestFileTransformPlugin() {
|
|
|
1893
2159
|
configResolved(config) {
|
|
1894
2160
|
root = config.root;
|
|
1895
2161
|
},
|
|
1896
|
-
transform
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
+
}
|
|
1904
2173
|
}
|
|
1905
2174
|
};
|
|
1906
2175
|
}
|
|
2176
|
+
const REQUEST_FILE_ID_FILTER = /[\\/](?:api|middleware)[\\/]/;
|
|
1907
2177
|
function requestFileIdForTransform(root, id) {
|
|
1908
2178
|
if (id.startsWith("\0")) return;
|
|
1909
2179
|
const pathname = decodePath(parseURL(id).pathname);
|
|
@@ -1916,18 +2186,21 @@ function requestFileIdForTransform(root, id) {
|
|
|
1916
2186
|
function requestFileBuildTransformPlugin(root) {
|
|
1917
2187
|
return {
|
|
1918
2188
|
name: "markless-router:nitro-request-files",
|
|
1919
|
-
transform
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
+
}
|
|
1927
2200
|
}
|
|
1928
2201
|
};
|
|
1929
2202
|
}
|
|
1930
|
-
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false, serverBuild = serverBuildState()) {
|
|
2203
|
+
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false, serverBuild = serverBuildState(), devNitro = devNitroState(), compressedAssets = compressedAssetsState()) {
|
|
1931
2204
|
const scanDirs = Array.isArray(nitroConfig?.scanDirs) ? nitroConfig.scanDirs.filter((dir) => typeof dir === "string") : [];
|
|
1932
2205
|
const watchOptions = withWatchIgnores(nitroConfig?.watchOptions, serverWatchIgnored);
|
|
1933
2206
|
const publicAssets = Array.isArray(nitroConfig?.publicAssets) ? nitroConfig.publicAssets : [];
|
|
@@ -1950,7 +2223,8 @@ function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isB
|
|
|
1950
2223
|
modules: [
|
|
1951
2224
|
...nitroConfig?.modules ?? [],
|
|
1952
2225
|
fragmentRequestModule(),
|
|
1953
|
-
|
|
2226
|
+
compressedAssetsModule(nitroConfig?.compressPublicAssets !== void 0, compressedAssets),
|
|
2227
|
+
...isBuild ? [prerenderRendererModule(serverBuild, [MARKLESS_BUILD_PREFIX, LINK_BRIDGE_DIR])] : [devNitroModule(devNitro)]
|
|
1954
2228
|
],
|
|
1955
2229
|
...isBuild && { routeRules: {
|
|
1956
2230
|
[`/${MARKLESS_BUILD_PREFIX}**`]: { headers: { "cache-control": "public, max-age=31536000, immutable" } },
|
|
@@ -2014,7 +2288,8 @@ function routePreloadState() {
|
|
|
2014
2288
|
navigation: {},
|
|
2015
2289
|
ssr: {},
|
|
2016
2290
|
styles: {}
|
|
2017
|
-
}
|
|
2291
|
+
},
|
|
2292
|
+
resumeModules: {}
|
|
2018
2293
|
};
|
|
2019
2294
|
}
|
|
2020
2295
|
let prerenderWakeChannelCaptured = false;
|
|
@@ -2023,18 +2298,21 @@ function prerenderWakeChannelEnabled() {
|
|
|
2023
2298
|
}
|
|
2024
2299
|
function routerClientInput(input, root) {
|
|
2025
2300
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
2301
|
+
const resumeModulesId = scopedVirtualEntryId(RESUME_MODULES_ID, root);
|
|
2026
2302
|
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
2027
2303
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
2028
2304
|
const fragmentEntryId = scopedVirtualEntryId(FRAGMENT_ENTRY_ID, root);
|
|
2029
2305
|
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
2030
2306
|
if (input === void 0) return [
|
|
2031
2307
|
resumeEntryId,
|
|
2308
|
+
resumeModulesId,
|
|
2032
2309
|
...wakeEntries,
|
|
2033
2310
|
navigationEntryId,
|
|
2034
2311
|
fragmentEntryId
|
|
2035
2312
|
];
|
|
2036
2313
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
2037
2314
|
resumeEntryId,
|
|
2315
|
+
resumeModulesId,
|
|
2038
2316
|
...wakeEntries,
|
|
2039
2317
|
navigationEntryId,
|
|
2040
2318
|
fragmentEntryId,
|
|
@@ -2043,6 +2321,7 @@ function routerClientInput(input, root) {
|
|
|
2043
2321
|
if (isRecord(input)) return {
|
|
2044
2322
|
...input,
|
|
2045
2323
|
"markless-router-resume": resumeEntryId,
|
|
2324
|
+
"markless-router-resume-modules": resumeModulesId,
|
|
2046
2325
|
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
2047
2326
|
"markless-router-navigation": navigationEntryId,
|
|
2048
2327
|
"markless-router-fragment": fragmentEntryId
|
|
@@ -2054,6 +2333,7 @@ function isVirtualEntryChunk(chunk, origin) {
|
|
|
2054
2333
|
}
|
|
2055
2334
|
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState(), options = {}, fragmentEntry = resumeEntryState()) {
|
|
2056
2335
|
let root = "";
|
|
2336
|
+
let serving = false;
|
|
2057
2337
|
return {
|
|
2058
2338
|
name: "markless-router:routes",
|
|
2059
2339
|
config() {
|
|
@@ -2062,6 +2342,7 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
2062
2342
|
sharedDuringBuild: true,
|
|
2063
2343
|
configResolved(config) {
|
|
2064
2344
|
root = config.root;
|
|
2345
|
+
serving = config.command === "serve";
|
|
2065
2346
|
},
|
|
2066
2347
|
resolveId: {
|
|
2067
2348
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
@@ -2071,27 +2352,31 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
2071
2352
|
id: `\0${baseId}${rootScopeQuery(root, id)}`,
|
|
2072
2353
|
moduleSideEffects: false
|
|
2073
2354
|
};
|
|
2074
|
-
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)}`;
|
|
2075
2356
|
const entryFile = virtualEntryFiles[baseId];
|
|
2076
2357
|
if (!entryFile) return;
|
|
2077
2358
|
if (baseId === ROUTE_HREF_ID) return join(VIRTUAL_ENTRY_DIR, entryFile);
|
|
2078
2359
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
2079
2360
|
}
|
|
2080
2361
|
},
|
|
2081
|
-
load
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
if (
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const
|
|
2093
|
-
this?.
|
|
2094
|
-
|
|
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
|
+
}
|
|
2095
2380
|
}
|
|
2096
2381
|
}
|
|
2097
2382
|
};
|
|
@@ -2188,7 +2473,7 @@ function serverEntrySource(root, options) {
|
|
|
2188
2473
|
`import { prerenderWakeEntryPath } from ${JSON.stringify(`${PRERENDER_WAKE_ENTRY_PATH_ID}${query}`)};`,
|
|
2189
2474
|
`import { navigationEntryPath } from ${JSON.stringify(`${NAVIGATION_ENTRY_PATH_ID}${query}`)};`,
|
|
2190
2475
|
`import { fragmentEntryPath } from ${JSON.stringify(`${FRAGMENT_ENTRY_PATH_ID}${query}`)};`,
|
|
2191
|
-
`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}`)};`,
|
|
2192
2477
|
`import { pageModuleLoaders, routeFileIds } from ${JSON.stringify(`${ROUTE_DISCOVERY_ID}${query}`)};`,
|
|
2193
2478
|
`import { documentNavigation } from ${JSON.stringify(`${ROUTER_OPTIONS_ID}${query}`)};`,
|
|
2194
2479
|
`const documentModuleLoaders = import.meta.glob(['/document.tsrx']);`,
|
|
@@ -2206,6 +2491,7 @@ function serverEntrySource(root, options) {
|
|
|
2206
2491
|
...options.prefetch === false ? [" prefetch: false,"] : [],
|
|
2207
2492
|
` routeModulePreloads,`,
|
|
2208
2493
|
` routeSsrModulePreloads,`,
|
|
2494
|
+
` routeResumeModules,`,
|
|
2209
2495
|
` routeStylesheets,`,
|
|
2210
2496
|
` documentStylesheets,`,
|
|
2211
2497
|
` importMap: documentImportMap,`,
|
|
@@ -2246,7 +2532,8 @@ function routePreloadsSource(state, client) {
|
|
|
2246
2532
|
`export const routeStylesheets = ${state.persisted ? "routePreloadData.styles ?? {}" : "undefined"};`,
|
|
2247
2533
|
`const documentStylesheetsJson = globalThis.__marklessRouterDocumentStylesheetsJson ?? ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)};`,
|
|
2248
2534
|
`export const documentStylesheets = documentStylesheetsJson === ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)} ? [] : JSON.parse(documentStylesheetsJson);`,
|
|
2249
|
-
`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 : {})};`
|
|
2250
2537
|
];
|
|
2251
2538
|
return [
|
|
2252
2539
|
`const routePreloadsJson = globalThis.__marklessRouterRoutePreloadsJson ?? ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)};`,
|
|
@@ -2376,7 +2663,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
2376
2663
|
const ssrFileNames = /* @__PURE__ */ new Set();
|
|
2377
2664
|
if (input.resumeChunk) {
|
|
2378
2665
|
includeChunk(ssrFileNames, landingScope, input.resumeChunk.fileName);
|
|
2379
|
-
for (const
|
|
2666
|
+
for (const table of [input.resumeChunk, input.resumeModulesChunk]) for (const fileName of table ? routeScopedDynamicImports(table, routeFile) : []) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
2380
2667
|
}
|
|
2381
2668
|
if (input.prerenderWakeChunk) {
|
|
2382
2669
|
includeChunk(ssrFileNames, landingScope, input.prerenderWakeChunk.fileName);
|
|
@@ -2406,7 +2693,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
2406
2693
|
for (const entry of lazySiblings.length ? [input.resumeChunk, input.prerenderWakeChunk] : []) {
|
|
2407
2694
|
if (!entry) continue;
|
|
2408
2695
|
includeChunk(firstEventPath, landingScope, entry.fileName);
|
|
2409
|
-
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);
|
|
2410
2697
|
}
|
|
2411
2698
|
for (const fileName of lazySiblings) includeChunk(firstEventPath, landingScope, fileName);
|
|
2412
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));
|
|
@@ -2482,6 +2769,68 @@ function narrowToPlanned(fileNames, planned, scope, entries) {
|
|
|
2482
2769
|
fileNames.clear();
|
|
2483
2770
|
for (const fileName of [...ordered, ...kept]) fileNames.add(fileName);
|
|
2484
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
|
+
}
|
|
2485
2834
|
function routeScopedDynamicImports(chunk, routeFile) {
|
|
2486
2835
|
if (!chunk.code) return [];
|
|
2487
2836
|
const imports = /* @__PURE__ */ new Set();
|