@markless/router 0.4.0 → 0.6.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-DaYssO9x.js +1331 -0
- package/dist/debug-channel-CewnC9fH.js +2 -0
- package/dist/{debug-channel-CSzo-Zj8.js → debug-channel-Di6SppZv.js} +36 -17
- package/dist/index-BjxpxRL7.d.ts +248 -0
- package/dist/index.d.ts +2 -212
- package/dist/index.js +108 -40
- package/dist/protocol-constants-B4WxrZAS.js +26 -0
- package/dist/request-locals-z3rJTizs.js +90 -0
- package/dist/route-href-CGooO_g6.js +18 -0
- package/dist/route-manifest-DMizh42B.js +456 -0
- package/dist/typescript-plugin.cjs +9 -6
- package/dist/typescript-plugin.d.ts +3 -11194
- package/dist/typescript-plugin.js +9 -6
- package/dist/vite/route-href.d.ts +2 -3
- package/dist/vite/route-href.js +1 -17
- package/dist/vite/runtime/create-route-discovery.d.ts +3 -4
- package/dist/vite/runtime/create-route-discovery.js +1 -1
- package/dist/vite/runtime/create-server-entry.d.ts +27 -5
- package/dist/vite/runtime/create-server-entry.js +2 -400
- package/dist/vite/runtime/mdx-route.d.ts +1116 -17
- package/dist/vite/runtime/mdx-route.js +1097 -31
- package/dist/vite.d.ts +10 -6
- package/dist/vite.js +965 -203
- package/package.json +7 -6
- package/src/vite/entries/client-entry.ts +10 -1
- package/src/vite/entries/fragment-entry.ts +797 -0
- package/src/vite/entries/resume-entry.ts +41 -7
- package/dist/request-files-C-0LLSsx.js +0 -232
- package/dist/route-manifest-DPgGIyrS.js +0 -186
package/dist/vite.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { __marklessRouteHref } from "./
|
|
1
|
+
import { c as PREFETCH_ATTRIBUTE, l as REPLACE_ATTRIBUTE, s as LINK_ATTRIBUTE, u as SCROLL_ATTRIBUTE } from "./request-locals-z3rJTizs.js";
|
|
2
|
+
import { l as transformRequestFileSource, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId, r as isPageModuleFile, t as assertNoEndpointConflicts } from "./route-manifest-DMizh42B.js";
|
|
3
|
+
import { t as __marklessRouteHref } from "./route-href-CGooO_g6.js";
|
|
4
|
+
import { r as protocolIslandSegment } from "./protocol-constants-B4WxrZAS.js";
|
|
5
|
+
import { a as runFragmentNavigation, i as FRAGMENT_PATH_PREFIX, n as routerLinkBridgeModules, o as startFragmentNavigation, s as listenForLinkIntent } from "./create-server-entry-DaYssO9x.js";
|
|
4
6
|
import { dirname, extname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
5
7
|
import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutLeadingSlash } from "ufo";
|
|
6
8
|
import { existsSync } from "node:fs";
|
|
7
|
-
import { nitro } from "nitro/vite";
|
|
8
|
-
import { sortUserPlugins } from "vite";
|
|
9
|
-
import { symbolVirtualModuleSourceFile } from "@markless/bundler/preload";
|
|
10
|
-
import { parseModule } from "yuku-tsrx";
|
|
11
9
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
10
|
+
import { nitro } from "nitro/vite";
|
|
11
|
+
import { minifySync, sortUserPlugins } from "vite";
|
|
12
|
+
import { isClientPrimarySourceRequest, isExecutionLogChunk, isMarklessNavigationPack, isRenderDataSourceRequest, isResumeSourceRequest, isRouteNavigationSourceRequest, isSymbolOnlySourceRequest, lazyStartupPackName, marklessDeferredChunkFileNames, optimizedDepsToInclude, plannedLandingFiles, resolverVirtualModuleSourceFile, startupPackOfLazy, symbolVirtualModuleSourceFile, warmEvaluatedImports } from "@markless/bundler/preload";
|
|
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
|
+
import { parseModule } from "@tsrx/yuku";
|
|
12
15
|
import { toHtml } from "hast-util-to-html";
|
|
13
16
|
import { HastReader, createMdxHastHandle, defineHastPlugin, dropHandle, materializeHastTree, resolveHastSubscriptions, serializeHandle, visitHastHandle } from "satteri";
|
|
17
|
+
import "@markless/web/render-to-string";
|
|
14
18
|
//#region src/route-types.ts
|
|
15
19
|
const routeTypesEnvPath = "markless-router-env.d.ts";
|
|
16
20
|
const routeTypesOutputPath = ".output/markless/router/types/routes.d.ts";
|
|
@@ -96,7 +100,7 @@ function createLinkTypes() {
|
|
|
96
100
|
"};",
|
|
97
101
|
"",
|
|
98
102
|
"export type MarklessRouterNavigationProps = {",
|
|
99
|
-
" readonly prefetch?: boolean
|
|
103
|
+
" readonly prefetch?: boolean;",
|
|
100
104
|
" readonly replace?: boolean;",
|
|
101
105
|
" readonly scroll?: boolean;",
|
|
102
106
|
"};",
|
|
@@ -113,7 +117,8 @@ function createLinkTypes() {
|
|
|
113
117
|
}
|
|
114
118
|
//#endregion
|
|
115
119
|
//#region src/vite/route-typegen.ts
|
|
116
|
-
const PAGE_EXTENSIONS = new Set([".tsrx", ".mdx"]);
|
|
120
|
+
const PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsrx", ".mdx"]);
|
|
121
|
+
const ENDPOINT_EXTENSIONS = /* @__PURE__ */ new Set([".ts"]);
|
|
117
122
|
function routeTypegenPlugin() {
|
|
118
123
|
let root = "";
|
|
119
124
|
return {
|
|
@@ -130,27 +135,27 @@ function routeTypegenPlugin() {
|
|
|
130
135
|
};
|
|
131
136
|
}
|
|
132
137
|
async function writeRouteTypes(fs, root) {
|
|
133
|
-
const
|
|
138
|
+
const pageFiles = await discoverPageFiles(fs, root);
|
|
139
|
+
const manifest = buildRouteManifestFromFileIds(pageFiles);
|
|
140
|
+
assertNoEndpointConflicts(manifest, await discoverFiles(fs, root, "api", ENDPOINT_EXTENSIONS));
|
|
134
141
|
await writeIfChanged(fs, join(root, routeTypesEnvPath), routeTypesEnvDeclaration);
|
|
135
142
|
await writeIfChanged(fs, join(root, routeTypesOutputPath), createRouteTypesDeclaration(manifest));
|
|
136
143
|
}
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
function discoverPageFiles(fs, root) {
|
|
145
|
+
return discoverFiles(fs, root, "pages", PAGE_EXTENSIONS);
|
|
146
|
+
}
|
|
147
|
+
async function discoverFiles(fs, root, dir, extensions) {
|
|
139
148
|
const files = [];
|
|
140
|
-
await
|
|
149
|
+
await collectFiles(fs, root, join(root, dir), extensions, files);
|
|
141
150
|
return files.toSorted((left, right) => left.localeCompare(right));
|
|
142
151
|
}
|
|
143
|
-
async function
|
|
152
|
+
async function collectFiles(fs, root, dir, extensions, files) {
|
|
144
153
|
const entries = await readdirIfExists(fs, dir);
|
|
145
|
-
await Promise.all(entries.map((entry) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
await collectPageFiles(fs, root, path, files);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (entry.isFile() && PAGE_EXTENSIONS.has(extname(path))) files.push(withLeadingSlash(relative(root, path)));
|
|
154
|
+
await Promise.all(entries.map(async (entry) => {
|
|
155
|
+
const path = join(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) await collectFiles(fs, root, path, extensions, files);
|
|
157
|
+
else if (entry.isFile() && extensions.has(extname(path))) files.push(withLeadingSlash(relative(root, path)));
|
|
158
|
+
}));
|
|
154
159
|
}
|
|
155
160
|
async function readdirIfExists(fs, dir) {
|
|
156
161
|
try {
|
|
@@ -180,14 +185,20 @@ function isNotFoundError(error) {
|
|
|
180
185
|
//#region src/vite/anchor-transform.ts
|
|
181
186
|
const ROUTE_HREF_HELPER_ID = "virtual:markless-router/route-href";
|
|
182
187
|
const JSX_FILE_FILTER = /\.(?:[jt]sx|tsrx)(?:$|\?)/;
|
|
183
|
-
const
|
|
188
|
+
const REWRITE_CACHE_LIMIT = 2048;
|
|
189
|
+
const ROUTER_IMPORT_SOURCES = /* @__PURE__ */ new Set(["@markless/router", "@markless/core/router"]);
|
|
184
190
|
function anchorTransformPlugin() {
|
|
185
191
|
let root = "";
|
|
186
192
|
let routePatterns = /* @__PURE__ */ new Map();
|
|
187
193
|
let routesLoaded = false;
|
|
194
|
+
let routeFingerprint = "";
|
|
195
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
188
196
|
const refreshRoutes = async (fs) => {
|
|
189
197
|
routePatterns = routePatternMap(buildRouteManifestFromFileIds(await discoverPageFiles(fs, root)));
|
|
190
198
|
routesLoaded = true;
|
|
199
|
+
const fingerprint = JSON.stringify([...routePatterns]);
|
|
200
|
+
if (fingerprint !== routeFingerprint) rewrites.clear();
|
|
201
|
+
routeFingerprint = fingerprint;
|
|
191
202
|
};
|
|
192
203
|
return {
|
|
193
204
|
name: "markless-router:anchors",
|
|
@@ -197,7 +208,18 @@ function anchorTransformPlugin() {
|
|
|
197
208
|
async handler(code, id) {
|
|
198
209
|
if (!isTransformCandidateCode(code)) return;
|
|
199
210
|
if (!routesLoaded) await refreshRoutes(this.fs);
|
|
200
|
-
const
|
|
211
|
+
const key = `${id.includes(".tsrx") ? "tsrx" : id.includes(".jsx") ? "jsx" : "tsx"}\0${code}`;
|
|
212
|
+
let transformed = rewrites.get(key);
|
|
213
|
+
if (transformed === void 0) {
|
|
214
|
+
const ast = parseTransformAst(code, id, (source, options) => this.parse(source, options));
|
|
215
|
+
transformed = transformAnchorSource(code, ast, routePatterns);
|
|
216
|
+
if (id.includes(".tsrx")) {
|
|
217
|
+
const linkedAst = transformed === code ? ast : parseTransformAst(transformed, id, (source, options) => this.parse(source, options));
|
|
218
|
+
transformed = lowerRouterLinks(transformed, linkedAst);
|
|
219
|
+
}
|
|
220
|
+
if (rewrites.size >= REWRITE_CACHE_LIMIT) rewrites.delete(rewrites.keys().next().value);
|
|
221
|
+
rewrites.set(key, transformed);
|
|
222
|
+
}
|
|
201
223
|
return transformed === code ? void 0 : {
|
|
202
224
|
code: transformed,
|
|
203
225
|
map: null
|
|
@@ -215,6 +237,68 @@ function anchorTransformPlugin() {
|
|
|
215
237
|
}
|
|
216
238
|
};
|
|
217
239
|
}
|
|
240
|
+
function lowerRouterLinks(code, ast) {
|
|
241
|
+
const linkNames = marklessRouterLinkImportNames(ast);
|
|
242
|
+
const edits = [];
|
|
243
|
+
walk(ast, (current) => {
|
|
244
|
+
if (current.type !== "JSXElement") return;
|
|
245
|
+
const opening = node$2(current.openingElement);
|
|
246
|
+
const name = opening && node$2(opening.name);
|
|
247
|
+
if (!name || !linkNames.has(jsxName$1(name) ?? "")) return;
|
|
248
|
+
const attributes = nodes$2(opening?.attributes);
|
|
249
|
+
if (attributes.some((attribute) => attribute.type === "JSXSpreadAttribute")) return;
|
|
250
|
+
if (findJsxAttribute(attributes, "children")) return;
|
|
251
|
+
const href = findJsxAttribute(attributes, "href") ? "" : " href=\"#\"";
|
|
252
|
+
edits.push({
|
|
253
|
+
...rangeOf(name),
|
|
254
|
+
text: `a ${LINK_ATTRIBUTE}${href}`
|
|
255
|
+
});
|
|
256
|
+
const closingName = node$2(node$2(current.closingElement)?.name);
|
|
257
|
+
if (closingName) edits.push({
|
|
258
|
+
...rangeOf(closingName),
|
|
259
|
+
text: "a"
|
|
260
|
+
});
|
|
261
|
+
for (const attribute of attributes) {
|
|
262
|
+
const attributeName = jsxName$1(node$2(attribute.name));
|
|
263
|
+
if (attributeName === "params" || attributeName === "prefetch" && !attribute.value) edits.push({
|
|
264
|
+
...attributeRemovalRange(code, attribute),
|
|
265
|
+
text: ""
|
|
266
|
+
});
|
|
267
|
+
else if (attributeName === "prefetch") {
|
|
268
|
+
const value = node$2(attribute.value);
|
|
269
|
+
const literal = jsxExpression(value) ?? value;
|
|
270
|
+
if ([
|
|
271
|
+
"Literal",
|
|
272
|
+
"StringLiteral",
|
|
273
|
+
"BooleanLiteral"
|
|
274
|
+
].includes(literal.type ?? "")) {
|
|
275
|
+
const choice = literal.value;
|
|
276
|
+
edits.push(choice === false ? {
|
|
277
|
+
...rangeOf(attribute),
|
|
278
|
+
text: `${PREFETCH_ATTRIBUTE}="none"`
|
|
279
|
+
} : {
|
|
280
|
+
...attributeRemovalRange(code, attribute),
|
|
281
|
+
text: ""
|
|
282
|
+
});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const expression = `(${slice$1(code, jsxExpression(value) ?? value)})`;
|
|
286
|
+
edits.push({
|
|
287
|
+
...rangeOf(attribute),
|
|
288
|
+
text: `${PREFETCH_ATTRIBUTE}={${expression} === false ? 'none' : undefined}`
|
|
289
|
+
});
|
|
290
|
+
} else if (attributeName === "replace" || attributeName === "scroll") {
|
|
291
|
+
const value = node$2(attribute.value);
|
|
292
|
+
const expression = value ? slice$1(code, jsxExpression(value) ?? value) : "true";
|
|
293
|
+
edits.push({
|
|
294
|
+
...rangeOf(attribute),
|
|
295
|
+
text: attributeName === "replace" ? `${REPLACE_ATTRIBUTE}={(${expression}) ? '' : undefined}` : `${SCROLL_ATTRIBUTE}={(${expression}) === false ? 'manual' : undefined}`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return applyEdits(code, edits);
|
|
301
|
+
}
|
|
218
302
|
function parseTransformAst(code, id, parse) {
|
|
219
303
|
if (id.includes(".tsrx")) return parseModule(code, id);
|
|
220
304
|
return parse(code, {
|
|
@@ -227,24 +311,24 @@ function isTransformCandidateCode(code) {
|
|
|
227
311
|
return code.includes("<a") || [...ROUTER_IMPORT_SOURCES].some((importSource) => code.includes(importSource));
|
|
228
312
|
}
|
|
229
313
|
function transformAnchorSource(code, astInput, routePatterns) {
|
|
230
|
-
const ast = node$
|
|
314
|
+
const ast = node$2(astInput);
|
|
231
315
|
if (!ast) return code;
|
|
232
316
|
const edits = [];
|
|
233
317
|
let needsHrefHelper = false;
|
|
234
318
|
const linkNames = marklessRouterLinkImportNames(ast);
|
|
235
319
|
walk(ast, (current) => {
|
|
236
320
|
if (current.type !== "JSXOpeningElement") return;
|
|
237
|
-
const name = jsxName$1(node$
|
|
321
|
+
const name = jsxName$1(node$2(current.name));
|
|
238
322
|
if (name !== "a" && (!name || !linkNames.has(name))) return;
|
|
239
|
-
const attributes = nodes$
|
|
323
|
+
const attributes = nodes$2(current.attributes);
|
|
240
324
|
const href = findJsxAttribute(attributes, "href");
|
|
241
|
-
const hrefValue = href ? stringLiteralValue(node$
|
|
325
|
+
const hrefValue = href ? stringLiteralValue(node$2(href.value)) : void 0;
|
|
242
326
|
if (!href || !hrefValue || !isRoutePatternHref(hrefValue)) return;
|
|
243
327
|
const routeParams = routePatterns.get(hrefValue);
|
|
244
328
|
if (!routeParams) fail$1(`Typed route error: ${hrefValue} does not match any route in pages/.`);
|
|
245
329
|
const params = findJsxAttribute(attributes, "params");
|
|
246
330
|
if (!params) fail$1(missingParamsMessage(hrefValue, routeParams));
|
|
247
|
-
const paramsExpression = jsxExpression(node$
|
|
331
|
+
const paramsExpression = jsxExpression(node$2(params.value));
|
|
248
332
|
if (!paramsExpression) fail$1(missingParamsMessage(hrefValue, routeParams));
|
|
249
333
|
validateObjectLiteralParams(hrefValue, routeParams, paramsExpression);
|
|
250
334
|
const staticParams = staticObjectValue(paramsExpression);
|
|
@@ -270,11 +354,11 @@ function transformAnchorSource(code, astInput, routePatterns) {
|
|
|
270
354
|
function staticObjectValue(expression) {
|
|
271
355
|
if (expression.type !== "ObjectExpression") return void 0;
|
|
272
356
|
const value = {};
|
|
273
|
-
for (const property of nodes$
|
|
357
|
+
for (const property of nodes$2(expression.properties)) {
|
|
274
358
|
if (property.type !== "Property" || property.computed === true) return void 0;
|
|
275
|
-
const key = node$
|
|
359
|
+
const key = node$2(property.key);
|
|
276
360
|
const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
|
|
277
|
-
const propertyValue = staticExpressionValue(node$
|
|
361
|
+
const propertyValue = staticExpressionValue(node$2(property.value));
|
|
278
362
|
if (!name || !propertyValue.known) return void 0;
|
|
279
363
|
value[name] = propertyValue.value;
|
|
280
364
|
}
|
|
@@ -288,7 +372,7 @@ function staticExpressionValue(expression) {
|
|
|
288
372
|
};
|
|
289
373
|
if (expression.type === "ArrayExpression") {
|
|
290
374
|
const values = [];
|
|
291
|
-
for (const element of nodes$
|
|
375
|
+
for (const element of nodes$2(expression.elements)) {
|
|
292
376
|
const value = staticExpressionValue(element);
|
|
293
377
|
if (!value.known) return { known: false };
|
|
294
378
|
values.push(value.value);
|
|
@@ -303,9 +387,9 @@ function staticExpressionValue(expression) {
|
|
|
303
387
|
function marklessRouterLinkImportNames(ast) {
|
|
304
388
|
const names = /* @__PURE__ */ new Set();
|
|
305
389
|
walk(ast, (current) => {
|
|
306
|
-
if (current.type !== "ImportDeclaration" || !ROUTER_IMPORT_SOURCES.has(literalString(node$
|
|
307
|
-
for (const specifier of nodes$
|
|
308
|
-
const localName = identifierName(node$
|
|
390
|
+
if (current.type !== "ImportDeclaration" || !ROUTER_IMPORT_SOURCES.has(literalString(node$2(current.source)) ?? "")) return;
|
|
391
|
+
for (const specifier of nodes$2(current.specifiers)) if (specifier.type === "ImportSpecifier" && identifierName(node$2(specifier.imported)) === "Link") {
|
|
392
|
+
const localName = identifierName(node$2(specifier.local));
|
|
309
393
|
if (localName) names.add(localName);
|
|
310
394
|
}
|
|
311
395
|
});
|
|
@@ -324,9 +408,9 @@ function validateObjectLiteralParams(pattern, routeParams, paramsExpression) {
|
|
|
324
408
|
if (missing.length > 0) fail$1(`Typed route error: ${pattern} requires params:\n${list(missing)}`);
|
|
325
409
|
}
|
|
326
410
|
function objectLiteralKeys(expression) {
|
|
327
|
-
return nodes$
|
|
411
|
+
return nodes$2(expression.properties).flatMap((property) => {
|
|
328
412
|
if (property.type !== "Property") return [];
|
|
329
|
-
const key = node$
|
|
413
|
+
const key = node$2(property.key);
|
|
330
414
|
const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
|
|
331
415
|
return name ? [name] : [];
|
|
332
416
|
});
|
|
@@ -341,7 +425,7 @@ function list(items) {
|
|
|
341
425
|
return items.map((item) => `- ${item}`).join("\n");
|
|
342
426
|
}
|
|
343
427
|
function findJsxAttribute(attributes, name) {
|
|
344
|
-
return attributes.find((attr) => attr.type === "JSXAttribute" && jsxName$1(node$
|
|
428
|
+
return attributes.find((attr) => attr.type === "JSXAttribute" && jsxName$1(node$2(attr.name)) === name);
|
|
345
429
|
}
|
|
346
430
|
function stringLiteralValue(value) {
|
|
347
431
|
return value?.type === "Literal" ? string$1(value.value) : void 0;
|
|
@@ -354,7 +438,7 @@ function identifierName(value) {
|
|
|
354
438
|
}
|
|
355
439
|
function jsxExpression(value) {
|
|
356
440
|
if (value?.type !== "JSXExpressionContainer") return;
|
|
357
|
-
const expression = node$
|
|
441
|
+
const expression = node$2(value.expression);
|
|
358
442
|
return expression?.type === "JSXEmptyExpression" ? void 0 : expression;
|
|
359
443
|
}
|
|
360
444
|
function isRoutePatternHref(href) {
|
|
@@ -377,7 +461,7 @@ function applyEdits(code, edits) {
|
|
|
377
461
|
function walk(root, visit) {
|
|
378
462
|
const stack = [root];
|
|
379
463
|
while (stack.length > 0) {
|
|
380
|
-
const current = node$
|
|
464
|
+
const current = node$2(stack.pop());
|
|
381
465
|
if (!current) continue;
|
|
382
466
|
visit(current);
|
|
383
467
|
for (const child of Object.values(current)) if (Array.isArray(child)) stack.push(...child);
|
|
@@ -402,10 +486,10 @@ function rangeOf(value) {
|
|
|
402
486
|
end: value.end
|
|
403
487
|
};
|
|
404
488
|
}
|
|
405
|
-
function nodes$
|
|
406
|
-
return Array.isArray(value) ? value.filter((item) => !!node$
|
|
489
|
+
function nodes$2(value) {
|
|
490
|
+
return Array.isArray(value) ? value.filter((item) => !!node$2(item)) : [];
|
|
407
491
|
}
|
|
408
|
-
function node$
|
|
492
|
+
function node$2(value) {
|
|
409
493
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
410
494
|
}
|
|
411
495
|
function string$1(value) {
|
|
@@ -416,7 +500,7 @@ function fail$1(message) {
|
|
|
416
500
|
}
|
|
417
501
|
//#endregion
|
|
418
502
|
//#region src/vite/client-assets-manifest.ts
|
|
419
|
-
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST = "
|
|
503
|
+
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST = "router-client-manifest.json";
|
|
420
504
|
const MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST_VERSION = 1;
|
|
421
505
|
const VALID_ROUTE_FILE = /^pages\/.+\.(?:tsrx|mdx)$/;
|
|
422
506
|
const ABSOLUTE_OR_PROTOCOL_RELATIVE_URL = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
|
|
@@ -427,7 +511,9 @@ function createClientAssetsManifest(input) {
|
|
|
427
511
|
entries: {
|
|
428
512
|
resume: input.resumeEntry,
|
|
429
513
|
...input.prerenderWakeEntry ? { prerenderWake: input.prerenderWakeEntry } : {},
|
|
430
|
-
navigation: input.navigationEntry
|
|
514
|
+
navigation: input.navigationEntry,
|
|
515
|
+
...input.fragmentEntry ? { fragment: input.fragmentEntry } : {},
|
|
516
|
+
...input.fragmentImports?.length ? { fragmentImports: input.fragmentImports } : {}
|
|
431
517
|
},
|
|
432
518
|
routes: {
|
|
433
519
|
navigation: sortRouteMap(input.routes.navigation),
|
|
@@ -436,6 +522,16 @@ function createClientAssetsManifest(input) {
|
|
|
436
522
|
}
|
|
437
523
|
};
|
|
438
524
|
}
|
|
525
|
+
function renameClientAssetChunks(manifest, renames) {
|
|
526
|
+
const urls = new Map([...renames].map(([previous, next]) => [joinURL(manifest.base, previous), joinURL(manifest.base, next)]));
|
|
527
|
+
return renameUrls(manifest, (url) => urls.get(url) ?? url);
|
|
528
|
+
}
|
|
529
|
+
function renameUrls(value, rename) {
|
|
530
|
+
if (typeof value === "string") return rename(value);
|
|
531
|
+
if (Array.isArray(value)) return value.map((item) => renameUrls(item, rename));
|
|
532
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, key === "base" ? item : renameUrls(item, rename)]));
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
439
535
|
async function removeClientAssetsManifest(clientOutDir) {
|
|
440
536
|
const path = clientAssetsManifestPath(clientOutDir);
|
|
441
537
|
await Promise.all([rm(path, { force: true }), rm(clientAssetsManifestTemporaryPath(clientOutDir), { force: true })]);
|
|
@@ -489,8 +585,9 @@ function clientAssetFileName(href, base) {
|
|
|
489
585
|
if (href !== joinURL(base, fileName)) throw clientAssetsManifestError(`contains a non-canonical asset URL ${JSON.stringify(href)} for base ${JSON.stringify(base)}`);
|
|
490
586
|
return fileName;
|
|
491
587
|
}
|
|
588
|
+
/** Where the client build leaves this manifest for the server build: outside the served client output. */
|
|
492
589
|
function clientAssetsManifestPath(clientOutDir) {
|
|
493
|
-
return join(clientOutDir, MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST);
|
|
590
|
+
return join(marklessBuildManifestDir(clientOutDir), MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST);
|
|
494
591
|
}
|
|
495
592
|
function clientAssetsManifestTemporaryPath(clientOutDir) {
|
|
496
593
|
return `${clientAssetsManifestPath(clientOutDir)}.tmp`;
|
|
@@ -504,7 +601,9 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
504
601
|
const entries = {
|
|
505
602
|
resume: requiredString(input.entries.resume, "entries.resume"),
|
|
506
603
|
...input.entries.prerenderWake !== void 0 ? { prerenderWake: requiredString(input.entries.prerenderWake, "entries.prerenderWake") } : {},
|
|
507
|
-
navigation: requiredString(input.entries.navigation, "entries.navigation")
|
|
604
|
+
navigation: requiredString(input.entries.navigation, "entries.navigation"),
|
|
605
|
+
...input.entries.fragment !== void 0 ? { fragment: requiredString(input.entries.fragment, "entries.fragment") } : {},
|
|
606
|
+
...input.entries.fragmentImports !== void 0 ? { fragmentImports: requiredStrings(input.entries.fragmentImports, "entries.fragmentImports") } : {}
|
|
508
607
|
};
|
|
509
608
|
const routes = {
|
|
510
609
|
navigation: routeMap(input.routes.navigation, "routes.navigation"),
|
|
@@ -512,16 +611,21 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
512
611
|
styles: routeMap(input.routes.styles, "routes.styles")
|
|
513
612
|
};
|
|
514
613
|
assertMatchingRouteKeys(routes);
|
|
515
|
-
const
|
|
614
|
+
const importMap = input.importMap === void 0 ? void 0 : importMapImports(input.importMap);
|
|
615
|
+
const hrefs = /* @__PURE__ */ new Set([
|
|
516
616
|
entries.resume,
|
|
517
617
|
...entries.prerenderWake !== void 0 ? [entries.prerenderWake] : [],
|
|
518
618
|
entries.navigation,
|
|
619
|
+
...entries.fragment !== void 0 ? [entries.fragment] : [],
|
|
620
|
+
...entries.fragmentImports ?? [],
|
|
519
621
|
...Object.values(routes.navigation).flat(),
|
|
520
622
|
...Object.values(routes.ssr).flat(),
|
|
521
|
-
...Object.values(routes.styles).flat()
|
|
623
|
+
...Object.values(routes.styles).flat(),
|
|
624
|
+
...Object.values(importMap?.imports ?? {})
|
|
522
625
|
]);
|
|
523
626
|
for (const href of hrefs) {
|
|
524
|
-
const
|
|
627
|
+
const fileName = clientAssetFileName(href, base);
|
|
628
|
+
const assetPath = join(clientOutDir, fileName);
|
|
525
629
|
const relativeAssetPath = relative(clientOutDir, assetPath);
|
|
526
630
|
if (relativeAssetPath === ".." || relativeAssetPath.startsWith("../") || isAbsolute(relativeAssetPath)) throw clientAssetsManifestError(`contains an unsafe asset path ${JSON.stringify(href)}`);
|
|
527
631
|
try {
|
|
@@ -534,9 +638,14 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
534
638
|
version: MARKLESS_ROUTER_CLIENT_ASSETS_MANIFEST_VERSION,
|
|
535
639
|
base,
|
|
536
640
|
entries,
|
|
537
|
-
routes
|
|
641
|
+
routes,
|
|
642
|
+
...importMap ? { importMap } : {}
|
|
538
643
|
};
|
|
539
644
|
}
|
|
645
|
+
function requiredStrings(input, label) {
|
|
646
|
+
if (!Array.isArray(input)) throw clientAssetsManifestError(`has invalid ${label}`);
|
|
647
|
+
return input.map((item, index) => requiredString(item, `${label}[${index}]`));
|
|
648
|
+
}
|
|
540
649
|
function routeMap(input, label) {
|
|
541
650
|
if (!isRecord$1(input)) throw clientAssetsManifestError(`has invalid ${label}`);
|
|
542
651
|
const routes = {};
|
|
@@ -580,10 +689,15 @@ function isRecord$1(input) {
|
|
|
580
689
|
function clientAssetsManifestError(message, cause) {
|
|
581
690
|
return new Error(`Markless Router client-assets manifest ${message}.`, { cause });
|
|
582
691
|
}
|
|
692
|
+
function importMapImports(input) {
|
|
693
|
+
const imports = isRecord$1(input) ? input.imports : void 0;
|
|
694
|
+
if (!isRecord$1(imports) || !Object.values(imports).every((href) => typeof href === "string")) throw clientAssetsManifestError("has an invalid import map");
|
|
695
|
+
return { imports };
|
|
696
|
+
}
|
|
583
697
|
//#endregion
|
|
584
698
|
//#region src/vite/html-transform.ts
|
|
585
699
|
const HELPER = "__marklessRouterHtmlAttributes";
|
|
586
|
-
const SAFE_GLOBALS = new Set([
|
|
700
|
+
const SAFE_GLOBALS = /* @__PURE__ */ new Set([
|
|
587
701
|
"String",
|
|
588
702
|
"Number",
|
|
589
703
|
"Boolean",
|
|
@@ -609,41 +723,42 @@ function htmlTransformPlugin() {
|
|
|
609
723
|
};
|
|
610
724
|
}
|
|
611
725
|
function transformHtmlSource(code, astInput) {
|
|
612
|
-
const ast = node(astInput);
|
|
726
|
+
const ast = node$1(astInput);
|
|
613
727
|
if (!ast) fail(`MarklessRouter could not read ${DOCUMENT_FILE_LABEL}. Check it for syntax errors.`);
|
|
614
728
|
const fn = findDefaultDocumentFunction(ast);
|
|
615
729
|
if (fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression") fail(`MarklessRouter needs to see your document body in ${DOCUMENT_FILE_LABEL}. Write \`export default (props) => <Html>...</Html>\` instead of passing a named function.`);
|
|
616
|
-
const props = first(nodes(fn.params));
|
|
730
|
+
const props = first(nodes$1(fn.params));
|
|
617
731
|
const propsName = props?.type === "Identifier" ? string(props.name) : void 0;
|
|
618
732
|
const propsSource = props ? slice(code, props) : "";
|
|
619
|
-
const attrs = nodes(node(findReturnedHtml(fn).openingElement)?.attributes).map((attr) => htmlAttr(code, attr, propsName));
|
|
620
|
-
const
|
|
733
|
+
const attrs = nodes$1(node$1(findReturnedHtml(fn).openingElement)?.attributes).map((attr) => htmlAttr(code, attr, propsName));
|
|
734
|
+
const body = attrs.length ? `{\n${attrs.map(([name, value]) => ` ${JSON.stringify(name)}: ${value}`).join(",\n")}\n }` : "{}";
|
|
735
|
+
const helper = `export function ${HELPER}(${propsSource}) {\n return ${body};\n}\n`;
|
|
621
736
|
return code.endsWith("\n") ? `${code}\n${helper}` : `${code}\n\n${helper}`;
|
|
622
737
|
}
|
|
623
738
|
function findDefaultDocumentFunction(ast) {
|
|
624
|
-
for (const statement of nodes(ast.body)) {
|
|
625
|
-
const declaration = node(statement.declaration);
|
|
739
|
+
for (const statement of nodes$1(ast.body)) {
|
|
740
|
+
const declaration = node$1(statement.declaration);
|
|
626
741
|
if (statement.type !== "ExportDefaultDeclaration") continue;
|
|
627
742
|
if (declaration?.type === "ArrowFunctionExpression" || declaration?.type === "FunctionExpression") return declaration;
|
|
628
743
|
}
|
|
629
744
|
fail(`MarklessRouter could not find the document function. ${DOCUMENT_FILE_LABEL} must default export \`() => <Html>...</Html>\`.`);
|
|
630
745
|
}
|
|
631
746
|
function findReturnedHtml(fn) {
|
|
632
|
-
const body = unwrap(node(fn.body));
|
|
633
|
-
const returned = body?.type === "BlockStatement" ? unwrap(node(nodes(body.body).find((item) => item.type === "ReturnStatement")?.argument)) : body;
|
|
747
|
+
const body = unwrap(node$1(fn.body));
|
|
748
|
+
const returned = body?.type === "BlockStatement" ? unwrap(node$1(nodes$1(body.body).find((item) => item.type === "ReturnStatement")?.argument)) : body;
|
|
634
749
|
if (returned?.type !== "JSXElement") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Wrap your document shell in \`<Html>...</Html>\`.`);
|
|
635
|
-
if (jsxName(node(node(returned.openingElement)?.name)) !== "Html") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Put <head> and <body> inside \`<Html>...</Html>\`.`);
|
|
750
|
+
if (jsxName(node$1(node$1(returned.openingElement)?.name)) !== "Html") fail(`MarklessRouter expected ${DOCUMENT_FILE_LABEL} to return <Html> at the top level. Put <head> and <body> inside \`<Html>...</Html>\`.`);
|
|
636
751
|
return returned;
|
|
637
752
|
}
|
|
638
753
|
function htmlAttr(code, attr, propsName) {
|
|
639
754
|
if (attr.type === "JSXSpreadAttribute") fail("MarklessRouter does not support spreading props onto <Html> yet. Write each html attribute directly, like `<Html lang=\"en\">`.");
|
|
640
|
-
const name = jsxName(node(attr.name));
|
|
755
|
+
const name = jsxName(node$1(attr.name));
|
|
641
756
|
if (!name) fail("MarklessRouter found an unsupported <Html> attribute name. Use plain html attributes like `lang`, `class`, or `data-theme`.");
|
|
642
|
-
const value = node(attr.value);
|
|
757
|
+
const value = node$1(attr.value);
|
|
643
758
|
if (!value) return [name, "true"];
|
|
644
759
|
if (value.type === "Literal") return [name, JSON.stringify(value.value)];
|
|
645
760
|
if (value.type !== "JSXExpressionContainer") fail(`MarklessRouter could not understand the <Html> "${name}" attribute. Use a string or JSX expression, like \`${name}="value"\` or \`${name}={props.url.pathname}\`.`);
|
|
646
|
-
const expression = unwrap(node(value.expression));
|
|
761
|
+
const expression = unwrap(node$1(value.expression));
|
|
647
762
|
if (!expression || expression.type === "JSXEmptyExpression") fail(`MarklessRouter found an empty <Html> "${name}" attribute expression. Add a value or remove the attribute.`);
|
|
648
763
|
const ref = unsupportedIdentifier(expression, propsName);
|
|
649
764
|
if (ref) fail(`MarklessRouter cannot use "${ref}" in <Html ${name}={...}> because html attributes are read before Markless renders ${DOCUMENT_FILE_LABEL}. Use props directly, like \`${name}={props.url.pathname}\`, or a literal value.`);
|
|
@@ -652,7 +767,7 @@ function htmlAttr(code, attr, propsName) {
|
|
|
652
767
|
function unsupportedIdentifier(root, propsName) {
|
|
653
768
|
const stack = [root];
|
|
654
769
|
while (stack.length > 0) {
|
|
655
|
-
const current = node(stack.pop());
|
|
770
|
+
const current = node$1(stack.pop());
|
|
656
771
|
if (!current) continue;
|
|
657
772
|
if (current.type === "Identifier") {
|
|
658
773
|
const name = string(current.name);
|
|
@@ -678,7 +793,7 @@ function jsxName(name) {
|
|
|
678
793
|
}
|
|
679
794
|
function unwrap(value) {
|
|
680
795
|
let current = value;
|
|
681
|
-
while (current?.type === "ParenthesizedExpression" || current?.type === "TSAsExpression" || current?.type === "TSNonNullExpression") current = node(current.expression);
|
|
796
|
+
while (current?.type === "ParenthesizedExpression" || current?.type === "TSAsExpression" || current?.type === "TSNonNullExpression") current = node$1(current.expression);
|
|
682
797
|
return current;
|
|
683
798
|
}
|
|
684
799
|
function slice(code, value) {
|
|
@@ -686,10 +801,10 @@ function slice(code, value) {
|
|
|
686
801
|
if (range) return code.slice(range[0], range[1]);
|
|
687
802
|
return code.slice(value.start, value.end);
|
|
688
803
|
}
|
|
689
|
-
function nodes(value) {
|
|
690
|
-
return Array.isArray(value) ? value.filter((item) => !!node(item)) : [];
|
|
804
|
+
function nodes$1(value) {
|
|
805
|
+
return Array.isArray(value) ? value.filter((item) => !!node$1(item)) : [];
|
|
691
806
|
}
|
|
692
|
-
function node(value) {
|
|
807
|
+
function node$1(value) {
|
|
693
808
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
694
809
|
}
|
|
695
810
|
function first(items) {
|
|
@@ -702,11 +817,72 @@ function fail(message) {
|
|
|
702
817
|
throw new Error(message);
|
|
703
818
|
}
|
|
704
819
|
//#endregion
|
|
820
|
+
//#region ../bundler/src/scalar-plan-source.ts
|
|
821
|
+
const MARKLESS_SCALAR_PLAN_SOURCE_QUERY = "markless-scalar-plans";
|
|
822
|
+
function scalarPlanSourceReference(source) {
|
|
823
|
+
return withQuery(source, {
|
|
824
|
+
"markless-symbols": null,
|
|
825
|
+
[MARKLESS_SCALAR_PLAN_SOURCE_QUERY]: null
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* The same one line for the live roster, gated on the same fact the reconcile
|
|
830
|
+
* plane is gated on: a payload with no computed nodes can hold no roster
|
|
831
|
+
* derivation, so it never names the module and never emits its chunk.
|
|
832
|
+
*
|
|
833
|
+
* Both call sites (the start-up wiring and a sync computed's re-derive) sit in
|
|
834
|
+
* modules every resumed page runs, which is why neither may write the specifier.
|
|
835
|
+
* A composed MDX route writes it too: it cannot see its islands' computeds.
|
|
836
|
+
*/
|
|
837
|
+
function emitRosterResumeLoaderInstall() {
|
|
838
|
+
return "globalThis.__marklessRosterResume ??= () => import('@markless/web/fns/roster-resume');";
|
|
839
|
+
}
|
|
840
|
+
function emitQueuedResumeContainerEvent(source, { censusRepair = true } = {}) {
|
|
841
|
+
const implementation = source.replace("export async function resumeContainerEvent", "async function marklessResumeContainerEvent");
|
|
842
|
+
if (implementation === source) throw new Error("MARKLESS_RESUME_DISPATCH_EXPORT_MISSING");
|
|
843
|
+
return [
|
|
844
|
+
implementation,
|
|
845
|
+
"export function resumeContainerEvent(input) {",
|
|
846
|
+
" const root = input.root;",
|
|
847
|
+
censusRepair ? " if (root.__marklessShifted) return import('@markless/web/fns/served-census-repair').then((census) => (census.marklessRepairCensus(root), resumeContainerEvent(input)));" : "",
|
|
848
|
+
" if (!root.__marklessDispatch) {",
|
|
849
|
+
" let handler = marklessResumeContainerEvent, tail = Promise.resolve(), done = tail, lastEvent = null, lastEventTime = undefined, seen = new WeakMap();",
|
|
850
|
+
" root.__marklessRegisterDispatch = next => {",
|
|
851
|
+
" const fallback = handler;",
|
|
852
|
+
" handler = input => next(input, fallback);",
|
|
853
|
+
" };",
|
|
854
|
+
" root.__marklessDispatch = next => {",
|
|
855
|
+
" const event = next.event, eventTime = event?.timeStamp;",
|
|
856
|
+
" if (event && next.eventRecord && next.element) event.__marklessPressHost ??= next.element;",
|
|
857
|
+
" if ((event === lastEvent && eventTime === lastEventTime) || (seen.has(event) && seen.get(event) === eventTime)) return done;",
|
|
858
|
+
" if (Object(event) === event) seen.set(event, eventTime);",
|
|
859
|
+
" lastEvent = event;",
|
|
860
|
+
" lastEventTime = eventTime;",
|
|
861
|
+
" let release;",
|
|
862
|
+
" const started = new Promise(resolve => release = resolve);",
|
|
863
|
+
" const run = () => (root.__marklessReleaseDispatch = release, handler(next));",
|
|
864
|
+
" done = tail.then(run, run);",
|
|
865
|
+
" done.then(release, release);",
|
|
866
|
+
" tail = started;",
|
|
867
|
+
" return done;",
|
|
868
|
+
" };",
|
|
869
|
+
" }",
|
|
870
|
+
" return root.__marklessDispatch(input);",
|
|
871
|
+
"}"
|
|
872
|
+
].join("\n");
|
|
873
|
+
}
|
|
874
|
+
//#endregion
|
|
705
875
|
//#region src/vite/mdx.ts
|
|
876
|
+
const MDX_ROUTE_RUNTIME_SPECIFIER = "@markless/router/vite/runtime/mdx-route";
|
|
706
877
|
function mdxTransformPlugin() {
|
|
707
878
|
return {
|
|
708
879
|
name: "markless-router:mdx",
|
|
709
880
|
enforce: "pre",
|
|
881
|
+
config(config, env) {
|
|
882
|
+
if (env.command !== "serve") return void 0;
|
|
883
|
+
const include = optimizedDepsToInclude([MDX_ROUTE_RUNTIME_SPECIFIER], config);
|
|
884
|
+
return include.length > 0 ? { optimizeDeps: { include } } : void 0;
|
|
885
|
+
},
|
|
710
886
|
transform: {
|
|
711
887
|
order: "pre",
|
|
712
888
|
async handler(code, id) {
|
|
@@ -723,7 +899,7 @@ async function transformMdxRoute(source, id) {
|
|
|
723
899
|
const route = parseMdxRoute(source, id);
|
|
724
900
|
const html = route.parts.map((part) => part.kind === "html" ? part.html : "").join("");
|
|
725
901
|
if (route.components.length === 0) return [
|
|
726
|
-
`import { createMdxRenderDataSurface } from '
|
|
902
|
+
`import { createMdxRenderDataSurface } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
727
903
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
728
904
|
"const marklessMdxRenderData = createMdxRenderDataSurface(marklessMdxParts, []);",
|
|
729
905
|
"const marklessMdxPage = {",
|
|
@@ -736,24 +912,52 @@ async function transformMdxRoute(source, id) {
|
|
|
736
912
|
"export default marklessMdxPage;",
|
|
737
913
|
""
|
|
738
914
|
].join("\n");
|
|
915
|
+
const query = new URLSearchParams(parsePath(id).search);
|
|
916
|
+
if (query.has("markless-route") || query.has("markless-resume")) return emitMdxClientRoute(route, id, query.has("markless-resume"));
|
|
739
917
|
return emitComposedMdxRoute(route, id);
|
|
740
918
|
}
|
|
919
|
+
function emitMdxClientRoute(route, id, resume) {
|
|
920
|
+
return [
|
|
921
|
+
`import { createMdxRenderDataSurface, loadMdxSymbol${resume ? ", tryResumeMdxScalar" : ""} } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
922
|
+
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
923
|
+
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id, route.parts)};`,
|
|
924
|
+
renderOverlayLoader(),
|
|
925
|
+
emitRosterResumeLoaderInstall(),
|
|
926
|
+
"const marklessMdxPage = {",
|
|
927
|
+
" renderData: marklessMdxRenderData,",
|
|
928
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
929
|
+
"};",
|
|
930
|
+
"export default marklessMdxPage;",
|
|
931
|
+
...resume ? renderMdxResumeHandler() : [],
|
|
932
|
+
"function marklessMdxLoadSymbol(symbolId, children = []) {",
|
|
933
|
+
" return loadMdxSymbol(symbolId, children, marklessMdxSymbolLoaders);",
|
|
934
|
+
"}",
|
|
935
|
+
""
|
|
936
|
+
].join("\n");
|
|
937
|
+
}
|
|
938
|
+
function renderOverlayLoader() {
|
|
939
|
+
return "globalThis.__marklessOverlay ??= (root) => root.querySelector('[overlay]') ? import('@markless/web/fns/overlay').then((m) => m.installOverlayBehavior(root)) : undefined;";
|
|
940
|
+
}
|
|
741
941
|
function emitComposedMdxRoute(route, id) {
|
|
742
942
|
return [
|
|
743
|
-
`import {
|
|
744
|
-
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild } from '@markless/router/vite/runtime/mdx-route';`,
|
|
943
|
+
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild, tryResumeMdxScalar } from '${MDX_ROUTE_RUNTIME_SPECIFIER}';`,
|
|
745
944
|
...route.imports,
|
|
746
945
|
"",
|
|
747
946
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
748
947
|
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
749
948
|
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id)};`,
|
|
750
949
|
`const marklessMdxStorageSeeds = ${renderStorageSeeds(route.components)};`,
|
|
950
|
+
`const marklessMdxHeadInjections = ${renderHeadInjections(route.components)};`,
|
|
951
|
+
"",
|
|
952
|
+
renderOverlayLoader(),
|
|
953
|
+
emitRosterResumeLoaderInstall(),
|
|
751
954
|
"",
|
|
752
955
|
"const marklessMdxPage = {",
|
|
753
956
|
" renderData: marklessMdxRenderData,",
|
|
754
957
|
" storageSeeds: marklessMdxStorageSeeds,",
|
|
958
|
+
" headInjections: marklessMdxHeadInjections,",
|
|
755
959
|
" loadSymbol: marklessMdxLoadSymbol,",
|
|
756
|
-
" async renderSsr(props = {}) {",
|
|
960
|
+
" async renderSsr(props = {}, marklessMdxRenderContext) {",
|
|
757
961
|
" const marklessMdxChildren = [];",
|
|
758
962
|
` const html = ${renderHtmlExpression(route)};`,
|
|
759
963
|
" const state = composeMdxState(marklessMdxChildren);",
|
|
@@ -764,15 +968,7 @@ function emitComposedMdxRoute(route, id) {
|
|
|
764
968
|
"};",
|
|
765
969
|
"export default marklessMdxPage;",
|
|
766
970
|
"",
|
|
767
|
-
|
|
768
|
-
" input.root.__asyncResumeRuntimeStarted = true;",
|
|
769
|
-
" const { runtime } = await resumeFromPayloadDocument({",
|
|
770
|
-
" document: input.root,",
|
|
771
|
-
" root: input.root,",
|
|
772
|
-
" loadSymbol: marklessMdxLoadSymbol,",
|
|
773
|
-
" });",
|
|
774
|
-
" await runtime.dispatch(input.event, { syncPolicyAlreadyApplied: true });",
|
|
775
|
-
"}",
|
|
971
|
+
...renderMdxResumeHandler(),
|
|
776
972
|
"",
|
|
777
973
|
"function marklessMdxLoadSymbol(symbolId, children = []) {",
|
|
778
974
|
" return loadMdxSymbol(symbolId, children, marklessMdxSymbolLoaders);",
|
|
@@ -780,6 +976,32 @@ function emitComposedMdxRoute(route, id) {
|
|
|
780
976
|
""
|
|
781
977
|
].join("\n");
|
|
782
978
|
}
|
|
979
|
+
function renderMdxResumeHandler() {
|
|
980
|
+
return [
|
|
981
|
+
"let marklessResumeModule;",
|
|
982
|
+
emitQueuedResumeContainerEvent([
|
|
983
|
+
"export async function resumeContainerEvent(input) {",
|
|
984
|
+
" if (await tryResumeMdxScalar(input, marklessMdxLoadScalarPlan, marklessMdxLoadSymbol)) return;",
|
|
985
|
+
" input.root.__asyncResumeRuntimeStarted = true;",
|
|
986
|
+
" const { resumeFromPayloadDocument } = await (marklessResumeModule ??= import('@markless/core/web/resume').catch(error => { marklessResumeModule = undefined; throw error; }));",
|
|
987
|
+
" const { runtime } = await resumeFromPayloadDocument({",
|
|
988
|
+
" document: input.root,",
|
|
989
|
+
" root: input.root,",
|
|
990
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
991
|
+
" handoffDispatchOptions: marklessMdxDispatchOptions,",
|
|
992
|
+
" renderData: marklessMdxRenderData,",
|
|
993
|
+
" });",
|
|
994
|
+
" await runtime.dispatch(input.event, marklessMdxDispatchOptions(input));",
|
|
995
|
+
"}",
|
|
996
|
+
"function marklessMdxDispatchOptions(input) {",
|
|
997
|
+
" return { syncPolicyAlreadyApplied: true, propagationStopped: input.propagationStopped, ignoreUnmatched: input.eventRecord == null };",
|
|
998
|
+
"}"
|
|
999
|
+
].join("\n")),
|
|
1000
|
+
"function marklessMdxLoadScalarPlan(symbolId) {",
|
|
1001
|
+
" return marklessMdxSymbolLoaders.find(loader => symbolId.startsWith(loader.prefix))?.loadScalarActionPlan(symbolId);",
|
|
1002
|
+
"}"
|
|
1003
|
+
];
|
|
1004
|
+
}
|
|
783
1005
|
function renderMdxPreload(components) {
|
|
784
1006
|
return [
|
|
785
1007
|
" preload() {",
|
|
@@ -828,7 +1050,8 @@ function tsrxImportsFromProgram(program, id) {
|
|
|
828
1050
|
if (statement.type !== "ImportDeclaration") throw new Error(`Markless Router MDX only supports importing TSRX components before content: ${id}`);
|
|
829
1051
|
const specifier = statement.source.value;
|
|
830
1052
|
const defaultImport = statement.specifiers.find((importSpecifier) => importSpecifier.type === "ImportDefaultSpecifier");
|
|
831
|
-
if (typeof specifier !== "string" || !specifier.endsWith(".tsrx")
|
|
1053
|
+
if (typeof specifier !== "string" || !specifier.endsWith(".tsrx")) throw new Error(`Markless Router MDX currently supports default imports from .tsrx files only: ${id}`);
|
|
1054
|
+
if (!defaultImport || statement.specifiers.length !== 1) throw new Error(tsrxImportShapeMessage(statement.specifiers, specifier, id));
|
|
832
1055
|
imports.push({
|
|
833
1056
|
localName: defaultImport.local.name,
|
|
834
1057
|
specifier
|
|
@@ -836,20 +1059,32 @@ function tsrxImportsFromProgram(program, id) {
|
|
|
836
1059
|
}
|
|
837
1060
|
return imports;
|
|
838
1061
|
}
|
|
1062
|
+
function tsrxImportShapeMessage(specifiers, specifier, id) {
|
|
1063
|
+
const intro = `Markless Router MDX imports a .tsrx component as that file's default export, one component per import: ${id}.`;
|
|
1064
|
+
const source = JSON.stringify(specifier).slice(1, -1);
|
|
1065
|
+
const defaultImport = specifiers.find((entry) => entry.type === "ImportDefaultSpecifier");
|
|
1066
|
+
if (defaultImport) return `${intro} Write \`import ${defaultImport.local.name} from '${source}';\` alone.`;
|
|
1067
|
+
const named = specifiers.find((entry) => entry.type === "ImportSpecifier");
|
|
1068
|
+
if (named) {
|
|
1069
|
+
const exported = named.imported.type === "Identifier" ? named.imported.name : String(named.imported.value);
|
|
1070
|
+
return `${intro} Write \`import ${named.local.name} from '${source}';\` and make ${exported} the file's \`export default\`.`;
|
|
1071
|
+
}
|
|
1072
|
+
return `${intro} Write \`import Component from '${source}';\` and make the component the file's \`export default\`.`;
|
|
1073
|
+
}
|
|
839
1074
|
function routePartsFromHast(nodes, context) {
|
|
840
1075
|
const parts = [];
|
|
841
1076
|
let staticNodes = [];
|
|
842
1077
|
for (const node of nodes) {
|
|
843
1078
|
if (isMdxEsm(node)) continue;
|
|
844
|
-
if (isMdxJsxFlowElement(node)) {
|
|
845
|
-
appendHtmlPart(parts, staticNodes, context
|
|
1079
|
+
if (isMdxJsxFlowElement(node) && !isIntrinsicElement(node, context)) {
|
|
1080
|
+
appendHtmlPart(parts, staticNodes, context);
|
|
846
1081
|
staticNodes = [];
|
|
847
1082
|
parts.push(componentPart(node, context));
|
|
848
1083
|
continue;
|
|
849
1084
|
}
|
|
850
1085
|
staticNodes.push(node);
|
|
851
1086
|
}
|
|
852
|
-
appendHtmlPart(parts, staticNodes, context
|
|
1087
|
+
appendHtmlPart(parts, staticNodes, context);
|
|
853
1088
|
return parts;
|
|
854
1089
|
}
|
|
855
1090
|
function componentPart(node, context) {
|
|
@@ -858,10 +1093,15 @@ function componentPart(node, context) {
|
|
|
858
1093
|
if (!imported) throw new Error(`Markless Router MDX component is not imported from .tsrx: ${name}`);
|
|
859
1094
|
const componentIndex = context.components.length;
|
|
860
1095
|
const props = componentProps(node, context.id);
|
|
861
|
-
const
|
|
1096
|
+
const childNodes = normalizeStaticHastNodes(node.children, context);
|
|
1097
|
+
const children = childNodes.length ? toHtml({
|
|
1098
|
+
type: "root",
|
|
1099
|
+
children: childNodes
|
|
1100
|
+
}, { allowDangerousHtml: true }) : "";
|
|
862
1101
|
context.components.push({
|
|
863
1102
|
...imported,
|
|
864
|
-
prefix:
|
|
1103
|
+
prefix: protocolIslandSegment(componentIndex),
|
|
1104
|
+
childrenElementTags: children ? hastElementTags(childNodes) : [],
|
|
865
1105
|
props: children ? [...props, {
|
|
866
1106
|
name: "children",
|
|
867
1107
|
value: children,
|
|
@@ -873,50 +1113,72 @@ function componentPart(node, context) {
|
|
|
873
1113
|
componentIndex
|
|
874
1114
|
};
|
|
875
1115
|
}
|
|
876
|
-
function appendHtmlPart(parts, nodes,
|
|
1116
|
+
function appendHtmlPart(parts, nodes, context) {
|
|
877
1117
|
if (nodes.length === 0) return;
|
|
878
|
-
const staticNodes = normalizeStaticHastNodes(nodes,
|
|
1118
|
+
const staticNodes = normalizeStaticHastNodes(nodes, context);
|
|
879
1119
|
const html = toHtml({
|
|
880
1120
|
type: "root",
|
|
881
1121
|
children: staticNodes
|
|
882
1122
|
}, { allowDangerousHtml: true });
|
|
883
1123
|
if (html) {
|
|
884
1124
|
const elementTags = hastElementTags(staticNodes);
|
|
1125
|
+
const commentCount = hastCommentCount(staticNodes);
|
|
885
1126
|
parts.push({
|
|
886
1127
|
kind: "html",
|
|
887
1128
|
html,
|
|
888
1129
|
elementCount: elementTags.length,
|
|
1130
|
+
...commentCount > 0 ? { commentCount } : {},
|
|
889
1131
|
elementTags
|
|
890
1132
|
});
|
|
891
1133
|
}
|
|
892
1134
|
}
|
|
893
|
-
function
|
|
894
|
-
|
|
895
|
-
return toHtml({
|
|
896
|
-
type: "root",
|
|
897
|
-
children: normalizeStaticHastNodes(nodes, id)
|
|
898
|
-
}, { allowDangerousHtml: true });
|
|
899
|
-
}
|
|
900
|
-
function normalizeStaticHastNodes(nodes, id) {
|
|
901
|
-
return nodes.flatMap((node) => normalizeStaticHastNode(node, id));
|
|
1135
|
+
function normalizeStaticHastNodes(nodes, context) {
|
|
1136
|
+
return nodes.flatMap((node) => normalizeStaticHastNode(node, context));
|
|
902
1137
|
}
|
|
903
|
-
function normalizeStaticHastNode(node,
|
|
1138
|
+
function normalizeStaticHastNode(node, context) {
|
|
1139
|
+
const { id } = context;
|
|
904
1140
|
if (isMdxEsm(node)) return [];
|
|
905
|
-
if (isMdxJsxFlowElement(node) || isMdxJsxTextElement(node))
|
|
1141
|
+
if (isMdxJsxFlowElement(node) || isMdxJsxTextElement(node)) {
|
|
1142
|
+
if (!isIntrinsicElement(node, context)) throw new Error(`Markless Router MDX only supports imported TSRX components as route-level MDX elements or static markdown children: ${id}`);
|
|
1143
|
+
return [intrinsicElement(node, context)];
|
|
1144
|
+
}
|
|
906
1145
|
if (isMdxExpression(node)) {
|
|
1146
|
+
if (isEmptyMdxExpression(node.value)) return [];
|
|
907
1147
|
const value = literalSafeExpressionValue(node.value, id);
|
|
908
1148
|
return [{
|
|
909
1149
|
type: "text",
|
|
910
1150
|
value: value == null ? "" : String(value)
|
|
911
1151
|
}];
|
|
912
1152
|
}
|
|
913
|
-
if (node.type === "root") return normalizeStaticHastNodes(node.children,
|
|
1153
|
+
if (node.type === "root") return normalizeStaticHastNodes(node.children, context);
|
|
914
1154
|
if ("children" in node && Array.isArray(node.children)) return [{
|
|
915
1155
|
...node,
|
|
916
|
-
children: normalizeStaticHastNodes(node.children,
|
|
1156
|
+
children: normalizeStaticHastNodes(node.children, context)
|
|
917
1157
|
}];
|
|
918
1158
|
return [node];
|
|
919
1159
|
}
|
|
1160
|
+
function isIntrinsicElement(node, context) {
|
|
1161
|
+
const name = node.name;
|
|
1162
|
+
return typeof name === "string" && /^[a-z][a-z0-9-]*$/.test(name) && !context.importedComponents.has(name);
|
|
1163
|
+
}
|
|
1164
|
+
const JSX_ATTRIBUTE_NAMES = {
|
|
1165
|
+
className: "class",
|
|
1166
|
+
htmlFor: "for"
|
|
1167
|
+
};
|
|
1168
|
+
function intrinsicElement(node, context) {
|
|
1169
|
+
const properties = {};
|
|
1170
|
+
for (const { name, value } of componentProps(node, context.id)) {
|
|
1171
|
+
if (value === false || value == null) continue;
|
|
1172
|
+
if (typeof value !== "string" && typeof value !== "number" && value !== true) throw literalExpressionError(context.id);
|
|
1173
|
+
properties[JSX_ATTRIBUTE_NAMES[name] ?? name.toLowerCase()] = value === true ? true : String(value);
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
type: "element",
|
|
1177
|
+
tagName: node.name,
|
|
1178
|
+
properties,
|
|
1179
|
+
children: normalizeStaticHastNodes(node.children, context)
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
920
1182
|
function componentProps(node, id) {
|
|
921
1183
|
return node.attributes.map((attribute) => componentProp(attribute, id));
|
|
922
1184
|
}
|
|
@@ -947,6 +1209,9 @@ function literalSafeExpressionValue(source, id) {
|
|
|
947
1209
|
if (program.body.length !== 1 || statement?.type !== "ExpressionStatement") throw new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
|
|
948
1210
|
return evaluateLiteralExpression(statement.expression, id);
|
|
949
1211
|
}
|
|
1212
|
+
function isEmptyMdxExpression(source) {
|
|
1213
|
+
return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "").trim() === "";
|
|
1214
|
+
}
|
|
950
1215
|
function parseMdxExpressionValue(source, id) {
|
|
951
1216
|
const handle = createMdxHastHandle(`{${source}}`);
|
|
952
1217
|
let program = null;
|
|
@@ -1015,9 +1280,12 @@ function renderHtmlExpression(route) {
|
|
|
1015
1280
|
return joinExpressions(route.parts.map((part) => {
|
|
1016
1281
|
if (part.kind === "html") return JSON.stringify(part.html);
|
|
1017
1282
|
const component = route.components[part.componentIndex];
|
|
1018
|
-
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)} }))`;
|
|
1283
|
+
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}${childrenElementTagsField(component)} }, marklessMdxRenderContext))`;
|
|
1019
1284
|
}));
|
|
1020
1285
|
}
|
|
1286
|
+
function childrenElementTagsField(component) {
|
|
1287
|
+
return component.childrenElementTags.length > 0 ? `, childrenElementTags: ${JSON.stringify(component.childrenElementTags)}` : "";
|
|
1288
|
+
}
|
|
1021
1289
|
function componentPropsExpression(component) {
|
|
1022
1290
|
if (component.props.length === 0) return "{}";
|
|
1023
1291
|
return `{ ${component.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.valueExpression}`).join(", ")} }`;
|
|
@@ -1034,14 +1302,26 @@ function hastElementTags(nodes) {
|
|
|
1034
1302
|
}
|
|
1035
1303
|
return tags;
|
|
1036
1304
|
}
|
|
1305
|
+
function hastCommentCount(nodes) {
|
|
1306
|
+
let comments = 0;
|
|
1307
|
+
for (const node of nodes) {
|
|
1308
|
+
if (node.type === "comment") comments++;
|
|
1309
|
+
if ("children" in node && Array.isArray(node.children)) comments += hastCommentCount(node.children);
|
|
1310
|
+
}
|
|
1311
|
+
return comments;
|
|
1312
|
+
}
|
|
1037
1313
|
function renderStorageSeeds(components) {
|
|
1038
1314
|
const localNames = [...new Set(components.map((component) => component.localName))];
|
|
1039
1315
|
return localNames.length === 0 ? "[]" : `[${localNames.map((localName) => `...(${localName}.storageSeeds ?? [])`).join(", ")}]`;
|
|
1040
1316
|
}
|
|
1317
|
+
function renderHeadInjections(components) {
|
|
1318
|
+
const localNames = [...new Set(components.map((component) => component.localName))];
|
|
1319
|
+
return localNames.length === 0 ? "[]" : `[...new Map([${localNames.map((localName) => `...(${localName}.headInjections ?? [])`).join(", ")}].map((injection) => [JSON.stringify(injection), injection])).values()]`;
|
|
1320
|
+
}
|
|
1041
1321
|
function renderSymbolLoaders(components) {
|
|
1042
|
-
return `[${components.map((component) => `{ prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return
|
|
1322
|
+
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(", ")}]`;
|
|
1043
1323
|
}
|
|
1044
|
-
function renderMdxRenderDataLoader(components, id) {
|
|
1324
|
+
function renderMdxRenderDataLoader(components, id, parts) {
|
|
1045
1325
|
const reachedFrom = mdxMaterializedReachRoot(id);
|
|
1046
1326
|
const imports = components.map((component) => {
|
|
1047
1327
|
const source = withQuery(component.specifier, {
|
|
@@ -1052,7 +1332,8 @@ function renderMdxRenderDataLoader(components, id) {
|
|
|
1052
1332
|
});
|
|
1053
1333
|
const exportName = reachedFrom ? "marklessPrerenderData" : "marklessRenderData";
|
|
1054
1334
|
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} }`);
|
|
1055
|
-
|
|
1335
|
+
const load = `Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
|
|
1336
|
+
return parts ? `() => {\nconst marklessMdxParts = ${JSON.stringify(parts)};\nreturn ${load};\n}` : `() => ${load}`;
|
|
1056
1337
|
}
|
|
1057
1338
|
function mdxMaterializedReachRoot(id) {
|
|
1058
1339
|
const query = new URLSearchParams(parsePath(id).search);
|
|
@@ -1073,7 +1354,166 @@ function isMdxExpression(node) {
|
|
|
1073
1354
|
return node.type === "mdxFlowExpression" || node.type === "mdxTextExpression";
|
|
1074
1355
|
}
|
|
1075
1356
|
//#endregion
|
|
1357
|
+
//#region src/vite/route-preload-data.ts
|
|
1358
|
+
function compactRoutePreloadData(data) {
|
|
1359
|
+
const urls = [];
|
|
1360
|
+
const indices = /* @__PURE__ */ new Map();
|
|
1361
|
+
const encode = (routes) => Object.fromEntries(Object.keys(routes).sort().map((route) => [route, routes[route].map((href) => {
|
|
1362
|
+
let index = indices.get(href);
|
|
1363
|
+
if (index === void 0) {
|
|
1364
|
+
index = urls.length;
|
|
1365
|
+
urls.push(href);
|
|
1366
|
+
indices.set(href, index);
|
|
1367
|
+
}
|
|
1368
|
+
return index;
|
|
1369
|
+
})]));
|
|
1370
|
+
return [
|
|
1371
|
+
urls,
|
|
1372
|
+
encode(data.navigation),
|
|
1373
|
+
encode(data.ssr)
|
|
1374
|
+
];
|
|
1375
|
+
}
|
|
1376
|
+
function routePreloadDecoderSource(chunkSpecifiers) {
|
|
1377
|
+
return [
|
|
1378
|
+
"if (Array.isArray(routePreloadData)) {",
|
|
1379
|
+
" const [urls, navigation, ssr] = routePreloadData;",
|
|
1380
|
+
...chunkSpecifiers ? [` const imports = Object.assign({}, ...[...(globalThis.document?.querySelectorAll('script[type="importmap"]') ?? [])].map((script) => { try { return JSON.parse(script.textContent).imports; } catch { return {}; } }));`, ` const href = (url) => (url.startsWith(${JSON.stringify(MARKLESS_CHUNK_SPECIFIER_PREFIX)}) && imports[url]) || url;`] : [],
|
|
1381
|
+
` const decode = (routes) => Object.fromEntries(Object.entries(routes).map(([file, indices]) => [file, indices.map((index) => ${chunkSpecifiers ? "href(urls[index])" : "urls[index]"})]));`,
|
|
1382
|
+
" routePreloadData = { navigation: decode(navigation), ssr: decode(ssr) };",
|
|
1383
|
+
"}"
|
|
1384
|
+
].join("\n");
|
|
1385
|
+
}
|
|
1386
|
+
//#endregion
|
|
1387
|
+
//#region src/navigation-polyfill.ts
|
|
1388
|
+
const NAVIGATION_POLYFILL_MODULE = "@virtualstate/navigation";
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/vite/document-navigation.ts
|
|
1391
|
+
const ROUTE_INVARIANT_DOCUMENT_PROPS = /* @__PURE__ */ new Set(["children"]);
|
|
1392
|
+
function documentNavigationFor(source, id) {
|
|
1393
|
+
if (source === void 0) return "client";
|
|
1394
|
+
let program;
|
|
1395
|
+
try {
|
|
1396
|
+
program = parseModule(source, id);
|
|
1397
|
+
} catch {
|
|
1398
|
+
return "document";
|
|
1399
|
+
}
|
|
1400
|
+
const body = nodes(program.body);
|
|
1401
|
+
if (body.some(exportsHtmlAttributesHook)) return "document";
|
|
1402
|
+
const component = defaultExportedFunction(body);
|
|
1403
|
+
if (!component) return "document";
|
|
1404
|
+
return readsOnlyRouteInvariantProps(nodes(component.params)) ? "client" : "document";
|
|
1405
|
+
}
|
|
1406
|
+
function readsOnlyRouteInvariantProps(params) {
|
|
1407
|
+
if (params.length === 0) return true;
|
|
1408
|
+
if (params.length > 1) return false;
|
|
1409
|
+
const [param] = params;
|
|
1410
|
+
const pattern = param?.type === "AssignmentPattern" ? node(param.left) : param;
|
|
1411
|
+
if (pattern?.type !== "ObjectPattern") return false;
|
|
1412
|
+
return nodes(pattern.properties).every((property) => {
|
|
1413
|
+
if (property.type !== "Property" || property.computed === true) return false;
|
|
1414
|
+
const key = node(property.key);
|
|
1415
|
+
const name = key?.type === "Identifier" ? key.name : key?.value;
|
|
1416
|
+
return typeof name === "string" && ROUTE_INVARIANT_DOCUMENT_PROPS.has(name);
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
function defaultExportedFunction(body) {
|
|
1420
|
+
const declaration = node(node(body.find((statement) => statement.type === "ExportDefaultDeclaration"))?.declaration);
|
|
1421
|
+
if (isFunction(declaration)) return declaration;
|
|
1422
|
+
if (declaration?.type !== "Identifier") return void 0;
|
|
1423
|
+
const name = declaration.name;
|
|
1424
|
+
for (const statement of body) {
|
|
1425
|
+
const local = statement.type === "ExportNamedDeclaration" ? node(statement.declaration) : statement;
|
|
1426
|
+
if (local?.type === "FunctionDeclaration" && node(local.id)?.name === name) return local;
|
|
1427
|
+
if (local?.type !== "VariableDeclaration") continue;
|
|
1428
|
+
for (const declarator of nodes(local.declarations)) {
|
|
1429
|
+
if (node(declarator.id)?.name !== name) continue;
|
|
1430
|
+
const init = node(declarator.init);
|
|
1431
|
+
return isFunction(init) ? init : void 0;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
function exportsHtmlAttributesHook(statement) {
|
|
1436
|
+
if (statement.type !== "ExportNamedDeclaration") return false;
|
|
1437
|
+
const declaration = node(statement.declaration);
|
|
1438
|
+
return [
|
|
1439
|
+
node(declaration?.id)?.name,
|
|
1440
|
+
...nodes(declaration?.declarations).map((declarator) => node(declarator.id)?.name),
|
|
1441
|
+
...nodes(statement.specifiers).map((specifier) => node(specifier.exported)?.name)
|
|
1442
|
+
].includes("__marklessRouterHtmlAttributes");
|
|
1443
|
+
}
|
|
1444
|
+
function isFunction(value) {
|
|
1445
|
+
return value?.type === "FunctionDeclaration" || value?.type === "FunctionExpression" || value?.type === "ArrowFunctionExpression";
|
|
1446
|
+
}
|
|
1447
|
+
function node(value) {
|
|
1448
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
1449
|
+
}
|
|
1450
|
+
function nodes(value) {
|
|
1451
|
+
return Array.isArray(value) ? value.filter((item) => node(item)) : [];
|
|
1452
|
+
}
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/vite/nitro-fragment-requests.ts
|
|
1455
|
+
function fragmentRequestModule() {
|
|
1456
|
+
return {
|
|
1457
|
+
name: "markless-router:fragment-requests",
|
|
1458
|
+
async setup(nitro) {
|
|
1459
|
+
const dir = join(nitro.options.buildDir, "markless-router");
|
|
1460
|
+
const plugin = join(dir, "fragment-requests.mjs");
|
|
1461
|
+
await mkdir(dir, { recursive: true });
|
|
1462
|
+
await writeFile(plugin, [
|
|
1463
|
+
`const prefix = ${JSON.stringify(`${FRAGMENT_PATH_PREFIX}/`)};`,
|
|
1464
|
+
"export default function fragmentRequests(nitroApp) {",
|
|
1465
|
+
" nitroApp.hooks?.hook('request', (event) => {",
|
|
1466
|
+
" const { pathname } = event.url;",
|
|
1467
|
+
" if (!pathname.startsWith(prefix)) return;",
|
|
1468
|
+
" const page = new URL(event.url.href);",
|
|
1469
|
+
" page.pathname = pathname.slice(prefix.length - 1);",
|
|
1470
|
+
" event.url = page;",
|
|
1471
|
+
" });",
|
|
1472
|
+
"}",
|
|
1473
|
+
""
|
|
1474
|
+
].join("\n"));
|
|
1475
|
+
nitro.options.plugins.push(plugin);
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/vite/nitro-prerender.ts
|
|
1481
|
+
function serverBuildState() {
|
|
1482
|
+
return { entry: void 0 };
|
|
1483
|
+
}
|
|
1484
|
+
function recordServerEntry(state, outDir, bundle, isServerEntry) {
|
|
1485
|
+
if (!outDir) return;
|
|
1486
|
+
const entries = Object.values(bundle).filter((item) => item.type === "chunk" && item.isEntry);
|
|
1487
|
+
const entry = entries.find((chunk) => chunk.facadeModuleId && isServerEntry(chunk.facadeModuleId)) ?? (entries.length === 1 ? entries[0] : void 0);
|
|
1488
|
+
if (entry) state.entry = resolve(outDir, entry.fileName);
|
|
1489
|
+
}
|
|
1490
|
+
function prerenderRendererModule(state) {
|
|
1491
|
+
return {
|
|
1492
|
+
name: "markless-router:prerender-renderer",
|
|
1493
|
+
setup(nitro) {
|
|
1494
|
+
nitro.hooks.hook("prerender:config", async (config) => {
|
|
1495
|
+
if (!state.entry) return;
|
|
1496
|
+
const dir = join(nitro.options.buildDir, "markless-router");
|
|
1497
|
+
const handler = join(dir, "prerender-renderer.mjs");
|
|
1498
|
+
await mkdir(dir, { recursive: true });
|
|
1499
|
+
await writeFile(handler, [
|
|
1500
|
+
`import * as service from ${JSON.stringify(state.entry)};`,
|
|
1501
|
+
"const server = service.default ?? service;",
|
|
1502
|
+
"export default ({ req }) => server.fetch(req);",
|
|
1503
|
+
""
|
|
1504
|
+
].join("\n"));
|
|
1505
|
+
config.renderer = { handler };
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
//#endregion
|
|
1076
1511
|
//#region src/vite/index.ts
|
|
1512
|
+
const PAGE_SERVER_ONLY_PROPS = [{
|
|
1513
|
+
exportName: "default",
|
|
1514
|
+
propName: "http",
|
|
1515
|
+
description: "the request with its cookies and headers, the locals middleware set, and the response being written"
|
|
1516
|
+
}];
|
|
1077
1517
|
const ROUTE_DISCOVERY_ID = "virtual:markless-router/routes";
|
|
1078
1518
|
const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
1079
1519
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
@@ -1085,12 +1525,17 @@ const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-ent
|
|
|
1085
1525
|
const NAVIGATION_ENTRY_ID = "virtual:markless-router/navigation-entry";
|
|
1086
1526
|
const NAVIGATION_ENTRY_ORIGIN = "/entries/client-entry.ts";
|
|
1087
1527
|
const NAVIGATION_ENTRY_PATH_ID = "virtual:markless-router/navigation-entry-path";
|
|
1528
|
+
const FRAGMENT_ENTRY_ID = "virtual:markless-router/fragment-entry";
|
|
1529
|
+
const FRAGMENT_ENTRY_ORIGIN = "/entries/fragment-entry.ts";
|
|
1530
|
+
const FRAGMENT_ENTRY_PATH_ID = "virtual:markless-router/fragment-entry-path";
|
|
1088
1531
|
const ROUTE_PRELOADS_ID = "virtual:markless-router/route-preloads";
|
|
1089
1532
|
const ROUTE_PRELOADS_PLACEHOLDER = "__MARKLESS_ROUTER_ROUTE_PRELOADS__";
|
|
1533
|
+
const DOCUMENT_STYLESHEETS_PLACEHOLDER = "__MARKLESS_ROUTER_DOCUMENT_STYLESHEETS__";
|
|
1534
|
+
const LINK_BRIDGE_PATHS_PLACEHOLDER = "__MARKLESS_ROUTER_LINK_BRIDGE_PATHS__";
|
|
1090
1535
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1091
1536
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1092
1537
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
1093
|
-
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|route-preloads|server-entry|route-href|options)(?:\?.*)?$/;
|
|
1538
|
+
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)(?:\?.*)?$/;
|
|
1094
1539
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1095
1540
|
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "src/vite/entries");
|
|
1096
1541
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1105,36 +1550,51 @@ const virtualEntryFiles = {
|
|
|
1105
1550
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1106
1551
|
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1107
1552
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1553
|
+
[FRAGMENT_ENTRY_ID]: "fragment-entry.ts",
|
|
1108
1554
|
[SERVER_ENTRY_ID]: "server-entry.ts",
|
|
1109
1555
|
[ROUTE_HREF_ID]: "route-href.ts"
|
|
1110
1556
|
};
|
|
1111
1557
|
function router(options = {}) {
|
|
1112
1558
|
prerenderWakeChannelCaptured = process.env.MARKLESS_PRERENDER_WAKE === "1";
|
|
1113
1559
|
if (options.nitro === false) return [
|
|
1560
|
+
pageServerOnlyPropsPlugin(),
|
|
1114
1561
|
mdxTransformPlugin(),
|
|
1115
1562
|
requestFileTransformPlugin(),
|
|
1116
1563
|
routeTypegenPlugin(),
|
|
1117
1564
|
anchorTransformPlugin(),
|
|
1118
1565
|
htmlTransformPlugin(),
|
|
1119
|
-
virtualModulesPlugin()
|
|
1566
|
+
virtualModulesPlugin(void 0, void 0, void 0, void 0, options)
|
|
1120
1567
|
];
|
|
1121
1568
|
const nitroPlugins = nitro();
|
|
1122
1569
|
const resumeEntry = resumeEntryState();
|
|
1123
1570
|
const prerenderWakeEntry = resumeEntryState();
|
|
1124
1571
|
const navigationEntry = resumeEntryState();
|
|
1572
|
+
const fragmentEntry = resumeEntryState();
|
|
1125
1573
|
const routePreloads = routePreloadState();
|
|
1126
1574
|
return [
|
|
1127
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1575
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry, serverBuildState()),
|
|
1128
1576
|
devSourceModuleRequestPlugin(),
|
|
1577
|
+
pageServerOnlyPropsPlugin(),
|
|
1129
1578
|
mdxTransformPlugin(),
|
|
1130
1579
|
requestFileTransformPlugin(),
|
|
1131
1580
|
routeTypegenPlugin(),
|
|
1132
1581
|
anchorTransformPlugin(),
|
|
1133
1582
|
htmlTransformPlugin(),
|
|
1134
|
-
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1583
|
+
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, options, fragmentEntry),
|
|
1584
|
+
linkBridgeFilesPlugin(navigationEntry, fragmentEntry, options),
|
|
1135
1585
|
nitroPlugins
|
|
1136
1586
|
];
|
|
1137
1587
|
}
|
|
1588
|
+
function pageServerOnlyPropsPlugin() {
|
|
1589
|
+
return {
|
|
1590
|
+
name: "markless-router:page-server-only-props",
|
|
1591
|
+
configResolved(config) {
|
|
1592
|
+
const markless = config.plugins.find((plugin) => typeof plugin.api?.declareServerOnlyProps === "function");
|
|
1593
|
+
markless?.api.declareServerOnlyProps((moduleId) => isPageModuleFile(normalizeRouteFileId(moduleId)) ? PAGE_SERVER_ONLY_PROPS : void 0);
|
|
1594
|
+
markless?.api.declareRouteModules?.((moduleId) => isPageModuleFile(normalizeRouteFileId(moduleId)));
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1138
1598
|
function devSourceModuleRequestPlugin() {
|
|
1139
1599
|
let root = "";
|
|
1140
1600
|
return {
|
|
@@ -1155,18 +1615,32 @@ function devSourceModuleRequestPlugin() {
|
|
|
1155
1615
|
}
|
|
1156
1616
|
};
|
|
1157
1617
|
}
|
|
1158
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads) {
|
|
1618
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry = resumeEntryState(), serverBuild = serverBuildState()) {
|
|
1159
1619
|
let clientOutDir = "";
|
|
1160
1620
|
let clientEnvironmentName = "";
|
|
1161
1621
|
let clientEnvironmentConfig;
|
|
1162
1622
|
let serverEnvironmentConfig;
|
|
1163
1623
|
let productionBuild = false;
|
|
1164
1624
|
let pendingClientAssets;
|
|
1625
|
+
let capturedChunkNames = /* @__PURE__ */ new Map();
|
|
1626
|
+
const navigationPolyfillIds = /* @__PURE__ */ new Set();
|
|
1165
1627
|
return {
|
|
1166
1628
|
name: "markless-router:vite",
|
|
1167
1629
|
enforce: "pre",
|
|
1168
1630
|
sharedDuringBuild: true,
|
|
1169
|
-
|
|
1631
|
+
resolveId: {
|
|
1632
|
+
filter: { id: new RegExp(`^${escapeRegExp(NAVIGATION_POLYFILL_MODULE)}$`) },
|
|
1633
|
+
async handler(source, importer, options) {
|
|
1634
|
+
if (source !== "@virtualstate/navigation") return null;
|
|
1635
|
+
const resolved = await this.resolve(source, importer, {
|
|
1636
|
+
...options,
|
|
1637
|
+
skipSelf: true
|
|
1638
|
+
});
|
|
1639
|
+
if (resolved) navigationPolyfillIds.add(resolved.id);
|
|
1640
|
+
return resolved;
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
config(config, env) {
|
|
1170
1644
|
throwIfUserAddedNitro(config.plugins, nitroPluginsFromRouter);
|
|
1171
1645
|
config.environments ??= {};
|
|
1172
1646
|
const ssrEnvironment = config.environments.ssr ??= {};
|
|
@@ -1175,7 +1649,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1175
1649
|
ssrEnvironment.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, config.root);
|
|
1176
1650
|
const serverWatch = withWatchIgnores(config.server?.watch);
|
|
1177
1651
|
return {
|
|
1178
|
-
nitro: createNitroConfig(config.nitro, config.root, serverWatch.ignored),
|
|
1652
|
+
nitro: createNitroConfig(config.nitro, config.root, serverWatch.ignored, env?.command === "build", serverBuild),
|
|
1179
1653
|
server: {
|
|
1180
1654
|
...config.server,
|
|
1181
1655
|
watch: serverWatch
|
|
@@ -1197,9 +1671,13 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1197
1671
|
resumeEntry,
|
|
1198
1672
|
prerenderWakeEntry,
|
|
1199
1673
|
navigationEntry,
|
|
1674
|
+
fragmentEntry,
|
|
1200
1675
|
routePreloads
|
|
1201
1676
|
]) entry.base = config.base;
|
|
1202
1677
|
routePreloads.root = config.root;
|
|
1678
|
+
const markless = (config.plugins ?? []).find((plugin) => typeof plugin.api?.chunkImportMap === "function");
|
|
1679
|
+
markless?.api.enableChunkImportMap?.();
|
|
1680
|
+
routePreloads.chunkSpecifiers = markless?.api.chunkImportMap() === true;
|
|
1203
1681
|
},
|
|
1204
1682
|
configEnvironment(_name, config) {
|
|
1205
1683
|
configureRouteInputs(config);
|
|
@@ -1215,12 +1693,14 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1215
1693
|
resumeEntry.fileName = void 0;
|
|
1216
1694
|
prerenderWakeEntry.fileName = void 0;
|
|
1217
1695
|
navigationEntry.fileName = void 0;
|
|
1696
|
+
fragmentEntry.fileName = void 0;
|
|
1218
1697
|
routePreloads.routes = {
|
|
1219
1698
|
navigation: {},
|
|
1220
1699
|
ssr: {},
|
|
1221
1700
|
styles: {}
|
|
1222
1701
|
};
|
|
1223
1702
|
routePreloads.persisted = false;
|
|
1703
|
+
routePreloads.importMap = void 0;
|
|
1224
1704
|
await removeClientAssetsManifest(clientOutDir);
|
|
1225
1705
|
return;
|
|
1226
1706
|
}
|
|
@@ -1229,28 +1709,48 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1229
1709
|
resumeEntry.fileName = clientAssetFileName(manifest.entries.resume, manifest.base);
|
|
1230
1710
|
prerenderWakeEntry.fileName = manifest.entries.prerenderWake ? clientAssetFileName(manifest.entries.prerenderWake, manifest.base) : void 0;
|
|
1231
1711
|
navigationEntry.fileName = clientAssetFileName(manifest.entries.navigation, manifest.base);
|
|
1712
|
+
fragmentEntry.fileName = manifest.entries.fragment ? clientAssetFileName(manifest.entries.fragment, manifest.base) : void 0;
|
|
1713
|
+
fragmentEntry.imports = manifest.entries.fragmentImports;
|
|
1232
1714
|
routePreloads.routes = manifest.routes;
|
|
1233
1715
|
routePreloads.persisted = true;
|
|
1716
|
+
routePreloads.importMap = manifest.importMap;
|
|
1234
1717
|
},
|
|
1235
1718
|
generateBundle: {
|
|
1236
1719
|
order: "post",
|
|
1237
1720
|
handler(_options, bundle) {
|
|
1238
|
-
if (this.environment?.config.consumer !== "client")
|
|
1721
|
+
if (this.environment?.config.consumer !== "client") {
|
|
1722
|
+
if (this.environment?.config.consumer === "server") patchDocumentStylesheetsInBundle(bundle, documentStylesheetsFromBundle(bundle, routePreloads.root, routePreloads.base));
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1239
1725
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1240
1726
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
1241
1727
|
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1242
1728
|
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1243
1729
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
1244
1730
|
if (navigationChunk) navigationEntry.fileName = navigationChunk.fileName;
|
|
1731
|
+
const fragmentChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, FRAGMENT_ENTRY_ORIGIN));
|
|
1732
|
+
if (fragmentChunk) {
|
|
1733
|
+
fragmentEntry.fileName = fragmentChunk.fileName;
|
|
1734
|
+
const everyChunk = new Map(outputChunks(bundle).map((chunk) => [chunk.fileName, chunk]));
|
|
1735
|
+
const closure = /* @__PURE__ */ new Set();
|
|
1736
|
+
includeChunk(closure, {
|
|
1737
|
+
chunks: everyChunk,
|
|
1738
|
+
everyChunk
|
|
1739
|
+
}, fragmentChunk.fileName);
|
|
1740
|
+
closure.delete(fragmentChunk.fileName);
|
|
1741
|
+
fragmentEntry.imports = [...closure].map((fileName) => joinURL(routePreloads.base, fileName));
|
|
1742
|
+
}
|
|
1245
1743
|
routePreloads.routes = routeModulePreloadsFromBundle({
|
|
1246
1744
|
base: routePreloads.base,
|
|
1247
1745
|
bundle,
|
|
1248
1746
|
navigationChunk,
|
|
1747
|
+
navigationPolyfillIds,
|
|
1249
1748
|
prerenderWakeChunk,
|
|
1250
1749
|
resumeChunk,
|
|
1251
1750
|
root: routePreloads.root
|
|
1252
1751
|
});
|
|
1253
1752
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
1753
|
+
capturedChunkNames = new Map(outputChunks(bundle).map((chunk) => [chunk.fileName, chunk.preliminaryFileName]));
|
|
1254
1754
|
if (productionBuild) {
|
|
1255
1755
|
const wakeRequired = prerenderWakeChannelEnabled();
|
|
1256
1756
|
if (!resumeEntry.fileName || wakeRequired && !prerenderWakeEntry.fileName || !navigationEntry.fileName) throw new Error(wakeRequired ? "Markless Router client build did not emit its resume, prerender-wake, and navigation entries." : "Markless Router client build did not emit its resume and navigation entries.");
|
|
@@ -1259,16 +1759,38 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEn
|
|
|
1259
1759
|
resumeEntry: joinURL(routePreloads.base, resumeEntry.fileName),
|
|
1260
1760
|
...prerenderWakeEntry.fileName ? { prerenderWakeEntry: joinURL(routePreloads.base, prerenderWakeEntry.fileName) } : {},
|
|
1261
1761
|
navigationEntry: joinURL(routePreloads.base, navigationEntry.fileName),
|
|
1762
|
+
...fragmentEntry.fileName ? {
|
|
1763
|
+
fragmentEntry: joinURL(routePreloads.base, fragmentEntry.fileName),
|
|
1764
|
+
fragmentImports: fragmentEntry.imports
|
|
1765
|
+
} : {},
|
|
1262
1766
|
routes: routePreloads.routes
|
|
1263
1767
|
});
|
|
1264
1768
|
}
|
|
1265
1769
|
}
|
|
1266
1770
|
},
|
|
1267
|
-
async writeBundle() {
|
|
1771
|
+
async writeBundle(options, bundle) {
|
|
1268
1772
|
const environment = this.environment;
|
|
1269
1773
|
const isClientEnvironment = environment?.name ? environment.name === clientEnvironmentName : environment?.config === clientEnvironmentConfig;
|
|
1774
|
+
if (productionBuild && environment?.name === "ssr") recordServerEntry(serverBuild, options.dir, bundle, (id) => id.startsWith(`\0${SERVER_ENTRY_ID}`));
|
|
1270
1775
|
if (!productionBuild || !isClientEnvironment) return;
|
|
1271
1776
|
if (!pendingClientAssets) throw new Error("Markless Router client build finished without a client-assets manifest.");
|
|
1777
|
+
const renames = bundle ? chunkRenamesSince(capturedChunkNames, bundle) : /* @__PURE__ */ new Map();
|
|
1778
|
+
if (renames.size > 0) {
|
|
1779
|
+
pendingClientAssets = renameClientAssetChunks(pendingClientAssets, renames);
|
|
1780
|
+
routePreloads.routes = pendingClientAssets.routes;
|
|
1781
|
+
for (const entry of [
|
|
1782
|
+
resumeEntry,
|
|
1783
|
+
prerenderWakeEntry,
|
|
1784
|
+
navigationEntry,
|
|
1785
|
+
fragmentEntry
|
|
1786
|
+
]) if (entry.fileName) entry.fileName = renames.get(entry.fileName) ?? entry.fileName;
|
|
1787
|
+
fragmentEntry.imports = pendingClientAssets.entries.fragmentImports;
|
|
1788
|
+
}
|
|
1789
|
+
const importMap = bundle?.[MARKLESS_IMPORT_MAP_ASSET];
|
|
1790
|
+
if (importMap?.type === "asset") pendingClientAssets = {
|
|
1791
|
+
...pendingClientAssets,
|
|
1792
|
+
importMap: JSON.parse(String(importMap.source))
|
|
1793
|
+
};
|
|
1272
1794
|
await writeClientAssetsManifest(clientOutDir, pendingClientAssets);
|
|
1273
1795
|
}
|
|
1274
1796
|
};
|
|
@@ -1279,16 +1801,7 @@ function configureRouteInputs(config) {
|
|
|
1279
1801
|
if (config.consumer === "client") {
|
|
1280
1802
|
config.build.rolldownOptions.input = routerClientInput(config.build.rolldownOptions.input, configRoot(config));
|
|
1281
1803
|
config.build.rolldownOptions.preserveEntrySignatures ??= "exports-only";
|
|
1282
|
-
} else
|
|
1283
|
-
config.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, configRoot(config));
|
|
1284
|
-
config.build.rolldownOptions.external = withExternal(config.build.rolldownOptions.external, "nitro");
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function withExternal(external, id) {
|
|
1288
|
-
if (Array.isArray(external)) return external.includes(id) ? external : [...external, id];
|
|
1289
|
-
if (typeof external === "string") return external === id ? external : [external, id];
|
|
1290
|
-
if (external === void 0) return [id];
|
|
1291
|
-
return external;
|
|
1804
|
+
} else config.build.rolldownOptions.input ??= scopedVirtualEntryId(SERVER_ENTRY_ID, configRoot(config));
|
|
1292
1805
|
}
|
|
1293
1806
|
function requestFileTransformPlugin() {
|
|
1294
1807
|
let root = "";
|
|
@@ -1315,6 +1828,7 @@ function requestFileIdForTransform(root, id) {
|
|
|
1315
1828
|
if (!isAbsolute(pathname)) return;
|
|
1316
1829
|
const relativeFileId = root ? withoutLeadingSlash(normalize(relative(root, pathname))) : withoutLeadingSlash(normalize(pathname));
|
|
1317
1830
|
if (relativeFileId.startsWith("api/") || relativeFileId.startsWith("middleware/")) return relativeFileId;
|
|
1831
|
+
if (relativeFileId.startsWith("pages/")) return void 0;
|
|
1318
1832
|
return relativeFileId.match(/(?:^|\/)((?:api|middleware)\/.+\.ts)$/)?.[1];
|
|
1319
1833
|
}
|
|
1320
1834
|
function requestFileBuildTransformPlugin(root) {
|
|
@@ -1331,7 +1845,7 @@ function requestFileBuildTransformPlugin(root) {
|
|
|
1331
1845
|
}
|
|
1332
1846
|
};
|
|
1333
1847
|
}
|
|
1334
|
-
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = []) {
|
|
1848
|
+
function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = [], isBuild = false, serverBuild = serverBuildState()) {
|
|
1335
1849
|
const scanDirs = Array.isArray(nitroConfig?.scanDirs) ? nitroConfig.scanDirs.filter((dir) => typeof dir === "string") : [];
|
|
1336
1850
|
const watchOptions = withWatchIgnores(nitroConfig?.watchOptions, serverWatchIgnored);
|
|
1337
1851
|
const publicAssets = Array.isArray(nitroConfig?.publicAssets) ? nitroConfig.publicAssets : [];
|
|
@@ -1351,9 +1865,20 @@ function createNitroConfig(nitroConfig, root = ".", serverWatchIgnored = []) {
|
|
|
1351
1865
|
...publicAssets
|
|
1352
1866
|
],
|
|
1353
1867
|
routesDir: nitroConfig?.routesDir ?? ".output/markless/router/nitro-routes",
|
|
1868
|
+
modules: [
|
|
1869
|
+
...nitroConfig?.modules ?? [],
|
|
1870
|
+
fragmentRequestModule(),
|
|
1871
|
+
...isBuild ? [prerenderRendererModule(serverBuild)] : []
|
|
1872
|
+
],
|
|
1873
|
+
...isBuild && { routeRules: {
|
|
1874
|
+
[`/${MARKLESS_BUILD_PREFIX}**`]: { headers: { "cache-control": "public, max-age=31536000, immutable" } },
|
|
1875
|
+
[`/_markless/links/**`]: { headers: { "cache-control": "public, max-age=31536000, immutable" } },
|
|
1876
|
+
...Object.fromEntries(MARKLESS_PUBLIC_BUILD_METADATA_FILES.map((file) => [`/${file}`, { headers: { "cache-control": "public, max-age=0, must-revalidate" } }])),
|
|
1877
|
+
...nitroConfig?.routeRules
|
|
1878
|
+
} },
|
|
1354
1879
|
rolldownConfig: withRequestFileBuildPlugin(nitroConfig?.rolldownConfig, root),
|
|
1355
1880
|
rollupConfig: withRequestFileBuildPlugin(nitroConfig?.rollupConfig, root),
|
|
1356
|
-
scanDirs: [
|
|
1881
|
+
scanDirs: [.../* @__PURE__ */ new Set([".", ...scanDirs])],
|
|
1357
1882
|
watchOptions
|
|
1358
1883
|
};
|
|
1359
1884
|
}
|
|
@@ -1400,6 +1925,7 @@ function resumeEntryState() {
|
|
|
1400
1925
|
function routePreloadState() {
|
|
1401
1926
|
return {
|
|
1402
1927
|
base: "/",
|
|
1928
|
+
chunkSpecifiers: false,
|
|
1403
1929
|
persisted: false,
|
|
1404
1930
|
root: ".",
|
|
1405
1931
|
routes: {
|
|
@@ -1417,33 +1943,40 @@ function routerClientInput(input, root) {
|
|
|
1417
1943
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
1418
1944
|
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
1419
1945
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
1946
|
+
const fragmentEntryId = scopedVirtualEntryId(FRAGMENT_ENTRY_ID, root);
|
|
1420
1947
|
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
1421
1948
|
if (input === void 0) return [
|
|
1422
1949
|
resumeEntryId,
|
|
1423
1950
|
...wakeEntries,
|
|
1424
|
-
navigationEntryId
|
|
1951
|
+
navigationEntryId,
|
|
1952
|
+
fragmentEntryId
|
|
1425
1953
|
];
|
|
1426
1954
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
1427
1955
|
resumeEntryId,
|
|
1428
1956
|
...wakeEntries,
|
|
1429
1957
|
navigationEntryId,
|
|
1958
|
+
fragmentEntryId,
|
|
1430
1959
|
...Array.isArray(input) ? input : [input]
|
|
1431
1960
|
];
|
|
1432
1961
|
if (isRecord(input)) return {
|
|
1433
1962
|
...input,
|
|
1434
1963
|
"markless-router-resume": resumeEntryId,
|
|
1435
1964
|
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
1436
|
-
"markless-router-navigation": navigationEntryId
|
|
1965
|
+
"markless-router-navigation": navigationEntryId,
|
|
1966
|
+
"markless-router-fragment": fragmentEntryId
|
|
1437
1967
|
};
|
|
1438
1968
|
return input;
|
|
1439
1969
|
}
|
|
1440
1970
|
function isVirtualEntryChunk(chunk, origin) {
|
|
1441
1971
|
return [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id ? decodePath(parseURL(id).pathname).endsWith(origin) : false);
|
|
1442
1972
|
}
|
|
1443
|
-
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1973
|
+
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState(), options = {}, fragmentEntry = resumeEntryState()) {
|
|
1444
1974
|
let root = "";
|
|
1445
1975
|
return {
|
|
1446
1976
|
name: "markless-router:routes",
|
|
1977
|
+
config() {
|
|
1978
|
+
return { define: { __MARKLESS_ROUTER_LINK_INTENT__: JSON.stringify(linkIntentEnabled(options)) } };
|
|
1979
|
+
},
|
|
1447
1980
|
sharedDuringBuild: true,
|
|
1448
1981
|
configResolved(config) {
|
|
1449
1982
|
root = config.root;
|
|
@@ -1452,48 +1985,148 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEnt
|
|
|
1452
1985
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
1453
1986
|
handler(id) {
|
|
1454
1987
|
const baseId = virtualModuleBaseId(id);
|
|
1455
|
-
if (baseId ===
|
|
1988
|
+
if (baseId === ROUTER_OPTIONS_ID) return {
|
|
1989
|
+
id: `\0${baseId}${rootScopeQuery(root, id)}`,
|
|
1990
|
+
moduleSideEffects: false
|
|
1991
|
+
};
|
|
1992
|
+
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)}`;
|
|
1456
1993
|
const entryFile = virtualEntryFiles[baseId];
|
|
1457
1994
|
if (!entryFile) return;
|
|
1995
|
+
if (baseId === ROUTE_HREF_ID) return join(VIRTUAL_ENTRY_DIR, entryFile);
|
|
1458
1996
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
1459
1997
|
}
|
|
1460
1998
|
},
|
|
1461
1999
|
load(id) {
|
|
1462
|
-
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root);
|
|
2000
|
+
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root, options);
|
|
1463
2001
|
if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
|
|
1464
2002
|
if (id.startsWith(`\0${PRERENDER_WAKE_ENTRY_PATH_ID}`)) {
|
|
1465
2003
|
if (!prerenderWakeChannelEnabled() || !prerenderWakeEntry.fileName) return "export const prerenderWakeEntryPath = undefined;";
|
|
1466
2004
|
return entryPathSource("prerenderWakeEntryPath", prerenderWakeEntry, PRERENDER_WAKE_ENTRY_ID, root);
|
|
1467
2005
|
}
|
|
1468
2006
|
if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
|
|
2007
|
+
if (id.startsWith(`\0${FRAGMENT_ENTRY_PATH_ID}`)) return entryPathSource("fragmentEntryPath", fragmentEntry, FRAGMENT_ENTRY_ID, root);
|
|
1469
2008
|
if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads, this?.environment?.config.consumer === "client");
|
|
1470
|
-
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`))
|
|
2009
|
+
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) {
|
|
2010
|
+
const documentFile = join(root, "document.tsrx");
|
|
2011
|
+
this?.addWatchFile?.(documentFile);
|
|
2012
|
+
return routerOptionsSource(options, documentFile);
|
|
2013
|
+
}
|
|
1471
2014
|
}
|
|
1472
2015
|
};
|
|
1473
2016
|
}
|
|
2017
|
+
async function routerOptionsSource(options, documentFile) {
|
|
2018
|
+
const documentSource = await readFile(documentFile, "utf8").catch(() => void 0);
|
|
2019
|
+
return [
|
|
2020
|
+
"export const routerMode = \"path\";",
|
|
2021
|
+
`export const documentNavigation = ${JSON.stringify(documentNavigationFor(documentSource, documentFile))};`,
|
|
2022
|
+
linkIntentEnabled(options) ? `export const startLinkIntentPreloading = (root, preload) => (${listenForLinkIntent.toString()})(root, ${JSON.stringify(LINK_ATTRIBUTE)}, preload, ${JSON.stringify(PREFETCH_ATTRIBUTE)});` : "export const startLinkIntentPreloading = () => () => {};"
|
|
2023
|
+
].join("\n");
|
|
2024
|
+
}
|
|
2025
|
+
function linkIntentEnabled(options) {
|
|
2026
|
+
return options.prefetch !== false;
|
|
2027
|
+
}
|
|
1474
2028
|
function entryPathSource(exportName, entry, entryId, root) {
|
|
1475
|
-
const
|
|
1476
|
-
|
|
2029
|
+
return `export const ${exportName} = ${JSON.stringify(entryPath(entry, entryId, root))};`;
|
|
2030
|
+
}
|
|
2031
|
+
function entryPath(entry, entryId, root) {
|
|
2032
|
+
return entry.fileName ? joinURL(entry.base, entry.fileName) : joinURL(entry.base, "@id", scopedVirtualEntryId(entryId, root));
|
|
1477
2033
|
}
|
|
1478
|
-
function
|
|
2034
|
+
function linkBridgeFilesPlugin(navigationEntry, fragmentEntry, options) {
|
|
2035
|
+
let root = "";
|
|
2036
|
+
let base = "/";
|
|
2037
|
+
let clientOutDir = "";
|
|
2038
|
+
let debug = false;
|
|
2039
|
+
let modules = [];
|
|
2040
|
+
return {
|
|
2041
|
+
name: "markless-router:link-bridge-files",
|
|
2042
|
+
apply: "build",
|
|
2043
|
+
sharedDuringBuild: true,
|
|
2044
|
+
configResolved(config) {
|
|
2045
|
+
root = config.root;
|
|
2046
|
+
base = config.base;
|
|
2047
|
+
debug = config.define?.__MARKLESS_DEBUG_ENABLED__ === "true";
|
|
2048
|
+
const client = Object.values(config.environments).find((environment) => environment.consumer === "client");
|
|
2049
|
+
if (client) clientOutDir = resolve(config.root, client.build.outDir);
|
|
2050
|
+
},
|
|
2051
|
+
generateBundle: {
|
|
2052
|
+
order: "post",
|
|
2053
|
+
async handler(_options, bundle) {
|
|
2054
|
+
if (this.environment?.name !== "ssr") return;
|
|
2055
|
+
const chunks = outputChunks(bundle);
|
|
2056
|
+
const target = chunks.find((chunk) => chunk.code?.includes(LINK_BRIDGE_PATHS_PLACEHOLDER));
|
|
2057
|
+
if (!target) return;
|
|
2058
|
+
if (!navigationEntry.fileName) throw new Error("Markless Router built its server entry before its navigation entry.");
|
|
2059
|
+
const documentFile = join(root, "document.tsrx");
|
|
2060
|
+
const documentSource = await readFile(documentFile, "utf8").catch(() => void 0);
|
|
2061
|
+
const built = routerLinkBridgeModules({
|
|
2062
|
+
base,
|
|
2063
|
+
navigationEntryPath: entryPath(navigationEntry, NAVIGATION_ENTRY_ID, root),
|
|
2064
|
+
fragmentEntryPath: entryPath(fragmentEntry, FRAGMENT_ENTRY_ID, root),
|
|
2065
|
+
fragmentEntryImports: fragmentEntry.imports,
|
|
2066
|
+
fragmentNavigationSource: minifiedFragmentNavigationSource(),
|
|
2067
|
+
fragmentNavigationRunSource: minifiedFragmentNavigationSource(runFragmentNavigation),
|
|
2068
|
+
prefetch: options.prefetch,
|
|
2069
|
+
documentNavigation: documentNavigationFor(documentSource, documentFile),
|
|
2070
|
+
routeFileIds: bundledPageFileIds(chunks, root),
|
|
2071
|
+
debug
|
|
2072
|
+
});
|
|
2073
|
+
modules = built;
|
|
2074
|
+
target.code = target.code.replace(LINK_BRIDGE_PATHS_PLACEHOLDER, jsStringLiteralContent(JSON.stringify(built.slice(0, 2).map((module) => module.path))));
|
|
2075
|
+
}
|
|
2076
|
+
},
|
|
2077
|
+
async writeBundle() {
|
|
2078
|
+
if (this.environment?.name !== "ssr" || modules.length === 0) return;
|
|
2079
|
+
const prefix = base.replace(/\/?$/, "/");
|
|
2080
|
+
for (const module of modules) {
|
|
2081
|
+
const file = join(clientOutDir, module.path.slice(prefix.length));
|
|
2082
|
+
await mkdir(dirname(file), { recursive: true });
|
|
2083
|
+
await writeFile(file, module.source, "utf8");
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function bundledPageFileIds(chunks, root) {
|
|
2089
|
+
const files = /* @__PURE__ */ new Set();
|
|
2090
|
+
for (const chunk of chunks) for (const id of chunk.moduleIds ?? []) {
|
|
2091
|
+
if (id.startsWith("\0")) continue;
|
|
2092
|
+
const pathname = decodePath(parseURL(id).pathname);
|
|
2093
|
+
if (!isAbsolute(pathname)) continue;
|
|
2094
|
+
const file = normalizeRouteFileId(relative(root, pathname));
|
|
2095
|
+
if (isPageModuleFile(file)) files.add(file);
|
|
2096
|
+
}
|
|
2097
|
+
return [...files];
|
|
2098
|
+
}
|
|
2099
|
+
function serverEntrySource(root, options) {
|
|
1479
2100
|
const query = rootScopeQuery(root);
|
|
1480
2101
|
return [
|
|
1481
2102
|
`import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';`,
|
|
1482
2103
|
`import { resumeEntryPath } from '${RESUME_ENTRY_PATH_ID}${query}';`,
|
|
1483
2104
|
`import { prerenderWakeEntryPath } from '${PRERENDER_WAKE_ENTRY_PATH_ID}${query}';`,
|
|
1484
2105
|
`import { navigationEntryPath } from '${NAVIGATION_ENTRY_PATH_ID}${query}';`,
|
|
1485
|
-
`import {
|
|
2106
|
+
`import { fragmentEntryPath } from '${FRAGMENT_ENTRY_PATH_ID}${query}';`,
|
|
2107
|
+
`import { documentStylesheets, routeModulePreloads, routeSsrModulePreloads, routeStylesheets, documentImportMap } from '${ROUTE_PRELOADS_ID}${query}';`,
|
|
1486
2108
|
`import { pageModuleLoaders, routeFileIds } from '${ROUTE_DISCOVERY_ID}${query}';`,
|
|
2109
|
+
`import { documentNavigation } from '${ROUTER_OPTIONS_ID}${query}';`,
|
|
1487
2110
|
`const documentModuleLoaders = import.meta.glob(['/document.tsrx']);`,
|
|
2111
|
+
`const linkBridgePathsJson = globalThis.__marklessRouterLinkBridgePathsJson ?? ${JSON.stringify(LINK_BRIDGE_PATHS_PLACEHOLDER)};`,
|
|
1488
2112
|
`const entry = createServerEntry({`,
|
|
1489
2113
|
` dev: import.meta.env.DEV,`,
|
|
2114
|
+
` linkBridgePaths: linkBridgePathsJson === ${JSON.stringify(LINK_BRIDGE_PATHS_PLACEHOLDER)} ? undefined : JSON.parse(linkBridgePathsJson),`,
|
|
2115
|
+
` base: import.meta.env.BASE_URL,`,
|
|
1490
2116
|
` resumeEntryPath,`,
|
|
1491
2117
|
` prerenderWakeEntryPath,`,
|
|
1492
2118
|
` navigationEntryPath,`,
|
|
2119
|
+
` fragmentEntryPath,`,
|
|
2120
|
+
` fragmentNavigationSource: ${JSON.stringify(minifiedFragmentNavigationSource())},`,
|
|
2121
|
+
` fragmentNavigationRunSource: ${JSON.stringify(minifiedFragmentNavigationSource(runFragmentNavigation))},`,
|
|
2122
|
+
...options.prefetch === false ? [" prefetch: false,"] : [],
|
|
1493
2123
|
` routeModulePreloads,`,
|
|
1494
2124
|
` routeSsrModulePreloads,`,
|
|
1495
2125
|
` routeStylesheets,`,
|
|
2126
|
+
` documentStylesheets,`,
|
|
2127
|
+
` importMap: documentImportMap,`,
|
|
1496
2128
|
` documentModuleLoader: documentModuleLoaders['/document.tsrx'],`,
|
|
2129
|
+
` documentNavigation,`,
|
|
1497
2130
|
` pageModuleLoaders,`,
|
|
1498
2131
|
` routeFileIds,`,
|
|
1499
2132
|
`});`,
|
|
@@ -1501,9 +2134,21 @@ function serverEntrySource(root) {
|
|
|
1501
2134
|
`export default entry;`
|
|
1502
2135
|
].join("\n");
|
|
1503
2136
|
}
|
|
2137
|
+
function minifiedFragmentNavigationSource(source = startFragmentNavigation) {
|
|
2138
|
+
const result = minifySync("markless-fragment-navigation.js", `export default (${source.toString()});`, {
|
|
2139
|
+
compress: true,
|
|
2140
|
+
mangle: true,
|
|
2141
|
+
module: true
|
|
2142
|
+
});
|
|
2143
|
+
const prefix = "export default";
|
|
2144
|
+
const code = result.code.trim();
|
|
2145
|
+
if (result.errors.length > 0 || !code.startsWith(prefix)) throw new Error(`MARKLESS_ROUTER_FRAGMENT_MINIFY_FAILED: ${result.errors.map((error) => error.message).join("\n")}`);
|
|
2146
|
+
return code.slice(14).replace(/;$/, "").trim();
|
|
2147
|
+
}
|
|
1504
2148
|
function rootScopeQuery(root, id = "") {
|
|
2149
|
+
const parsed = parsePath(id);
|
|
1505
2150
|
const query = stringifyQuery({
|
|
1506
|
-
...parseQuery(
|
|
2151
|
+
...parseQuery(parsed.search),
|
|
1507
2152
|
"markless-router-root": root || "."
|
|
1508
2153
|
});
|
|
1509
2154
|
return query ? `?${query}&lang.ts` : "";
|
|
@@ -1513,10 +2158,16 @@ function routePreloadsSource(state, client) {
|
|
|
1513
2158
|
navigation: state.routes.navigation,
|
|
1514
2159
|
ssr: state.routes.ssr
|
|
1515
2160
|
} : state.routes;
|
|
1516
|
-
const routeStylesheetExport = client ? [] : [
|
|
2161
|
+
const routeStylesheetExport = client ? [] : [
|
|
2162
|
+
`export const routeStylesheets = ${state.persisted ? "routePreloadData.styles ?? {}" : "undefined"};`,
|
|
2163
|
+
`const documentStylesheetsJson = globalThis.__marklessRouterDocumentStylesheetsJson ?? ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)};`,
|
|
2164
|
+
`export const documentStylesheets = documentStylesheetsJson === ${JSON.stringify(DOCUMENT_STYLESHEETS_PLACEHOLDER)} ? [] : JSON.parse(documentStylesheetsJson);`,
|
|
2165
|
+
`export const documentImportMap = ${JSON.stringify(state.persisted && state.importMap ? importMapScript(state.importMap) : "")};`
|
|
2166
|
+
];
|
|
1517
2167
|
return [
|
|
1518
2168
|
`const routePreloadsJson = globalThis.__marklessRouterRoutePreloadsJson ?? ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)};`,
|
|
1519
|
-
`
|
|
2169
|
+
`let routePreloadData = routePreloadsJson === ${JSON.stringify(ROUTE_PRELOADS_PLACEHOLDER)} ? ${JSON.stringify(routes)} : JSON.parse(routePreloadsJson);`,
|
|
2170
|
+
routePreloadDecoderSource(client && state.chunkSpecifiers),
|
|
1520
2171
|
`export const routeModulePreloads = routePreloadData.navigation ?? {};`,
|
|
1521
2172
|
`export const routeSsrModulePreloads = routePreloadData.ssr ?? {};`,
|
|
1522
2173
|
...routeStylesheetExport,
|
|
@@ -1540,21 +2191,51 @@ function routePreloadsSource(state, client) {
|
|
|
1540
2191
|
].join("\n");
|
|
1541
2192
|
}
|
|
1542
2193
|
function patchRoutePreloadsInBundle(bundle, routes) {
|
|
1543
|
-
const replacement = jsStringLiteralContent(JSON.stringify(
|
|
1544
|
-
navigation: routes.navigation,
|
|
1545
|
-
ssr: routes.ssr
|
|
1546
|
-
}));
|
|
2194
|
+
const replacement = jsStringLiteralContent(JSON.stringify(compactRoutePreloadData(routes)));
|
|
1547
2195
|
for (const chunk of outputChunks(bundle)) {
|
|
1548
2196
|
if (!chunk.code?.includes(ROUTE_PRELOADS_PLACEHOLDER)) continue;
|
|
1549
2197
|
chunk.code = chunk.code.replace(ROUTE_PRELOADS_PLACEHOLDER, replacement);
|
|
1550
2198
|
}
|
|
1551
2199
|
}
|
|
2200
|
+
function documentStylesheetsFromBundle(bundle, root, base) {
|
|
2201
|
+
const chunks = outputChunks(bundle);
|
|
2202
|
+
const chunksByFileName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
|
|
2203
|
+
const documentFile = join(root, "document.tsrx");
|
|
2204
|
+
const documentChunks = chunks.filter((chunk) => [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id !== null && id !== void 0 && decodePath(parseURL(id).pathname) === documentFile));
|
|
2205
|
+
if (documentChunks.length === 0) return [];
|
|
2206
|
+
return routeStylesheetsForChunks(documentChunks, new Set(documentChunks.map((chunk) => chunk.fileName)), chunksByFileName, base);
|
|
2207
|
+
}
|
|
2208
|
+
function patchDocumentStylesheetsInBundle(bundle, stylesheets) {
|
|
2209
|
+
const replacement = jsStringLiteralContent(JSON.stringify(stylesheets));
|
|
2210
|
+
for (const chunk of outputChunks(bundle)) {
|
|
2211
|
+
if (!chunk.code?.includes(DOCUMENT_STYLESHEETS_PLACEHOLDER)) continue;
|
|
2212
|
+
chunk.code = chunk.code.replace(DOCUMENT_STYLESHEETS_PLACEHOLDER, replacement);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
1552
2215
|
function jsStringLiteralContent(value) {
|
|
1553
2216
|
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("'", "\\'").replaceAll("`", "\\`").replaceAll("${", "\\${");
|
|
1554
2217
|
}
|
|
1555
2218
|
function routeModulePreloadsFromBundle(input) {
|
|
1556
|
-
const
|
|
1557
|
-
const
|
|
2219
|
+
const allChunks = outputChunks(input.bundle).filter((chunk) => !isExecutionLogChunk(chunk));
|
|
2220
|
+
const deferred = marklessDeferredChunkFileNames(allChunks);
|
|
2221
|
+
const chunks = allChunks.filter((chunk) => !deferred.has(chunk.fileName));
|
|
2222
|
+
const deferredChunks = allChunks.filter((chunk) => deferred.has(chunk.fileName));
|
|
2223
|
+
const everyChunk = new Map(allChunks.map((chunk) => [chunk.fileName, chunk]));
|
|
2224
|
+
const plannedLanding = plannedLandingFiles(input.bundle);
|
|
2225
|
+
const deferredFileNames = new Set(deferredChunks.map((chunk) => chunk.fileName));
|
|
2226
|
+
const resumeRuntimeFileNames = /* @__PURE__ */ new Set();
|
|
2227
|
+
for (const entry of [input.resumeChunk, input.prerenderWakeChunk]) if (entry) includeChunk(resumeRuntimeFileNames, {
|
|
2228
|
+
chunks: everyChunk,
|
|
2229
|
+
everyChunk
|
|
2230
|
+
}, entry.fileName);
|
|
2231
|
+
const lazyRuntimePacks = new Set([...resumeRuntimeFileNames].flatMap((fileName) => {
|
|
2232
|
+
const name = everyChunk.get(fileName)?.name;
|
|
2233
|
+
return name && lazyStartupPackName(name) !== name ? [lazyStartupPackName(name)] : [];
|
|
2234
|
+
}));
|
|
2235
|
+
for (const chunk of allChunks) if (chunk.name && lazyRuntimePacks.has(chunk.name)) includeChunk(resumeRuntimeFileNames, {
|
|
2236
|
+
chunks: everyChunk,
|
|
2237
|
+
everyChunk
|
|
2238
|
+
}, chunk.fileName);
|
|
1558
2239
|
const routeChunks = /* @__PURE__ */ new Map();
|
|
1559
2240
|
const routeChunkFileNames = /* @__PURE__ */ new Set();
|
|
1560
2241
|
for (const chunk of chunks) {
|
|
@@ -1566,40 +2247,86 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1566
2247
|
routeChunkFileNames.add(chunk.fileName);
|
|
1567
2248
|
}
|
|
1568
2249
|
const symbolChunks = symbolChunksBySourceFile(chunks);
|
|
2250
|
+
const resolverSourceFiles = new Set(allChunks.flatMap((chunk) => (chunk.moduleIds ?? []).flatMap((moduleId) => {
|
|
2251
|
+
const sourceFile = resolverVirtualModuleSourceFile(moduleId.startsWith("\0") ? moduleId.slice(1) : moduleId);
|
|
2252
|
+
return sourceFile ? [sourceModulePathname(sourceFile)] : [];
|
|
2253
|
+
})));
|
|
2254
|
+
const polyfillFileNames = new Set(chunks.filter((chunk) => chunk.facadeModuleId && input.navigationPolyfillIds?.has(chunk.facadeModuleId)).map((chunk) => chunk.fileName));
|
|
1569
2255
|
const navigation = {};
|
|
1570
2256
|
const ssr = {};
|
|
1571
2257
|
const styles = {};
|
|
1572
2258
|
for (const [routeFile, routeFileChunks] of routeChunks) {
|
|
2259
|
+
const currentRouteFiles = new Set(routeFileChunks.map((chunk) => chunk.fileName));
|
|
2260
|
+
const chunksByFileName = new Map(chunks.filter((chunk) => !routeChunkFileNames.has(chunk.fileName) || currentRouteFiles.has(chunk.fileName)).map((chunk) => [chunk.fileName, chunk]));
|
|
2261
|
+
const scope = {
|
|
2262
|
+
chunks: chunksByFileName,
|
|
2263
|
+
everyChunk
|
|
2264
|
+
};
|
|
2265
|
+
const landingScope = {
|
|
2266
|
+
chunks: new Map([...chunksByFileName].filter(([, chunk]) => !isMarklessNavigationPack(chunk.name))),
|
|
2267
|
+
everyChunk
|
|
2268
|
+
};
|
|
1573
2269
|
const routeChunk = primaryRouteChunk(input.root, routeFile, routeFileChunks);
|
|
2270
|
+
const landingChunks = routeFileChunks.filter((chunk) => !isMarklessNavigationPack(chunk.name) && !isNavigationOnlyRouteChunk(input.root, routeFile, chunk));
|
|
2271
|
+
const landingChunk = landingChunks.length ? primaryRouteChunk(input.root, routeFile, landingChunks) : routeChunk;
|
|
1574
2272
|
const navigationFileNames = /* @__PURE__ */ new Set();
|
|
1575
2273
|
if (input.navigationChunk) {
|
|
1576
|
-
includeChunk(navigationFileNames,
|
|
1577
|
-
|
|
2274
|
+
includeChunk(navigationFileNames, scope, input.navigationChunk.fileName);
|
|
2275
|
+
const renderPathChunks = new Map(chunksByFileName);
|
|
2276
|
+
for (const chunk of deferredChunks) renderPathChunks.set(chunk.fileName, chunk);
|
|
2277
|
+
const renderPath = /* @__PURE__ */ new Set();
|
|
2278
|
+
for (const fileName of input.navigationChunk.dynamicImports) if (!routeChunkFileNames.has(fileName) && !polyfillFileNames.has(fileName)) includeChunk(renderPath, scope, fileName, true);
|
|
2279
|
+
for (const fileName of renderPath) {
|
|
2280
|
+
navigationFileNames.add(fileName);
|
|
2281
|
+
if (resumeRuntimeFileNames.has(fileName)) continue;
|
|
2282
|
+
const chunk = everyChunk.get(fileName);
|
|
2283
|
+
for (const target of [...chunk.dynamicImports, ...codeDynamicImports(chunk)]) if (deferredFileNames.has(target)) includeChunk(navigationFileNames, {
|
|
2284
|
+
chunks: renderPathChunks,
|
|
2285
|
+
everyChunk
|
|
2286
|
+
}, target, true);
|
|
2287
|
+
}
|
|
1578
2288
|
}
|
|
1579
|
-
includeChunk(navigationFileNames,
|
|
1580
|
-
includeChunk(navigationFileNames,
|
|
1581
|
-
if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames,
|
|
2289
|
+
includeChunk(navigationFileNames, scope, routeChunk.fileName);
|
|
2290
|
+
includeChunk(navigationFileNames, scope, routeChunk.fileName, true);
|
|
2291
|
+
if (input.navigationChunk) for (const fileName of routeScopedDynamicImports(input.navigationChunk, routeFile)) includeChunk(navigationFileNames, scope, fileName, true);
|
|
1582
2292
|
const ssrFileNames = /* @__PURE__ */ new Set();
|
|
1583
2293
|
if (input.resumeChunk) {
|
|
1584
|
-
includeChunk(ssrFileNames,
|
|
1585
|
-
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames,
|
|
2294
|
+
includeChunk(ssrFileNames, landingScope, input.resumeChunk.fileName);
|
|
2295
|
+
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1586
2296
|
}
|
|
1587
2297
|
if (input.prerenderWakeChunk) {
|
|
1588
|
-
includeChunk(ssrFileNames,
|
|
1589
|
-
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames,
|
|
2298
|
+
includeChunk(ssrFileNames, landingScope, input.prerenderWakeChunk.fileName);
|
|
2299
|
+
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1590
2300
|
}
|
|
1591
|
-
includeChunk(ssrFileNames,
|
|
1592
|
-
const routeSourceFiles = sourceFilesForChunks(new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
2301
|
+
includeChunk(ssrFileNames, landingScope, landingChunk.fileName, true);
|
|
2302
|
+
const routeSourceFiles = sourceFilesForChunks(/* @__PURE__ */ new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
2303
|
+
let routeHasSymbols = false;
|
|
1593
2304
|
for (const [sourceFile, symbolFileNames] of symbolChunks) {
|
|
1594
2305
|
if (!routeSourceFiles.has(sourceFile)) continue;
|
|
2306
|
+
routeHasSymbols = true;
|
|
1595
2307
|
for (const fileName of symbolFileNames) {
|
|
1596
|
-
includeChunk(navigationFileNames,
|
|
1597
|
-
includeChunk(ssrFileNames,
|
|
2308
|
+
includeChunk(navigationFileNames, scope, fileName, true);
|
|
2309
|
+
includeChunk(ssrFileNames, landingScope, fileName, true);
|
|
1598
2310
|
}
|
|
1599
2311
|
}
|
|
2312
|
+
const styleFileNames = /* @__PURE__ */ new Set([...navigationFileNames, ...ssrFileNames]);
|
|
2313
|
+
const planned = plannedLanding?.get(routeFile);
|
|
2314
|
+
if (planned) narrowToPlanned(ssrFileNames, planned, landingScope, [input.resumeChunk, input.prerenderWakeChunk]);
|
|
2315
|
+
for (const fileName of ssrFileNames) for (const specifier of warmEvaluatedImports(everyChunk.get(fileName)?.code ?? "")) includeChunk(ssrFileNames, landingScope, normalize(join(dirname(fileName), specifier)));
|
|
1600
2316
|
navigation[routeFile] = [...navigationFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1601
|
-
|
|
1602
|
-
|
|
2317
|
+
const firstEventPath = /* @__PURE__ */ new Set();
|
|
2318
|
+
const lazySiblings = [...ssrFileNames].filter((fileName) => {
|
|
2319
|
+
const name = everyChunk.get(fileName)?.name;
|
|
2320
|
+
return !!name && startupPackOfLazy(name) !== name;
|
|
2321
|
+
});
|
|
2322
|
+
for (const entry of lazySiblings.length ? [input.resumeChunk, input.prerenderWakeChunk] : []) {
|
|
2323
|
+
if (!entry) continue;
|
|
2324
|
+
includeChunk(firstEventPath, landingScope, entry.fileName);
|
|
2325
|
+
for (const fileName of routeScopedDynamicImports(entry, routeFile)) includeChunk(firstEventPath, landingScope, fileName);
|
|
2326
|
+
}
|
|
2327
|
+
for (const fileName of lazySiblings) includeChunk(firstEventPath, landingScope, fileName);
|
|
2328
|
+
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));
|
|
2329
|
+
styles[routeFile] = routeStylesheetsForChunks(routeFileChunks, styleFileNames, chunksByFileName, input.base);
|
|
1603
2330
|
}
|
|
1604
2331
|
return {
|
|
1605
2332
|
navigation,
|
|
@@ -1607,7 +2334,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1607
2334
|
styles
|
|
1608
2335
|
};
|
|
1609
2336
|
}
|
|
1610
|
-
function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
2337
|
+
function routeStylesheetsForChunks(routeFileChunks, closureFileNames, chunksByFileName, base) {
|
|
1611
2338
|
const styles = /* @__PURE__ */ new Set();
|
|
1612
2339
|
const visited = /* @__PURE__ */ new Set();
|
|
1613
2340
|
const visit = (chunk) => {
|
|
@@ -1619,18 +2346,25 @@ function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
|
1619
2346
|
}
|
|
1620
2347
|
for (const stylesheet of chunk.viteMetadata?.importedCss ?? []) styles.add(joinURL(base, stylesheet));
|
|
1621
2348
|
};
|
|
2349
|
+
for (const fileName of closureFileNames) {
|
|
2350
|
+
const chunk = chunksByFileName.get(fileName);
|
|
2351
|
+
if (chunk) visit(chunk);
|
|
2352
|
+
}
|
|
1622
2353
|
for (const chunk of routeFileChunks) visit(chunk);
|
|
1623
2354
|
return [...styles];
|
|
1624
2355
|
}
|
|
1625
2356
|
function symbolChunksBySourceFile(chunks) {
|
|
1626
2357
|
const bySourceFile = /* @__PURE__ */ new Map();
|
|
1627
|
-
for (const chunk of chunks)
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
2358
|
+
for (const chunk of chunks) {
|
|
2359
|
+
const moduleIds = chunk.facadeModuleId ? [...chunk.moduleIds ?? [], chunk.facadeModuleId] : chunk.moduleIds ?? [];
|
|
2360
|
+
for (const moduleId of moduleIds) {
|
|
2361
|
+
const sourceFile = symbolVirtualModuleSourceFile(moduleId);
|
|
2362
|
+
if (!sourceFile) continue;
|
|
2363
|
+
const key = sourceModulePathname(sourceFile);
|
|
2364
|
+
const fileNames = bySourceFile.get(key) ?? [];
|
|
2365
|
+
if (!fileNames.includes(chunk.fileName)) fileNames.push(chunk.fileName);
|
|
2366
|
+
bySourceFile.set(key, fileNames);
|
|
2367
|
+
}
|
|
1634
2368
|
}
|
|
1635
2369
|
return bySourceFile;
|
|
1636
2370
|
}
|
|
@@ -1646,22 +2380,23 @@ function sourceFilesForChunks(fileNames, chunksByFileName) {
|
|
|
1646
2380
|
function sourceModulePathname(moduleId) {
|
|
1647
2381
|
return decodePath(parseURL(moduleId).pathname);
|
|
1648
2382
|
}
|
|
1649
|
-
function includeChunk(fileNames,
|
|
1650
|
-
if (
|
|
1651
|
-
const chunk =
|
|
2383
|
+
function includeChunk(fileNames, scope, fileName, includeDynamic = false, visited = /* @__PURE__ */ new Set(), requiredByStaticImport = false) {
|
|
2384
|
+
if (visited.has(fileName)) return;
|
|
2385
|
+
const chunk = (requiredByStaticImport ? scope.everyChunk : scope.chunks).get(fileName);
|
|
1652
2386
|
if (!chunk) return;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
for (const
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2387
|
+
visited.add(fileName);
|
|
2388
|
+
fileNames.add(fileName);
|
|
2389
|
+
for (const imported of /* @__PURE__ */ new Set([...chunk.imports, ...codeStaticImports(chunk)])) includeChunk(fileNames, scope, imported, includeDynamic, visited, true);
|
|
2390
|
+
if (!includeDynamic) return;
|
|
2391
|
+
for (const imported of /* @__PURE__ */ new Set([...chunk.dynamicImports, ...codeDynamicImports(chunk)])) includeChunk(fileNames, scope, imported, true, visited);
|
|
2392
|
+
}
|
|
2393
|
+
function narrowToPlanned(fileNames, planned, scope, entries) {
|
|
2394
|
+
const kept = /* @__PURE__ */ new Set();
|
|
2395
|
+
for (const entry of entries) if (entry) includeChunk(kept, scope, entry.fileName);
|
|
2396
|
+
for (const fileName of fileNames) if (planned.has(fileName)) includeChunk(kept, scope, fileName, false, /* @__PURE__ */ new Set(), true);
|
|
2397
|
+
const ordered = [...fileNames].filter((fileName) => kept.has(fileName));
|
|
2398
|
+
fileNames.clear();
|
|
2399
|
+
for (const fileName of [...ordered, ...kept]) fileNames.add(fileName);
|
|
1665
2400
|
}
|
|
1666
2401
|
function routeScopedDynamicImports(chunk, routeFile) {
|
|
1667
2402
|
if (!chunk.code) return [];
|
|
@@ -1675,10 +2410,23 @@ function routeScopedDynamicImports(chunk, routeFile) {
|
|
|
1675
2410
|
return [...imports];
|
|
1676
2411
|
}
|
|
1677
2412
|
function codeStaticImports(chunk) {
|
|
1678
|
-
return
|
|
2413
|
+
return cachedCodeImports(chunk).static;
|
|
1679
2414
|
}
|
|
1680
2415
|
function codeDynamicImports(chunk) {
|
|
1681
|
-
return
|
|
2416
|
+
return cachedCodeImports(chunk).dynamic;
|
|
2417
|
+
}
|
|
2418
|
+
const chunkCodeImports = /* @__PURE__ */ new WeakMap();
|
|
2419
|
+
function cachedCodeImports(chunk) {
|
|
2420
|
+
const cached = chunkCodeImports.get(chunk);
|
|
2421
|
+
if (cached?.code === chunk.code && cached?.fileName === chunk.fileName) return cached;
|
|
2422
|
+
const imports = {
|
|
2423
|
+
code: chunk.code,
|
|
2424
|
+
fileName: chunk.fileName,
|
|
2425
|
+
static: codeImportSpecifiers(chunk, /(?:import\s*(?:[^('"`]*?\bfrom\s*)?|export\s*[^('"`]*?\bfrom\s*)["'](\.\/[^"']+\.js)["']/g),
|
|
2426
|
+
dynamic: codeDynamicImportSpecifiers(chunk.code ?? "").map((specifier) => normalize(join(dirname(chunk.fileName), specifier)))
|
|
2427
|
+
};
|
|
2428
|
+
chunkCodeImports.set(chunk, imports);
|
|
2429
|
+
return imports;
|
|
1682
2430
|
}
|
|
1683
2431
|
function codeDynamicImportSpecifiers(code) {
|
|
1684
2432
|
const imports = /* @__PURE__ */ new Set();
|
|
@@ -1697,6 +2445,15 @@ function codeImportSpecifiers(chunk, pattern) {
|
|
|
1697
2445
|
}
|
|
1698
2446
|
return [...imports];
|
|
1699
2447
|
}
|
|
2448
|
+
function chunkRenamesSince(captured, bundle) {
|
|
2449
|
+
const finalNames = new Map(outputChunks(bundle).map((chunk) => [chunk.preliminaryFileName, chunk.fileName]));
|
|
2450
|
+
const renames = /* @__PURE__ */ new Map();
|
|
2451
|
+
for (const [fileName, preliminary] of captured) {
|
|
2452
|
+
const next = preliminary === void 0 ? void 0 : finalNames.get(preliminary);
|
|
2453
|
+
if (next !== void 0 && next !== fileName) renames.set(fileName, next);
|
|
2454
|
+
}
|
|
2455
|
+
return renames;
|
|
2456
|
+
}
|
|
1700
2457
|
function outputChunks(bundle) {
|
|
1701
2458
|
return Object.values(bundle).filter((item) => {
|
|
1702
2459
|
return isRecord(item) && item.type === "chunk" && typeof item.fileName === "string" && Array.isArray(item.imports) && Array.isArray(item.dynamicImports);
|
|
@@ -1709,24 +2466,26 @@ function routeFileForChunk(root, chunk) {
|
|
|
1709
2466
|
}
|
|
1710
2467
|
}
|
|
1711
2468
|
function primaryRouteChunk(root, routeFile, chunks) {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
2469
|
+
const ranked = chunks.map((chunk) => ({
|
|
2470
|
+
chunk,
|
|
2471
|
+
rank: routeChunkRank(root, routeFile, chunk)
|
|
2472
|
+
}));
|
|
2473
|
+
const highestRank = Math.max(...ranked.map(({ rank }) => rank));
|
|
2474
|
+
const candidates = ranked.filter(({ rank }) => rank === highestRank);
|
|
2475
|
+
if (candidates.length !== 1) throw new Error(`Markless Router found ambiguous primary chunks for ${routeFile}: ${candidates.map(({ chunk }) => chunk.fileName).sort().join(", ")}`);
|
|
2476
|
+
return candidates[0].chunk;
|
|
2477
|
+
}
|
|
2478
|
+
function isNavigationOnlyRouteChunk(root, routeFile, chunk) {
|
|
2479
|
+
const owned = [chunk.facadeModuleId, ...chunk.moduleIds ?? []].filter((moduleId) => !!moduleId && routeFileForModuleId(root, moduleId) === routeFile);
|
|
2480
|
+
return owned.length > 0 && owned.every(isRouteNavigationSourceRequest);
|
|
1721
2481
|
}
|
|
1722
2482
|
function routeChunkRank(root, routeFile, chunk) {
|
|
1723
2483
|
let rank = 0;
|
|
1724
2484
|
for (const moduleId of [chunk.facadeModuleId, ...chunk.moduleIds ?? []]) {
|
|
1725
|
-
if (routeFileForModuleId(root, moduleId) !== routeFile) continue;
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
else return 3;
|
|
2485
|
+
if (!moduleId || routeFileForModuleId(root, moduleId) !== routeFile) continue;
|
|
2486
|
+
const role = isClientPrimarySourceRequest(moduleId) ? 5 : isRenderDataSourceRequest(moduleId) ? 4 : isSymbolOnlySourceRequest(moduleId) ? 1 : isResumeSourceRequest(moduleId) ? 2 : 3;
|
|
2487
|
+
const ownsModule = chunk.moduleIds?.includes(moduleId) ? 1 : 0;
|
|
2488
|
+
rank = Math.max(rank, role * 2 + ownsModule);
|
|
1730
2489
|
}
|
|
1731
2490
|
return rank;
|
|
1732
2491
|
}
|
|
@@ -1747,5 +2506,8 @@ function configRoot(config) {
|
|
|
1747
2506
|
const root = config.root;
|
|
1748
2507
|
return typeof root === "string" ? root : ".";
|
|
1749
2508
|
}
|
|
2509
|
+
function escapeRegExp(value) {
|
|
2510
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2511
|
+
}
|
|
1750
2512
|
//#endregion
|
|
1751
2513
|
export { router };
|