@markless/router 0.2.2 → 0.3.1
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/README.md +1 -1
- package/dist/{debug-channel-C7v9u5SM.js → debug-channel-CSzo-Zj8.js} +13 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.js +49 -16
- package/dist/vite/runtime/create-server-entry.d.ts +1 -0
- package/dist/vite/runtime/create-server-entry.js +48 -15
- package/dist/vite/runtime/mdx-route.d.ts +27 -5
- package/dist/vite/runtime/mdx-route.js +138 -21
- package/dist/vite.d.ts +3 -2
- package/dist/vite.js +172 -59
- package/package.json +6 -6
- package/src/vite/entries/client-entry.ts +3 -1
- package/src/vite/entries/prerender-wake-entry.ts +40 -0
- package/src/vite/entries/server-entry.ts +2 -0
package/README.md
CHANGED
|
@@ -4,4 +4,4 @@ Markless router: client-side navigation with SSR streaming and build-time
|
|
|
4
4
|
preload maps. Used through `@markless/core/router` and the
|
|
5
5
|
`@markless/router/vite` plugin.
|
|
6
6
|
|
|
7
|
-
See the repository for documentation: https://github.com/
|
|
7
|
+
See the repository for documentation: https://github.com/compiled-run/markless
|
|
@@ -28,6 +28,7 @@ function installDebugChannelLayer(root, initialPhase, initiallyActive = true) {
|
|
|
28
28
|
const rootKey = priorContainer?.root.key ?? `root:${seed}`;
|
|
29
29
|
const rootRef = new WeakRef(root);
|
|
30
30
|
const events = /* @__PURE__ */ new WeakMap();
|
|
31
|
+
const delegatedEvents = /* @__PURE__ */ new Set();
|
|
31
32
|
const keys = /* @__PURE__ */ new WeakMap();
|
|
32
33
|
const invalidated = /* @__PURE__ */ new WeakSet();
|
|
33
34
|
const routerSources = global[Symbol.for("markless.debug.channel.v1.router-sources")] ??= /* @__PURE__ */ new Set();
|
|
@@ -99,6 +100,13 @@ function installDebugChannelLayer(root, initialPhase, initiallyActive = true) {
|
|
|
99
100
|
}
|
|
100
101
|
const interaction = events.get(element)?.get(eventName);
|
|
101
102
|
if (interaction) return freeze({ ...interaction });
|
|
103
|
+
if (delegatedEvents.has(eventName)) return freeze({
|
|
104
|
+
kind: "inline-resumer",
|
|
105
|
+
source: "ssr-inline",
|
|
106
|
+
containerId: id,
|
|
107
|
+
elementKey: keyFor(element),
|
|
108
|
+
eventName
|
|
109
|
+
});
|
|
102
110
|
if (invalidated.has(element)) return none(element, eventName, "not-registered");
|
|
103
111
|
try {
|
|
104
112
|
const prior = previous?.explainInteraction(element, eventName);
|
|
@@ -209,6 +217,10 @@ function installDebugChannelLayer(root, initialPhase, initiallyActive = true) {
|
|
|
209
217
|
eventName
|
|
210
218
|
}));
|
|
211
219
|
},
|
|
220
|
+
delegated(eventNames) {
|
|
221
|
+
if (lifecycle === "disposed") return;
|
|
222
|
+
for (const eventName of eventNames) delegatedEvents.add(eventName);
|
|
223
|
+
},
|
|
212
224
|
violation: pushViolation,
|
|
213
225
|
router: (source) => void routerSources.add(source),
|
|
214
226
|
boundaries(value) {
|
|
@@ -236,7 +248,7 @@ function safely(run) {
|
|
|
236
248
|
}
|
|
237
249
|
}
|
|
238
250
|
function __marklessDebugBootstrapSource() {
|
|
239
|
-
return `((root,phase,active)=>{const controls=(${installDebugChannelLayer.toString()})(root,phase,active);return Object.freeze({record:(element,eventName,input)=>controls.record(element,eventName,input),router:(source)=>controls.router(source),activate:()=>controls.activate()})})`;
|
|
251
|
+
return `((root,phase,active)=>{const controls=(${installDebugChannelLayer.toString()})(root,phase,active);return Object.freeze({record:(element,eventName,input)=>controls.record(element,eventName,input),delegated:(eventNames)=>controls.delegated(eventNames),router:(source)=>controls.router(source),activate:()=>controls.activate()})})`;
|
|
240
252
|
}
|
|
241
253
|
function __marklessDebugRegisterRouter(root, source) {
|
|
242
254
|
if (root) safely(() => (moduleRoots.get(root) ?? controlsFor(root, "ssr-resume")).router(source));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createExternalDelegateStructureTokens, createExternalDelegateView } from "@markless/web/fns/external-delegate";
|
|
2
|
+
|
|
1
3
|
//#region src/route-manifest.d.ts
|
|
2
4
|
interface RouteManifestRoute {
|
|
3
5
|
readonly pathname: string;
|
|
@@ -186,15 +188,25 @@ declare const Html: ((props: {
|
|
|
186
188
|
readonly children?: unknown;
|
|
187
189
|
}): {
|
|
188
190
|
html: string;
|
|
191
|
+
elementCount: number;
|
|
189
192
|
};
|
|
190
193
|
};
|
|
191
|
-
|
|
194
|
+
type LinkComponent = {
|
|
195
|
+
(props?: LinkProps): unknown;
|
|
192
196
|
renderCsr(props?: LinkProps): {
|
|
193
197
|
root: HTMLAnchorElement;
|
|
198
|
+
view: ReturnType<typeof createExternalDelegateView>;
|
|
199
|
+
liveHostNodes: Map<string, HTMLAnchorElement>;
|
|
194
200
|
};
|
|
195
|
-
renderSsr(props?: LinkProps
|
|
201
|
+
renderSsr(props?: LinkProps, renderContext?: {
|
|
202
|
+
readonly idPrefix?: string;
|
|
203
|
+
}): {
|
|
196
204
|
html: string;
|
|
205
|
+
elementCount: number;
|
|
206
|
+
structureTokens: ReturnType<typeof createExternalDelegateStructureTokens>;
|
|
207
|
+
view: ReturnType<typeof createExternalDelegateView>;
|
|
197
208
|
};
|
|
198
209
|
};
|
|
210
|
+
declare const Link: LinkComponent;
|
|
199
211
|
//#endregion
|
|
200
212
|
export { type ApiRequestFileCache, type ApiRequestFileMethod, type ApiRequestFileRoute, AppLocals, DefaultLinkProps, EndpointHttpContext, Html, HttpContext, HttpResponse, Link, LinkNavigationProps, LinkProps, MARKLESS_ROUTER_ROUTE_EVENT, MarklessRouterGeneratedRoutes, MiddlewareHttpContext, PageProps, type RequestFileDefaultExport, type RequestFileDiagnostic, type RequestFileDiagnosticCode, type RequestFileParam, type RequestFileParseResult, type RequestFileTransformResult, type RouteDocumentModule, type RouteManifest, type RouteManifestMatch, type RouteManifestParam, type RouteManifestRoute, type RouteManifestStatusPages, type RoutePageModule, type RouteState, type RouteUpdate, __marklessCreateHttpContext, __marklessRouterStartSpaNavigation, buildRouteManifestFromFileIds, dispatchRouteUpdate, ensureNavigationRuntime, matchRouteManifest, normalizeRequestFileId, normalizeRequestPathname, normalizeRouteFileId, parseRequestFile, routePageProps, startRouteUpdateRenderer, transformRequestFileSource };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { i as normalizeRouteFileId, n as matchRouteManifest, r as normalizeRequestPathname, t as buildRouteManifestFromFileIds } from "./route-manifest-DPgGIyrS.js";
|
|
2
2
|
import { n as parseRequestFile, r as transformRequestFileSource, t as normalizeRequestFileId } from "./request-files-C-0LLSsx.js";
|
|
3
|
+
import { createExternalDelegateStructureTokens, createExternalDelegateView } from "@markless/web/fns/external-delegate";
|
|
3
4
|
import { render } from "@markless/web/render";
|
|
4
5
|
import { MARKLESS_PENDING_MIN_VISIBLE_MS as MARKLESS_NAV_PENDING_MIN_MS, settleOrPendingDeadline } from "@markless/web";
|
|
5
6
|
//#region src/navigation-hold.ts
|
|
@@ -31,6 +32,7 @@ function dispatchRouteUpdate(document, update) {
|
|
|
31
32
|
}
|
|
32
33
|
//#endregion
|
|
33
34
|
//#region src/route-renderer.ts
|
|
35
|
+
const CURRENT_ROUTE_CONTAINER = "__marklessRouterCurrentRouteContainer";
|
|
34
36
|
function startRouteUpdateRenderer(document = window.document) {
|
|
35
37
|
const state = document;
|
|
36
38
|
if (state["__marklessRouterRouteRendererStarted"]) return;
|
|
@@ -43,18 +45,25 @@ async function renderRouteUpdate(document, update) {
|
|
|
43
45
|
try {
|
|
44
46
|
const artifact = update.page.default;
|
|
45
47
|
if (!artifact || update.signal?.aborted) return;
|
|
48
|
+
if (!artifact.renderData) throw new Error(`MARKLESS_ROUTER_RENDER_DATA_MISSING: Navigated route ${JSON.stringify(update.route.file)} has no linked render-data module.`);
|
|
46
49
|
const props = routePageProps(update.route);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const state = document;
|
|
51
|
+
const container = await render({
|
|
52
|
+
renderData: artifact.renderData,
|
|
53
|
+
loadSymbol: artifact.loadSymbol,
|
|
54
|
+
props
|
|
55
|
+
}, {
|
|
56
|
+
target: document.body,
|
|
57
|
+
beforeMount: async (incoming) => {
|
|
58
|
+
if (await holdNavigationSwapUntilSettled({
|
|
59
|
+
runtime: incoming.runtime,
|
|
52
60
|
signal: update.signal
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
}) === false) return false;
|
|
62
|
+
state[CURRENT_ROUTE_CONTAINER]?.runtime?.dispose?.();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (!update.signal?.aborted) state[CURRENT_ROUTE_CONTAINER] = container;
|
|
58
67
|
} finally {
|
|
59
68
|
update.onRendered?.();
|
|
60
69
|
}
|
|
@@ -89,8 +98,8 @@ async function __marklessRouterStartSpaNavigation(options) {
|
|
|
89
98
|
navigation.addEventListener("navigate", (event) => {
|
|
90
99
|
handleNavigateEvent(event, context);
|
|
91
100
|
});
|
|
92
|
-
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-
|
|
93
|
-
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-
|
|
101
|
+
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-CSzo-Zj8.js").then((n) => n.n).then((debug) => debug.__marklessDebugRegisterRouter(void 0, "spa-click-listener")).catch(() => {});
|
|
102
|
+
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-CSzo-Zj8.js").then((n) => n.n).then((debug) => debug.__marklessDebugRegisterRouter(void 0, "navigation-event")).catch(() => {});
|
|
94
103
|
{
|
|
95
104
|
const bootUrl = parseSameOriginUrl(runtimeWindow.location.href, runtimeWindow.location.href);
|
|
96
105
|
const bootHashPath = bootUrl ? hashRoutePath(bootUrl) : void 0;
|
|
@@ -228,7 +237,10 @@ function __marklessCreateHttpContext(event) {
|
|
|
228
237
|
const Html = Object.assign(function Html(props) {
|
|
229
238
|
return props.children;
|
|
230
239
|
}, { renderSsr(props = {}) {
|
|
231
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
html: props.children == null ? "" : String(props.children),
|
|
242
|
+
elementCount: 0
|
|
243
|
+
};
|
|
232
244
|
} });
|
|
233
245
|
const Link = Object.assign(function Link(props = {}) {
|
|
234
246
|
return props.children;
|
|
@@ -237,12 +249,33 @@ const Link = Object.assign(function Link(props = {}) {
|
|
|
237
249
|
const anchor = document.createElement("a");
|
|
238
250
|
for (const [name, value] of linkAnchorAttributes(props)) anchor.setAttribute(name, value);
|
|
239
251
|
anchor.innerHTML = linkChildrenHtml(props);
|
|
240
|
-
return {
|
|
252
|
+
return {
|
|
253
|
+
root: anchor,
|
|
254
|
+
view: linkExternalDelegateView(),
|
|
255
|
+
liveHostNodes: new Map([["router:link", anchor]])
|
|
256
|
+
};
|
|
241
257
|
},
|
|
242
|
-
renderSsr(props = {}) {
|
|
243
|
-
return {
|
|
258
|
+
renderSsr(props = {}, renderContext) {
|
|
259
|
+
return {
|
|
260
|
+
html: `<a ${linkAnchorAttributes(props).map(([name, value]) => value === "" ? name : `${name}="${escapeHtml(value)}"`).join(" ")}>${linkChildrenHtml(props)}</a>`,
|
|
261
|
+
elementCount: 1,
|
|
262
|
+
structureTokens: createExternalDelegateStructureTokens({
|
|
263
|
+
hostNodeId: "router:link",
|
|
264
|
+
idPrefix: renderContext?.idPrefix,
|
|
265
|
+
tagName: "a"
|
|
266
|
+
}),
|
|
267
|
+
view: linkExternalDelegateView()
|
|
268
|
+
};
|
|
244
269
|
}
|
|
245
270
|
});
|
|
271
|
+
function linkExternalDelegateView() {
|
|
272
|
+
return createExternalDelegateView({
|
|
273
|
+
eventName: "click",
|
|
274
|
+
hostNodeId: "router:link",
|
|
275
|
+
owner: "router",
|
|
276
|
+
tagName: "a"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
246
279
|
function linkAnchorAttributes(props) {
|
|
247
280
|
const attributes = /* @__PURE__ */ new Map();
|
|
248
281
|
const input = props;
|
|
@@ -5,6 +5,7 @@ interface ServerEntryOptions {
|
|
|
5
5
|
readonly dev?: boolean;
|
|
6
6
|
readonly navigationEntryPath?: string;
|
|
7
7
|
readonly resumeEntryPath?: string;
|
|
8
|
+
readonly prerenderWakeEntryPath?: string;
|
|
8
9
|
readonly routeModulePreloads?: Record<string, readonly ModulePreloadInput[]>;
|
|
9
10
|
readonly routeSsrModulePreloads?: Record<string, readonly ModulePreloadInput[]>;
|
|
10
11
|
readonly routeStylesheets?: Record<string, readonly string[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as __marklessDebugBootstrapSource } from "../../debug-channel-
|
|
1
|
+
import { t as __marklessDebugBootstrapSource } from "../../debug-channel-CSzo-Zj8.js";
|
|
2
2
|
import { i as normalizeRouteFileId, n as matchRouteManifest, t as buildRouteManifestFromFileIds } from "../../route-manifest-DPgGIyrS.js";
|
|
3
3
|
import { renderToString } from "@markless/web/render-to-string";
|
|
4
4
|
import { renderToStream } from "@markless/web/render-to-stream";
|
|
@@ -80,20 +80,24 @@ function createServerEntry(options) {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
const pageArtifact = routedPageArtifact(renderSsr, baseArtifact, pageProps, file, options.navigationEntryPath, options.routeStylesheets?.[file]);
|
|
83
|
+
const routedArtifact = options.prerenderWakeEntryPath ? {
|
|
84
|
+
...pageArtifact,
|
|
85
|
+
prerenderWakeModuleUrl: options.prerenderWakeEntryPath
|
|
86
|
+
} : pageArtifact;
|
|
83
87
|
const renderOptions = {
|
|
84
88
|
props: pageProps,
|
|
85
89
|
resumeModuleUrl: options.resumeEntryPath ?? baseArtifact?.resumeModuleUrl,
|
|
86
90
|
modulePreloads: (html) => modulePreloadsForPage(file, html, pageProps.url.href, manifest, options.routeModulePreloads, options.routeSsrModulePreloads)
|
|
87
91
|
};
|
|
88
92
|
if (options.render === "blocking") {
|
|
89
|
-
const pageHtml = splitLeadingHeadHtml(await renderToString(
|
|
93
|
+
const pageHtml = splitLeadingHeadHtml(await renderToString(routedArtifact, renderOptions));
|
|
90
94
|
const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml);
|
|
91
95
|
return new Response(fillDocumentChildren(shell, pageHtml.bodyHtml), {
|
|
92
96
|
status,
|
|
93
97
|
headers
|
|
94
98
|
});
|
|
95
99
|
}
|
|
96
|
-
const stream = await renderToStream(
|
|
100
|
+
const stream = await renderToStream(routedArtifact, renderOptions);
|
|
97
101
|
const pageHtml = splitLeadingHeadHtml(stream.shell);
|
|
98
102
|
const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml);
|
|
99
103
|
if (stream.pendingArmCount === 0) return new Response(fillDocumentChildren(shell, pageHtml.bodyHtml), {
|
|
@@ -104,18 +108,34 @@ function createServerEntry(options) {
|
|
|
104
108
|
const prefix = placeholderAt === -1 ? shell : shell.slice(0, placeholderAt) + pageHtml.bodyHtml;
|
|
105
109
|
const suffix = placeholderAt === -1 ? "" : shell.slice(placeholderAt + 37);
|
|
106
110
|
const encoder = new TextEncoder();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
let cancelled = false;
|
|
112
|
+
let completed = false;
|
|
113
|
+
const body = new ReadableStream({
|
|
114
|
+
async start(controller) {
|
|
115
|
+
controller.enqueue(encoder.encode(prefix));
|
|
116
|
+
try {
|
|
117
|
+
for await (const chunk of stream.appends()) {
|
|
118
|
+
if (cancelled) return;
|
|
119
|
+
controller.enqueue(encoder.encode(chunk));
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (cancelled) return;
|
|
123
|
+
console.error("[markless-router] streaming settle failed:", error);
|
|
124
|
+
controller.error(error);
|
|
125
|
+
completed = true;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (cancelled) return;
|
|
129
|
+
controller.enqueue(encoder.encode(suffix));
|
|
130
|
+
controller.close();
|
|
131
|
+
completed = true;
|
|
132
|
+
},
|
|
133
|
+
cancel(reason) {
|
|
134
|
+
if (completed) return;
|
|
135
|
+
cancelled = true;
|
|
136
|
+
stream.cancel(reason);
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
controller.close();
|
|
118
|
-
} });
|
|
138
|
+
});
|
|
119
139
|
return new Response(body, {
|
|
120
140
|
status,
|
|
121
141
|
headers
|
|
@@ -261,7 +281,20 @@ function fillDocumentChildren(documentShell, children) {
|
|
|
261
281
|
return documentShell.slice(0, at) + children + documentShell.slice(at + 37);
|
|
262
282
|
}
|
|
263
283
|
async function renderDocumentModule(documentModule, props) {
|
|
264
|
-
|
|
284
|
+
const output = await documentModule?.default?.renderSsr?.(props);
|
|
285
|
+
assertDocumentStorageFree(output);
|
|
286
|
+
return output?.html;
|
|
287
|
+
}
|
|
288
|
+
function assertDocumentStorageFree(output) {
|
|
289
|
+
const storage = documentStorageRecords(output?.state);
|
|
290
|
+
if (storage.length === 0) return;
|
|
291
|
+
const keys = storage.map((record) => record.key).join(", ");
|
|
292
|
+
throw new Error(`MARKLESS_ROUTER_DOCUMENT_STORAGE_UNSUPPORTED: the document declares storage cells (${keys}), but the router serves only the document's HTML, so their state payload never reaches the browser and they can never resume. Declare storage() at module scope in a component the page renders instead.`);
|
|
293
|
+
}
|
|
294
|
+
function documentStorageRecords(state) {
|
|
295
|
+
if (!state || typeof state !== "object") return [];
|
|
296
|
+
const storage = state.storage;
|
|
297
|
+
return Array.isArray(storage) ? storage : [];
|
|
265
298
|
}
|
|
266
299
|
function insertHeadHtml(documentHtml, headHtml) {
|
|
267
300
|
if (!headHtml) return documentHtml;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
type MdxRoutePart = {
|
|
3
3
|
readonly kind: 'html';
|
|
4
4
|
readonly elementCount: number;
|
|
5
|
+
readonly html?: string;
|
|
6
|
+
readonly elementTags?: ReadonlyArray<string>;
|
|
5
7
|
} | {
|
|
6
8
|
readonly kind: 'component';
|
|
7
9
|
readonly componentIndex: number;
|
|
@@ -12,7 +14,6 @@ type MdxRenderOutput = {
|
|
|
12
14
|
readonly state?: MdxStatePayload;
|
|
13
15
|
readonly view?: MdxViewPayload;
|
|
14
16
|
readonly loadSymbol?: (symbolId: string) => unknown;
|
|
15
|
-
readonly connectRuntime?: (context: unknown) => unknown;
|
|
16
17
|
};
|
|
17
18
|
type MdxChild = {
|
|
18
19
|
readonly componentIndex: number;
|
|
@@ -28,11 +29,34 @@ type MdxSymbolLoader = {
|
|
|
28
29
|
readonly prefix: string;
|
|
29
30
|
readonly loadSymbol: (symbolId: string) => unknown;
|
|
30
31
|
};
|
|
32
|
+
type MdxRenderDataSurface = {
|
|
33
|
+
readonly rootComponentName: string | null;
|
|
34
|
+
readonly renderData: Readonly<Record<string, unknown>>;
|
|
35
|
+
readonly components: Readonly<Record<string, unknown>>;
|
|
36
|
+
readonly imports: Readonly<Record<string, MdxRenderDataSurface>>;
|
|
37
|
+
};
|
|
38
|
+
type MdxRenderDataChild = {
|
|
39
|
+
readonly componentIndex: number;
|
|
40
|
+
readonly hostPrefix: string;
|
|
41
|
+
readonly symbolPrefix: string;
|
|
42
|
+
readonly props: Readonly<Record<string, unknown>>;
|
|
43
|
+
readonly surface: MdxRenderDataSurface;
|
|
44
|
+
};
|
|
45
|
+
declare function createMdxRenderDataSurface(parts: ReadonlyArray<MdxRoutePart>, children: ReadonlyArray<MdxRenderDataChild>): MdxRenderDataSurface;
|
|
46
|
+
type MdxComputedRecord = {
|
|
47
|
+
readonly deriveSymbolId?: string;
|
|
48
|
+
readonly [key: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
type MdxStorageRecord = {
|
|
51
|
+
readonly graphNodeId: string;
|
|
52
|
+
readonly key: string;
|
|
53
|
+
};
|
|
31
54
|
type MdxStatePayload = {
|
|
32
55
|
readonly version: unknown;
|
|
33
56
|
readonly cells?: readonly unknown[];
|
|
34
|
-
readonly computed?: readonly
|
|
57
|
+
readonly computed?: readonly MdxComputedRecord[];
|
|
35
58
|
readonly sharedDefinitions?: readonly unknown[];
|
|
59
|
+
readonly storage?: readonly MdxStorageRecord[];
|
|
36
60
|
};
|
|
37
61
|
type MdxLocator = {
|
|
38
62
|
readonly hostNodeId: string;
|
|
@@ -59,10 +83,8 @@ type MdxViewPayload = {
|
|
|
59
83
|
readonly asyncBoundaries?: readonly unknown[];
|
|
60
84
|
};
|
|
61
85
|
declare function renderMdxChild(children: MdxChild[], component: MdxComponentArtifact, props: unknown, child: Omit<MdxChild, 'output'>): Promise<string>;
|
|
62
|
-
declare function rootFromMdxHtml(html: string): Element;
|
|
63
|
-
declare function replaceMdxChild(root: ParentNode, index: number, child: ChildNode | undefined): void;
|
|
64
86
|
declare function composeMdxState(children: readonly MdxChild[]): MdxStatePayload | undefined;
|
|
65
87
|
declare function composeMdxView(parts: readonly MdxRoutePart[], children: readonly MdxChild[], initialElementOffset: number): MdxViewPayload | undefined;
|
|
66
88
|
declare function loadMdxSymbol(symbolId: string, children: readonly MdxChild[], loaders: readonly MdxSymbolLoader[]): unknown;
|
|
67
89
|
//#endregion
|
|
68
|
-
export { MdxChild, MdxComponentArtifact, MdxRenderOutput, MdxRoutePart, MdxSymbolLoader, composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
90
|
+
export { MdxChild, MdxComponentArtifact, MdxRenderOutput, MdxRoutePart, MdxSymbolLoader, composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild };
|
|
@@ -1,4 +1,125 @@
|
|
|
1
|
+
function protocolStateVersion(storage) {
|
|
2
|
+
return (storage?.length ?? 0) > 0 ? 2 : 1;
|
|
3
|
+
}
|
|
4
|
+
//#endregion
|
|
1
5
|
//#region src/vite/runtime/mdx-route.ts
|
|
6
|
+
function createMdxRenderDataSurface(parts, children) {
|
|
7
|
+
const componentName = "MarklessMdxRoute";
|
|
8
|
+
const rootChunkId = `template:${componentName}`;
|
|
9
|
+
const childrenByIndex = new Map(children.map((child) => [child.componentIndex, child]));
|
|
10
|
+
const statics = [];
|
|
11
|
+
const slots = [];
|
|
12
|
+
const hosts = [{
|
|
13
|
+
hostNodeId: "__mdx:root",
|
|
14
|
+
tagName: "main",
|
|
15
|
+
coordinate: {
|
|
16
|
+
kind: "child-index",
|
|
17
|
+
path: [0]
|
|
18
|
+
}
|
|
19
|
+
}];
|
|
20
|
+
const edges = [];
|
|
21
|
+
let staticHtml = "<main data-markless-mdx-root>";
|
|
22
|
+
let slotIndex = 0;
|
|
23
|
+
for (const [partIndex, part] of parts.entries()) {
|
|
24
|
+
if (part.kind === "html") {
|
|
25
|
+
staticHtml += part.html ?? "";
|
|
26
|
+
for (const [tagIndex, tagName] of (part.elementTags ?? []).entries()) hosts.push({
|
|
27
|
+
hostNodeId: `__mdx:static:${partIndex}:${tagIndex}`,
|
|
28
|
+
tagName,
|
|
29
|
+
coordinate: {
|
|
30
|
+
kind: "child-index",
|
|
31
|
+
path: [
|
|
32
|
+
0,
|
|
33
|
+
partIndex,
|
|
34
|
+
tagIndex
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const child = childrenByIndex.get(part.componentIndex);
|
|
41
|
+
if (!child) throw new Error(`MARKLESS_MDX_RENDER_DATA_CHILD_MISSING: ${part.componentIndex}`);
|
|
42
|
+
const childComponentName = child.surface.rootComponentName;
|
|
43
|
+
if (!childComponentName) throw new Error(`MARKLESS_MDX_RENDER_DATA_ROOT_MISSING: ${part.componentIndex}`);
|
|
44
|
+
const edgeId = `mdx:edge:${part.componentIndex}`;
|
|
45
|
+
staticHtml += `<!--markless-slot:${slotIndex}-->`;
|
|
46
|
+
statics.push(staticHtml);
|
|
47
|
+
staticHtml = "";
|
|
48
|
+
slots.push({
|
|
49
|
+
kind: "child-component",
|
|
50
|
+
componentEdgeId: edgeId,
|
|
51
|
+
childComponentName,
|
|
52
|
+
childTemplateId: `template:${childComponentName}`,
|
|
53
|
+
coordinate: {
|
|
54
|
+
kind: "comment-anchor",
|
|
55
|
+
path: [0, partIndex]
|
|
56
|
+
},
|
|
57
|
+
staticIndex: slotIndex
|
|
58
|
+
});
|
|
59
|
+
edges.push({
|
|
60
|
+
id: edgeId,
|
|
61
|
+
childComponentName,
|
|
62
|
+
hostPrefix: child.hostPrefix,
|
|
63
|
+
symbolPrefix: child.symbolPrefix,
|
|
64
|
+
props: Object.entries(child.props).map(([name, value]) => ({
|
|
65
|
+
name,
|
|
66
|
+
kind: "serializable",
|
|
67
|
+
value
|
|
68
|
+
}))
|
|
69
|
+
});
|
|
70
|
+
slotIndex++;
|
|
71
|
+
}
|
|
72
|
+
statics.push(`${staticHtml}</main>`);
|
|
73
|
+
const state = {
|
|
74
|
+
version: 1,
|
|
75
|
+
cells: [],
|
|
76
|
+
computed: []
|
|
77
|
+
};
|
|
78
|
+
const view = {
|
|
79
|
+
version: 1,
|
|
80
|
+
locators: [],
|
|
81
|
+
events: [],
|
|
82
|
+
domUpdates: [],
|
|
83
|
+
behaviors: [],
|
|
84
|
+
elementHandles: [],
|
|
85
|
+
asyncBoundaries: []
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
rootComponentName: componentName,
|
|
89
|
+
renderData: {
|
|
90
|
+
root: {
|
|
91
|
+
componentName,
|
|
92
|
+
templateId: rootChunkId
|
|
93
|
+
},
|
|
94
|
+
chunks: [{
|
|
95
|
+
id: rootChunkId,
|
|
96
|
+
kind: "template",
|
|
97
|
+
componentName,
|
|
98
|
+
statics,
|
|
99
|
+
hosts,
|
|
100
|
+
slots
|
|
101
|
+
}],
|
|
102
|
+
initialValues: [],
|
|
103
|
+
branches: [],
|
|
104
|
+
repeats: [],
|
|
105
|
+
boundaries: [],
|
|
106
|
+
interactions: []
|
|
107
|
+
},
|
|
108
|
+
components: { [componentName]: {
|
|
109
|
+
name: componentName,
|
|
110
|
+
state,
|
|
111
|
+
view,
|
|
112
|
+
rootChunkId,
|
|
113
|
+
stateGraphNodeIds: [],
|
|
114
|
+
initialValues: [],
|
|
115
|
+
branches: [],
|
|
116
|
+
boundaries: [],
|
|
117
|
+
edges,
|
|
118
|
+
propCellId: null
|
|
119
|
+
} },
|
|
120
|
+
imports: Object.fromEntries(children.flatMap((child) => child.surface.rootComponentName ? [[child.surface.rootComponentName, child.surface]] : []))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
2
123
|
async function renderMdxChild(children, component, props, child) {
|
|
3
124
|
const output = await component.renderSsr?.(props);
|
|
4
125
|
if (output) children.push({
|
|
@@ -7,29 +128,19 @@ async function renderMdxChild(children, component, props, child) {
|
|
|
7
128
|
});
|
|
8
129
|
return output?.html ?? "";
|
|
9
130
|
}
|
|
10
|
-
function rootFromMdxHtml(html) {
|
|
11
|
-
const template = document.createElement("template");
|
|
12
|
-
template.innerHTML = html;
|
|
13
|
-
const root = template.content.firstElementChild;
|
|
14
|
-
if (!root) throw new Error("Markless Router MDX render did not produce a root element.");
|
|
15
|
-
return root;
|
|
16
|
-
}
|
|
17
|
-
function replaceMdxChild(root, index, child) {
|
|
18
|
-
const placeholder = root.querySelector?.(`[data-markless-mdx-child="${index}"]`);
|
|
19
|
-
if (placeholder && child) {
|
|
20
|
-
placeholder.replaceWith(child);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
placeholder?.remove();
|
|
24
|
-
}
|
|
25
131
|
function composeMdxState(children) {
|
|
26
|
-
const childStates = children.map((child) =>
|
|
132
|
+
const childStates = children.map((child) => ({
|
|
133
|
+
child,
|
|
134
|
+
state: child.output?.state
|
|
135
|
+
})).filter((entry) => isDefined(entry.state));
|
|
27
136
|
if (childStates.length === 0) return;
|
|
137
|
+
const storage = childStates.flatMap(({ state }) => state.storage ?? []);
|
|
28
138
|
return {
|
|
29
|
-
version:
|
|
30
|
-
cells: childStates.flatMap((state) => state.cells ?? []),
|
|
31
|
-
computed: childStates.flatMap((state) => state.computed ?? []),
|
|
32
|
-
...childStates.some((state) => state.sharedDefinitions?.length) ? { sharedDefinitions: childStates.flatMap((state) => state.sharedDefinitions ?? []) } : {}
|
|
139
|
+
version: protocolStateVersion(storage),
|
|
140
|
+
cells: childStates.flatMap(({ state }) => state.cells ?? []),
|
|
141
|
+
computed: childStates.flatMap(({ child, state }) => (state.computed ?? []).map((record) => prefixMdxComputedRecord(record, child))),
|
|
142
|
+
...childStates.some(({ state }) => state.sharedDefinitions?.length) ? { sharedDefinitions: childStates.flatMap(({ state }) => state.sharedDefinitions ?? []) } : {},
|
|
143
|
+
...storage.length > 0 ? { storage } : {}
|
|
33
144
|
};
|
|
34
145
|
}
|
|
35
146
|
function composeMdxView(parts, children, initialElementOffset) {
|
|
@@ -99,6 +210,12 @@ function appendMdxChildView(context) {
|
|
|
99
210
|
hostNodeId: context.child.hostPrefix + handle.hostNodeId
|
|
100
211
|
});
|
|
101
212
|
}
|
|
213
|
+
function prefixMdxComputedRecord(record, child) {
|
|
214
|
+
return {
|
|
215
|
+
...record,
|
|
216
|
+
...record.deriveSymbolId ? { deriveSymbolId: child.symbolPrefix + record.deriveSymbolId } : {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
102
219
|
function prefixMdxSymbolRecord(record, child) {
|
|
103
220
|
return {
|
|
104
221
|
...record,
|
|
@@ -110,4 +227,4 @@ function isDefined(value) {
|
|
|
110
227
|
return value !== void 0;
|
|
111
228
|
}
|
|
112
229
|
//#endregion
|
|
113
|
-
export { composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
230
|
+
export { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild };
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PluginOption } from "vite";
|
|
1
|
+
import { PluginOption, UserConfig } from "vite";
|
|
2
2
|
|
|
3
3
|
//#region src/vite/index.d.ts
|
|
4
4
|
interface MarklessRouterOptions {
|
|
@@ -6,5 +6,6 @@ interface MarklessRouterOptions {
|
|
|
6
6
|
nitro?: boolean;
|
|
7
7
|
}
|
|
8
8
|
declare function router(options?: MarklessRouterOptions): PluginOption[];
|
|
9
|
+
type WatchIgnorePattern = Extract<NonNullable<NonNullable<NonNullable<UserConfig['server']>['watch']>['ignored']>, readonly unknown[]>[number];
|
|
9
10
|
//#endregion
|
|
10
|
-
export { MarklessRouterOptions, router };
|
|
11
|
+
export { MarklessRouterOptions, WatchIgnorePattern, router };
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { t as buildRouteManifestFromFileIds } from "./route-manifest-DPgGIyrS.js";
|
|
2
2
|
import { r as transformRequestFileSource } from "./request-files-C-0LLSsx.js";
|
|
3
|
+
import { __marklessRouteHref } from "./vite/route-href.js";
|
|
3
4
|
import { dirname, extname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
4
|
-
import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withoutLeadingSlash } from "ufo";
|
|
5
|
+
import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutLeadingSlash } from "ufo";
|
|
5
6
|
import { existsSync } from "node:fs";
|
|
6
7
|
import { nitro } from "nitro/vite";
|
|
7
8
|
import { sortUserPlugins } from "vite";
|
|
@@ -229,6 +230,7 @@ function transformAnchorSource(code, astInput, routePatterns) {
|
|
|
229
230
|
const ast = node$1(astInput);
|
|
230
231
|
if (!ast) return code;
|
|
231
232
|
const edits = [];
|
|
233
|
+
let needsHrefHelper = false;
|
|
232
234
|
const linkNames = marklessRouterLinkImportNames(ast);
|
|
233
235
|
walk(ast, (current) => {
|
|
234
236
|
if (current.type !== "JSXOpeningElement") return;
|
|
@@ -245,17 +247,58 @@ function transformAnchorSource(code, astInput, routePatterns) {
|
|
|
245
247
|
const paramsExpression = jsxExpression(node$1(params.value));
|
|
246
248
|
if (!paramsExpression) fail$1(missingParamsMessage(hrefValue, routeParams));
|
|
247
249
|
validateObjectLiteralParams(hrefValue, routeParams, paramsExpression);
|
|
248
|
-
|
|
250
|
+
const staticParams = staticObjectValue(paramsExpression);
|
|
251
|
+
if (staticParams) edits.push({
|
|
249
252
|
...rangeOf(href),
|
|
250
|
-
text: `href
|
|
253
|
+
text: `href=${JSON.stringify(__marklessRouteHref(hrefValue, staticParams))}`
|
|
251
254
|
});
|
|
255
|
+
else {
|
|
256
|
+
needsHrefHelper = true;
|
|
257
|
+
edits.push({
|
|
258
|
+
...rangeOf(href),
|
|
259
|
+
text: `href={__marklessRouteHref(${JSON.stringify(hrefValue)}, ${slice$1(code, paramsExpression)})}`
|
|
260
|
+
});
|
|
261
|
+
}
|
|
252
262
|
edits.push({
|
|
253
263
|
...attributeRemovalRange(code, params),
|
|
254
264
|
text: ""
|
|
255
265
|
});
|
|
256
266
|
});
|
|
257
267
|
if (edits.length === 0) return code;
|
|
258
|
-
return `${helperImport()}${applyEdits(code, edits)}`;
|
|
268
|
+
return `${needsHrefHelper ? helperImport() : ""}${applyEdits(code, edits)}`;
|
|
269
|
+
}
|
|
270
|
+
function staticObjectValue(expression) {
|
|
271
|
+
if (expression.type !== "ObjectExpression") return void 0;
|
|
272
|
+
const value = {};
|
|
273
|
+
for (const property of nodes$1(expression.properties)) {
|
|
274
|
+
if (property.type !== "Property" || property.computed === true) return void 0;
|
|
275
|
+
const key = node$1(property.key);
|
|
276
|
+
const name = key?.type === "Identifier" ? string$1(key.name) : literalString(key);
|
|
277
|
+
const propertyValue = staticExpressionValue(node$1(property.value));
|
|
278
|
+
if (!name || !propertyValue.known) return void 0;
|
|
279
|
+
value[name] = propertyValue.value;
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
function staticExpressionValue(expression) {
|
|
284
|
+
if (!expression) return { known: false };
|
|
285
|
+
if (expression.type === "Literal") return {
|
|
286
|
+
known: true,
|
|
287
|
+
value: expression.value
|
|
288
|
+
};
|
|
289
|
+
if (expression.type === "ArrayExpression") {
|
|
290
|
+
const values = [];
|
|
291
|
+
for (const element of nodes$1(expression.elements)) {
|
|
292
|
+
const value = staticExpressionValue(element);
|
|
293
|
+
if (!value.known) return { known: false };
|
|
294
|
+
values.push(value.value);
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
known: true,
|
|
298
|
+
value: values
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return { known: false };
|
|
259
302
|
}
|
|
260
303
|
function marklessRouterLinkImportNames(ast) {
|
|
261
304
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -383,6 +426,7 @@ function createClientAssetsManifest(input) {
|
|
|
383
426
|
base: input.base,
|
|
384
427
|
entries: {
|
|
385
428
|
resume: input.resumeEntry,
|
|
429
|
+
...input.prerenderWakeEntry ? { prerenderWake: input.prerenderWakeEntry } : {},
|
|
386
430
|
navigation: input.navigationEntry
|
|
387
431
|
},
|
|
388
432
|
routes: {
|
|
@@ -459,6 +503,7 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
459
503
|
if (!isRecord$1(input.routes)) throw clientAssetsManifestError("has invalid route maps");
|
|
460
504
|
const entries = {
|
|
461
505
|
resume: requiredString(input.entries.resume, "entries.resume"),
|
|
506
|
+
...input.entries.prerenderWake !== void 0 ? { prerenderWake: requiredString(input.entries.prerenderWake, "entries.prerenderWake") } : {},
|
|
462
507
|
navigation: requiredString(input.entries.navigation, "entries.navigation")
|
|
463
508
|
};
|
|
464
509
|
const routes = {
|
|
@@ -469,6 +514,7 @@ async function validateClientAssetsManifest(input, base, clientOutDir) {
|
|
|
469
514
|
assertMatchingRouteKeys(routes);
|
|
470
515
|
const hrefs = new Set([
|
|
471
516
|
entries.resume,
|
|
517
|
+
...entries.prerenderWake !== void 0 ? [entries.prerenderWake] : [],
|
|
472
518
|
entries.navigation,
|
|
473
519
|
...Object.values(routes.navigation).flat(),
|
|
474
520
|
...Object.values(routes.ssr).flat(),
|
|
@@ -677,7 +723,12 @@ async function transformMdxRoute(source, id) {
|
|
|
677
723
|
const route = parseMdxRoute(source, id);
|
|
678
724
|
const html = route.parts.map((part) => part.kind === "html" ? part.html : "").join("");
|
|
679
725
|
if (route.components.length === 0) return [
|
|
726
|
+
`import { createMdxRenderDataSurface } from '@markless/router/vite/runtime/mdx-route';`,
|
|
727
|
+
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
728
|
+
"const marklessMdxRenderData = createMdxRenderDataSurface(marklessMdxParts, []);",
|
|
680
729
|
"const marklessMdxPage = {",
|
|
730
|
+
" renderData: marklessMdxRenderData,",
|
|
731
|
+
" storageSeeds: marklessMdxStorageSeeds,",
|
|
681
732
|
" renderSsr() {",
|
|
682
733
|
` return { html: ${JSON.stringify(html)} };`,
|
|
683
734
|
" }",
|
|
@@ -685,43 +736,30 @@ async function transformMdxRoute(source, id) {
|
|
|
685
736
|
"export default marklessMdxPage;",
|
|
686
737
|
""
|
|
687
738
|
].join("\n");
|
|
688
|
-
return emitComposedMdxRoute(route);
|
|
739
|
+
return emitComposedMdxRoute(route, id);
|
|
689
740
|
}
|
|
690
|
-
function emitComposedMdxRoute(route) {
|
|
741
|
+
function emitComposedMdxRoute(route, id) {
|
|
691
742
|
return [
|
|
692
743
|
`import { resumeFromPayloadDocument } from '@markless/core/web/resume';`,
|
|
693
|
-
`import { composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
744
|
+
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild } from '@markless/router/vite/runtime/mdx-route';`,
|
|
694
745
|
...route.imports,
|
|
695
746
|
"",
|
|
696
747
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
697
748
|
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
749
|
+
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id)};`,
|
|
750
|
+
`const marklessMdxStorageSeeds = ${renderStorageSeeds(route.components)};`,
|
|
698
751
|
"",
|
|
699
752
|
"const marklessMdxPage = {",
|
|
753
|
+
" renderData: marklessMdxRenderData,",
|
|
754
|
+
" storageSeeds: marklessMdxStorageSeeds,",
|
|
755
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
700
756
|
" async renderSsr(props = {}) {",
|
|
701
757
|
" const marklessMdxChildren = [];",
|
|
702
|
-
` const html = ${renderHtmlExpression(route
|
|
758
|
+
` const html = ${renderHtmlExpression(route)};`,
|
|
703
759
|
" const state = composeMdxState(marklessMdxChildren);",
|
|
704
760
|
" const view = composeMdxView(marklessMdxParts, marklessMdxChildren, 0);",
|
|
705
761
|
" return { html, ...(state ? { state } : {}), ...(view ? { view } : {}) };",
|
|
706
762
|
" },",
|
|
707
|
-
" renderCsr(props = {}) {",
|
|
708
|
-
" const marklessMdxChildren = [];",
|
|
709
|
-
` const root = rootFromMdxHtml(${renderCsrHtml(route)});`,
|
|
710
|
-
...route.components.flatMap((component, index) => [
|
|
711
|
-
` const marklessMdxChild${index} = ${component.localName}.renderCsr?.(${componentPropsExpression(component)});`,
|
|
712
|
-
` replaceMdxChild(root, ${index}, marklessMdxChild${index}?.root);`,
|
|
713
|
-
` if (marklessMdxChild${index}) marklessMdxChildren.push({ componentIndex: ${index}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)}, output: marklessMdxChild${index} });`
|
|
714
|
-
]),
|
|
715
|
-
" const state = composeMdxState(marklessMdxChildren);",
|
|
716
|
-
" const view = composeMdxView(marklessMdxParts, marklessMdxChildren, 1);",
|
|
717
|
-
" return {",
|
|
718
|
-
" root,",
|
|
719
|
-
" ...(state ? { state } : {}),",
|
|
720
|
-
" ...(view ? { view } : {}),",
|
|
721
|
-
" loadSymbol(symbolId) { return marklessMdxLoadSymbol(symbolId, marklessMdxChildren); },",
|
|
722
|
-
" connectRuntime(context) { for (const child of marklessMdxChildren) child.output?.connectRuntime?.(context); },",
|
|
723
|
-
" };",
|
|
724
|
-
" },",
|
|
725
763
|
...renderMdxPreload(route.components),
|
|
726
764
|
"};",
|
|
727
765
|
"export default marklessMdxPage;",
|
|
@@ -776,7 +814,7 @@ function parseMdxRoute(source, id) {
|
|
|
776
814
|
return {
|
|
777
815
|
imports,
|
|
778
816
|
components,
|
|
779
|
-
parts: routePartsFromHast(root.children
|
|
817
|
+
parts: routePartsFromHast("children" in root ? root.children : [], {
|
|
780
818
|
components,
|
|
781
819
|
id,
|
|
782
820
|
importedComponents
|
|
@@ -826,6 +864,7 @@ function componentPart(node, context) {
|
|
|
826
864
|
prefix: `m${componentIndex}:`,
|
|
827
865
|
props: children ? [...props, {
|
|
828
866
|
name: "children",
|
|
867
|
+
value: children,
|
|
829
868
|
valueExpression: JSON.stringify(children)
|
|
830
869
|
}] : props
|
|
831
870
|
});
|
|
@@ -841,11 +880,15 @@ function appendHtmlPart(parts, nodes, id) {
|
|
|
841
880
|
type: "root",
|
|
842
881
|
children: staticNodes
|
|
843
882
|
}, { allowDangerousHtml: true });
|
|
844
|
-
if (html)
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
883
|
+
if (html) {
|
|
884
|
+
const elementTags = hastElementTags(staticNodes);
|
|
885
|
+
parts.push({
|
|
886
|
+
kind: "html",
|
|
887
|
+
html,
|
|
888
|
+
elementCount: elementTags.length,
|
|
889
|
+
elementTags
|
|
890
|
+
});
|
|
891
|
+
}
|
|
849
892
|
}
|
|
850
893
|
function renderStaticHtml(nodes, id) {
|
|
851
894
|
if (nodes.length === 0) return "";
|
|
@@ -867,6 +910,7 @@ function normalizeStaticHastNode(node, id) {
|
|
|
867
910
|
value: value == null ? "" : String(value)
|
|
868
911
|
}];
|
|
869
912
|
}
|
|
913
|
+
if (node.type === "root") return normalizeStaticHastNodes(node.children, id);
|
|
870
914
|
if ("children" in node && Array.isArray(node.children)) return [{
|
|
871
915
|
...node,
|
|
872
916
|
children: normalizeStaticHastNodes(node.children, id)
|
|
@@ -881,15 +925,19 @@ function componentProp(attribute, id) {
|
|
|
881
925
|
const value = attribute.value;
|
|
882
926
|
if (value == null) return {
|
|
883
927
|
name: attribute.name,
|
|
928
|
+
value: true,
|
|
884
929
|
valueExpression: "true"
|
|
885
930
|
};
|
|
886
931
|
if (typeof value === "string") return {
|
|
887
932
|
name: attribute.name,
|
|
933
|
+
value,
|
|
888
934
|
valueExpression: JSON.stringify(value)
|
|
889
935
|
};
|
|
936
|
+
const literal = literalSafeExpressionValue(value.value, id);
|
|
890
937
|
return {
|
|
891
938
|
name: attribute.name,
|
|
892
|
-
|
|
939
|
+
value: literal,
|
|
940
|
+
valueExpression: literalValueExpression(literal, id)
|
|
893
941
|
};
|
|
894
942
|
}
|
|
895
943
|
function literalSafeExpressionValue(source, id) {
|
|
@@ -918,8 +966,15 @@ function parseMdxExpressionValue(source, id) {
|
|
|
918
966
|
dropHandle(handle);
|
|
919
967
|
}
|
|
920
968
|
}
|
|
969
|
+
function literalPropertyName(key) {
|
|
970
|
+
if (key.type === "Identifier" && typeof key.name === "string") return key.name;
|
|
971
|
+
if (key.type === "Literal" && typeof key.value === "string") return key.value;
|
|
972
|
+
}
|
|
973
|
+
function isEstreeNode(value) {
|
|
974
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
975
|
+
}
|
|
921
976
|
function evaluateLiteralExpression(node, id) {
|
|
922
|
-
if (!node) throw literalExpressionError(id);
|
|
977
|
+
if (!isEstreeNode(node)) throw literalExpressionError(id);
|
|
923
978
|
if (node.type === "Literal") {
|
|
924
979
|
const value = node.value;
|
|
925
980
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
@@ -938,7 +993,7 @@ function evaluateLiteralExpression(node, id) {
|
|
|
938
993
|
for (const property of node.properties) {
|
|
939
994
|
if (property.type !== "Property" || property.kind !== "init" || property.method || property.computed) throw literalExpressionError(id);
|
|
940
995
|
const key = property.key;
|
|
941
|
-
const name = key
|
|
996
|
+
const name = isEstreeNode(key) ? literalPropertyName(key) : void 0;
|
|
942
997
|
if (!name) throw literalExpressionError(id);
|
|
943
998
|
value[name] = evaluateLiteralExpression(property.value, id);
|
|
944
999
|
}
|
|
@@ -956,10 +1011,9 @@ function literalValueExpression(value, id) {
|
|
|
956
1011
|
function literalExpressionError(id) {
|
|
957
1012
|
return /* @__PURE__ */ new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
|
|
958
1013
|
}
|
|
959
|
-
function renderHtmlExpression(route
|
|
1014
|
+
function renderHtmlExpression(route) {
|
|
960
1015
|
return joinExpressions(route.parts.map((part) => {
|
|
961
1016
|
if (part.kind === "html") return JSON.stringify(part.html);
|
|
962
|
-
if (mode === "csr") return JSON.stringify(`<span data-markless-mdx-child="${part.componentIndex}"></span>`);
|
|
963
1017
|
const component = route.components[part.componentIndex];
|
|
964
1018
|
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)} }))`;
|
|
965
1019
|
}));
|
|
@@ -968,24 +1022,44 @@ function componentPropsExpression(component) {
|
|
|
968
1022
|
if (component.props.length === 0) return "{}";
|
|
969
1023
|
return `{ ${component.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.valueExpression}`).join(", ")} }`;
|
|
970
1024
|
}
|
|
971
|
-
function renderCsrHtml(route) {
|
|
972
|
-
return `"<main data-markless-mdx-root>" + ${renderHtmlExpression(route, "csr")} + "</main>"`;
|
|
973
|
-
}
|
|
974
1025
|
function joinExpressions(expressions) {
|
|
975
1026
|
const filtered = expressions.filter((expression) => expression !== "\"\"");
|
|
976
1027
|
return filtered.length === 0 ? "\"\"" : filtered.join(" + ");
|
|
977
1028
|
}
|
|
978
|
-
function
|
|
979
|
-
|
|
1029
|
+
function hastElementTags(nodes) {
|
|
1030
|
+
const tags = [];
|
|
980
1031
|
for (const node of nodes) {
|
|
981
|
-
if (node.type === "element")
|
|
982
|
-
if ("children" in node && Array.isArray(node.children))
|
|
1032
|
+
if (node.type === "element") tags.push(String(node.tagName));
|
|
1033
|
+
if ("children" in node && Array.isArray(node.children)) tags.push(...hastElementTags(node.children));
|
|
983
1034
|
}
|
|
984
|
-
return
|
|
1035
|
+
return tags;
|
|
1036
|
+
}
|
|
1037
|
+
function renderStorageSeeds(components) {
|
|
1038
|
+
const localNames = [...new Set(components.map((component) => component.localName))];
|
|
1039
|
+
return localNames.length === 0 ? "[]" : `[${localNames.map((localName) => `...(${localName}.storageSeeds ?? [])`).join(", ")}]`;
|
|
985
1040
|
}
|
|
986
1041
|
function renderSymbolLoaders(components) {
|
|
987
1042
|
return `[${components.map((component) => `{ prefix: ${JSON.stringify(component.prefix)}, loadSymbol(symbolId) { return import(${JSON.stringify(`${component.specifier}?markless-symbols`)}).then((mod) => mod.loadSymbol(symbolId.slice(${component.prefix.length}))); } }`).join(", ")}]`;
|
|
988
1043
|
}
|
|
1044
|
+
function renderMdxRenderDataLoader(components, id) {
|
|
1045
|
+
const reachedFrom = mdxMaterializedReachRoot(id);
|
|
1046
|
+
const imports = components.map((component) => {
|
|
1047
|
+
const source = withQuery(component.specifier, {
|
|
1048
|
+
"markless-render-data": null,
|
|
1049
|
+
...reachedFrom ? { "markless-reached-from": reachedFrom } : {}
|
|
1050
|
+
});
|
|
1051
|
+
return `import(${JSON.stringify(source)})`;
|
|
1052
|
+
});
|
|
1053
|
+
const exportName = reachedFrom ? "marklessPrerenderData" : "marklessRenderData";
|
|
1054
|
+
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
|
+
return `() => Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
|
|
1056
|
+
}
|
|
1057
|
+
function mdxMaterializedReachRoot(id) {
|
|
1058
|
+
const query = new URLSearchParams(parsePath(id).search);
|
|
1059
|
+
const reachedFrom = query.get("markless-reached-from");
|
|
1060
|
+
if (reachedFrom) return reachedFrom;
|
|
1061
|
+
return query.has("markless-route") ? decodePath(parseURL(id).pathname) : void 0;
|
|
1062
|
+
}
|
|
989
1063
|
function isMdxEsm(node) {
|
|
990
1064
|
return node.type === "mdxjsEsm";
|
|
991
1065
|
}
|
|
@@ -1005,6 +1079,9 @@ const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
|
1005
1079
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
1006
1080
|
const RESUME_ENTRY_ORIGIN = "/entries/resume-entry.ts";
|
|
1007
1081
|
const RESUME_ENTRY_PATH_ID = "virtual:markless-router/resume-entry-path";
|
|
1082
|
+
const PRERENDER_WAKE_ENTRY_ID = "virtual:markless-router/prerender-wake-entry";
|
|
1083
|
+
const PRERENDER_WAKE_ENTRY_ORIGIN = "/entries/prerender-wake-entry.ts";
|
|
1084
|
+
const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-entry-path";
|
|
1008
1085
|
const NAVIGATION_ENTRY_ID = "virtual:markless-router/navigation-entry";
|
|
1009
1086
|
const NAVIGATION_ENTRY_ORIGIN = "/entries/client-entry.ts";
|
|
1010
1087
|
const NAVIGATION_ENTRY_PATH_ID = "virtual:markless-router/navigation-entry-path";
|
|
@@ -1013,7 +1090,7 @@ const ROUTE_PRELOADS_PLACEHOLDER = "__MARKLESS_ROUTER_ROUTE_PRELOADS__";
|
|
|
1013
1090
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1014
1091
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1015
1092
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
1016
|
-
const PUBLIC_VIRTUAL_MODULE_ID_RE = /^virtual:markless-router\/(?:routes|client-entry|resume-entry|resume-entry-path|navigation-entry|navigation-entry-path|route-preloads|server-entry|route-href|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)(?:\?.*)?$/;
|
|
1017
1094
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1018
1095
|
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "src/vite/entries");
|
|
1019
1096
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1026,11 +1103,13 @@ const virtualEntryFiles = {
|
|
|
1026
1103
|
[ROUTE_DISCOVERY_ID]: "route-discovery.ts",
|
|
1027
1104
|
[CLIENT_ENTRY_ID]: "client-entry.ts",
|
|
1028
1105
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1106
|
+
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1029
1107
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1030
1108
|
[SERVER_ENTRY_ID]: "server-entry.ts",
|
|
1031
1109
|
[ROUTE_HREF_ID]: "route-href.ts"
|
|
1032
1110
|
};
|
|
1033
1111
|
function router(options = {}) {
|
|
1112
|
+
prerenderWakeChannelCaptured = process.env.MARKLESS_PRERENDER_WAKE === "1";
|
|
1034
1113
|
if (options.nitro === false) return [
|
|
1035
1114
|
mdxTransformPlugin(),
|
|
1036
1115
|
requestFileTransformPlugin(),
|
|
@@ -1041,17 +1120,18 @@ function router(options = {}) {
|
|
|
1041
1120
|
];
|
|
1042
1121
|
const nitroPlugins = nitro();
|
|
1043
1122
|
const resumeEntry = resumeEntryState();
|
|
1123
|
+
const prerenderWakeEntry = resumeEntryState();
|
|
1044
1124
|
const navigationEntry = resumeEntryState();
|
|
1045
1125
|
const routePreloads = routePreloadState();
|
|
1046
1126
|
return [
|
|
1047
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, navigationEntry, routePreloads),
|
|
1127
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1048
1128
|
devSourceModuleRequestPlugin(),
|
|
1049
1129
|
mdxTransformPlugin(),
|
|
1050
1130
|
requestFileTransformPlugin(),
|
|
1051
1131
|
routeTypegenPlugin(),
|
|
1052
1132
|
anchorTransformPlugin(),
|
|
1053
1133
|
htmlTransformPlugin(),
|
|
1054
|
-
virtualModulesPlugin(resumeEntry, navigationEntry, routePreloads),
|
|
1134
|
+
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1055
1135
|
nitroPlugins
|
|
1056
1136
|
];
|
|
1057
1137
|
}
|
|
@@ -1075,7 +1155,7 @@ function devSourceModuleRequestPlugin() {
|
|
|
1075
1155
|
}
|
|
1076
1156
|
};
|
|
1077
1157
|
}
|
|
1078
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry, routePreloads) {
|
|
1158
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads) {
|
|
1079
1159
|
let clientOutDir = "";
|
|
1080
1160
|
let clientEnvironmentName = "";
|
|
1081
1161
|
let clientEnvironmentConfig;
|
|
@@ -1115,6 +1195,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1115
1195
|
}
|
|
1116
1196
|
for (const entry of [
|
|
1117
1197
|
resumeEntry,
|
|
1198
|
+
prerenderWakeEntry,
|
|
1118
1199
|
navigationEntry,
|
|
1119
1200
|
routePreloads
|
|
1120
1201
|
]) entry.base = config.base;
|
|
@@ -1132,6 +1213,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1132
1213
|
if (isClientEnvironment) {
|
|
1133
1214
|
pendingClientAssets = void 0;
|
|
1134
1215
|
resumeEntry.fileName = void 0;
|
|
1216
|
+
prerenderWakeEntry.fileName = void 0;
|
|
1135
1217
|
navigationEntry.fileName = void 0;
|
|
1136
1218
|
routePreloads.routes = {
|
|
1137
1219
|
navigation: {},
|
|
@@ -1145,6 +1227,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1145
1227
|
if (!isRouterServerEnvironment) return;
|
|
1146
1228
|
const manifest = await readClientAssetsManifest(clientOutDir, routePreloads.base);
|
|
1147
1229
|
resumeEntry.fileName = clientAssetFileName(manifest.entries.resume, manifest.base);
|
|
1230
|
+
prerenderWakeEntry.fileName = manifest.entries.prerenderWake ? clientAssetFileName(manifest.entries.prerenderWake, manifest.base) : void 0;
|
|
1148
1231
|
navigationEntry.fileName = clientAssetFileName(manifest.entries.navigation, manifest.base);
|
|
1149
1232
|
routePreloads.routes = manifest.routes;
|
|
1150
1233
|
routePreloads.persisted = true;
|
|
@@ -1155,21 +1238,26 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1155
1238
|
if (this.environment?.config.consumer !== "client") return;
|
|
1156
1239
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1157
1240
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
1241
|
+
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1242
|
+
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1158
1243
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
1159
1244
|
if (navigationChunk) navigationEntry.fileName = navigationChunk.fileName;
|
|
1160
1245
|
routePreloads.routes = routeModulePreloadsFromBundle({
|
|
1161
1246
|
base: routePreloads.base,
|
|
1162
1247
|
bundle,
|
|
1163
1248
|
navigationChunk,
|
|
1249
|
+
prerenderWakeChunk,
|
|
1164
1250
|
resumeChunk,
|
|
1165
1251
|
root: routePreloads.root
|
|
1166
1252
|
});
|
|
1167
1253
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
1168
1254
|
if (productionBuild) {
|
|
1169
|
-
|
|
1255
|
+
const wakeRequired = prerenderWakeChannelEnabled();
|
|
1256
|
+
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.");
|
|
1170
1257
|
pendingClientAssets = createClientAssetsManifest({
|
|
1171
1258
|
base: routePreloads.base,
|
|
1172
1259
|
resumeEntry: joinURL(routePreloads.base, resumeEntry.fileName),
|
|
1260
|
+
...prerenderWakeEntry.fileName ? { prerenderWakeEntry: joinURL(routePreloads.base, prerenderWakeEntry.fileName) } : {},
|
|
1173
1261
|
navigationEntry: joinURL(routePreloads.base, navigationEntry.fileName),
|
|
1174
1262
|
routes: routePreloads.routes
|
|
1175
1263
|
});
|
|
@@ -1275,15 +1363,18 @@ function withWatchIgnores(watchOptions, extraIgnored = []) {
|
|
|
1275
1363
|
...watchOptionsObject,
|
|
1276
1364
|
followSymlinks: watchOptionsObject.followSymlinks ?? false,
|
|
1277
1365
|
ignored: [
|
|
1278
|
-
...
|
|
1366
|
+
...toWatchIgnores(watchOptionsObject.ignored),
|
|
1279
1367
|
...extraIgnored,
|
|
1280
1368
|
...DEFAULT_WATCH_IGNORES
|
|
1281
1369
|
]
|
|
1282
1370
|
};
|
|
1283
1371
|
}
|
|
1284
|
-
function
|
|
1372
|
+
function toWatchIgnores(value) {
|
|
1285
1373
|
if (value === void 0) return [];
|
|
1286
|
-
return
|
|
1374
|
+
return isPatternList(value) ? [...value] : [value];
|
|
1375
|
+
}
|
|
1376
|
+
function isPatternList(value) {
|
|
1377
|
+
return Array.isArray(value);
|
|
1287
1378
|
}
|
|
1288
1379
|
function withRequestFileBuildPlugin(config, root) {
|
|
1289
1380
|
const configObject = isRecord(config) ? config : {};
|
|
@@ -1318,18 +1409,30 @@ function routePreloadState() {
|
|
|
1318
1409
|
}
|
|
1319
1410
|
};
|
|
1320
1411
|
}
|
|
1412
|
+
let prerenderWakeChannelCaptured = false;
|
|
1413
|
+
function prerenderWakeChannelEnabled() {
|
|
1414
|
+
return prerenderWakeChannelCaptured;
|
|
1415
|
+
}
|
|
1321
1416
|
function routerClientInput(input, root) {
|
|
1322
1417
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
1418
|
+
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
1323
1419
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
1324
|
-
|
|
1420
|
+
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
1421
|
+
if (input === void 0) return [
|
|
1422
|
+
resumeEntryId,
|
|
1423
|
+
...wakeEntries,
|
|
1424
|
+
navigationEntryId
|
|
1425
|
+
];
|
|
1325
1426
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
1326
1427
|
resumeEntryId,
|
|
1428
|
+
...wakeEntries,
|
|
1327
1429
|
navigationEntryId,
|
|
1328
1430
|
...Array.isArray(input) ? input : [input]
|
|
1329
1431
|
];
|
|
1330
1432
|
if (isRecord(input)) return {
|
|
1331
1433
|
...input,
|
|
1332
1434
|
"markless-router-resume": resumeEntryId,
|
|
1435
|
+
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
1333
1436
|
"markless-router-navigation": navigationEntryId
|
|
1334
1437
|
};
|
|
1335
1438
|
return input;
|
|
@@ -1337,7 +1440,7 @@ function routerClientInput(input, root) {
|
|
|
1337
1440
|
function isVirtualEntryChunk(chunk, origin) {
|
|
1338
1441
|
return [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id ? decodePath(parseURL(id).pathname).endsWith(origin) : false);
|
|
1339
1442
|
}
|
|
1340
|
-
function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1443
|
+
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1341
1444
|
let root = "";
|
|
1342
1445
|
return {
|
|
1343
1446
|
name: "markless-router:routes",
|
|
@@ -1349,7 +1452,7 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry
|
|
|
1349
1452
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
1350
1453
|
handler(id) {
|
|
1351
1454
|
const baseId = virtualModuleBaseId(id);
|
|
1352
|
-
if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID || baseId === ROUTER_OPTIONS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
|
|
1455
|
+
if (baseId === SERVER_ENTRY_ID || baseId === RESUME_ENTRY_PATH_ID || baseId === PRERENDER_WAKE_ENTRY_PATH_ID || baseId === NAVIGATION_ENTRY_PATH_ID || baseId === ROUTE_PRELOADS_ID || baseId === ROUTER_OPTIONS_ID) return `\0${baseId}${rootScopeQuery(root, id)}`;
|
|
1353
1456
|
const entryFile = virtualEntryFiles[baseId];
|
|
1354
1457
|
if (!entryFile) return;
|
|
1355
1458
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
@@ -1358,6 +1461,10 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry
|
|
|
1358
1461
|
load(id) {
|
|
1359
1462
|
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root);
|
|
1360
1463
|
if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
|
|
1464
|
+
if (id.startsWith(`\0${PRERENDER_WAKE_ENTRY_PATH_ID}`)) {
|
|
1465
|
+
if (!prerenderWakeChannelEnabled() || !prerenderWakeEntry.fileName) return "export const prerenderWakeEntryPath = undefined;";
|
|
1466
|
+
return entryPathSource("prerenderWakeEntryPath", prerenderWakeEntry, PRERENDER_WAKE_ENTRY_ID, root);
|
|
1467
|
+
}
|
|
1361
1468
|
if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
|
|
1362
1469
|
if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads, this?.environment?.config.consumer === "client");
|
|
1363
1470
|
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) return "export const routerMode = \"path\"; // retained for compat; hash paths are always first-class";
|
|
@@ -1373,6 +1480,7 @@ function serverEntrySource(root) {
|
|
|
1373
1480
|
return [
|
|
1374
1481
|
`import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';`,
|
|
1375
1482
|
`import { resumeEntryPath } from '${RESUME_ENTRY_PATH_ID}${query}';`,
|
|
1483
|
+
`import { prerenderWakeEntryPath } from '${PRERENDER_WAKE_ENTRY_PATH_ID}${query}';`,
|
|
1376
1484
|
`import { navigationEntryPath } from '${NAVIGATION_ENTRY_PATH_ID}${query}';`,
|
|
1377
1485
|
`import { routeModulePreloads, routeSsrModulePreloads, routeStylesheets } from '${ROUTE_PRELOADS_ID}${query}';`,
|
|
1378
1486
|
`import { pageModuleLoaders, routeFileIds } from '${ROUTE_DISCOVERY_ID}${query}';`,
|
|
@@ -1380,6 +1488,7 @@ function serverEntrySource(root) {
|
|
|
1380
1488
|
`const entry = createServerEntry({`,
|
|
1381
1489
|
` dev: import.meta.env.DEV,`,
|
|
1382
1490
|
` resumeEntryPath,`,
|
|
1491
|
+
` prerenderWakeEntryPath,`,
|
|
1383
1492
|
` navigationEntryPath,`,
|
|
1384
1493
|
` routeModulePreloads,`,
|
|
1385
1494
|
` routeSsrModulePreloads,`,
|
|
@@ -1475,6 +1584,10 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1475
1584
|
includeChunk(ssrFileNames, chunksByFileName, input.resumeChunk.fileName);
|
|
1476
1585
|
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
|
|
1477
1586
|
}
|
|
1587
|
+
if (input.prerenderWakeChunk) {
|
|
1588
|
+
includeChunk(ssrFileNames, chunksByFileName, input.prerenderWakeChunk.fileName);
|
|
1589
|
+
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
|
|
1590
|
+
}
|
|
1478
1591
|
includeChunk(ssrFileNames, chunksByFileName, routeChunk.fileName, true);
|
|
1479
1592
|
const routeSourceFiles = sourceFilesForChunks(new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
1480
1593
|
for (const [sourceFile, symbolFileNames] of symbolChunks) {
|
|
@@ -1486,7 +1599,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1486
1599
|
}
|
|
1487
1600
|
navigation[routeFile] = [...navigationFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1488
1601
|
ssr[routeFile] = [...ssrFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1489
|
-
styles[routeFile] =
|
|
1602
|
+
styles[routeFile] = routeStylesheetsForChunks(routeFileChunks, chunksByFileName, input.base);
|
|
1490
1603
|
}
|
|
1491
1604
|
return {
|
|
1492
1605
|
navigation,
|
|
@@ -1494,7 +1607,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1494
1607
|
styles
|
|
1495
1608
|
};
|
|
1496
1609
|
}
|
|
1497
|
-
function
|
|
1610
|
+
function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
1498
1611
|
const styles = /* @__PURE__ */ new Set();
|
|
1499
1612
|
const visited = /* @__PURE__ */ new Set();
|
|
1500
1613
|
const visit = (chunk) => {
|
|
@@ -1506,7 +1619,7 @@ function routeStylesheetsForChunk(routeChunk, chunksByFileName, base) {
|
|
|
1506
1619
|
}
|
|
1507
1620
|
for (const stylesheet of chunk.viteMetadata?.importedCss ?? []) styles.add(joinURL(base, stylesheet));
|
|
1508
1621
|
};
|
|
1509
|
-
visit(
|
|
1622
|
+
for (const chunk of routeFileChunks) visit(chunk);
|
|
1510
1623
|
return [...styles];
|
|
1511
1624
|
}
|
|
1512
1625
|
function symbolChunksBySourceFile(chunks) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markless/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Markless router: client-side navigation with SSR streaming and build-time preload maps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/compiled-run/markless.git",
|
|
9
9
|
"directory": "packages/router"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
"provenance": true
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@tsrx/core": "^0.1.
|
|
60
|
+
"@tsrx/core": "^0.1.58",
|
|
61
61
|
"@virtualstate/navigation": "1.0.1-alpha.212",
|
|
62
62
|
"hast-util-to-html": "^9.0.5",
|
|
63
63
|
"nitro": "3.0.260429-beta",
|
|
64
64
|
"pathe": "^2.0.3",
|
|
65
65
|
"satteri": "0.6.3",
|
|
66
66
|
"ufo": "^1.6.4",
|
|
67
|
-
"@markless/
|
|
68
|
-
"@markless/
|
|
67
|
+
"@markless/web": "0.3.1",
|
|
68
|
+
"@markless/bundler": "0.3.1"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@markless/analyzer": "0.
|
|
71
|
+
"@markless/analyzer": "0.3.1"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"vite": "8.0.16"
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
import { preloadRouteModule } from 'virtual:markless-router/route-preloads';
|
|
13
13
|
|
|
14
14
|
const routeDiscovery = createRouteDiscovery(
|
|
15
|
-
import.meta.glob(['/pages/**/*.tsrx', '/pages/**/*.mdx']
|
|
15
|
+
import.meta.glob(['/pages/**/*.tsrx', '/pages/**/*.mdx'], {
|
|
16
|
+
query: '?markless-route',
|
|
17
|
+
}),
|
|
16
18
|
);
|
|
17
19
|
|
|
18
20
|
export const pageModules = routeDiscovery.pageModuleLoaders;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const mdxPageModuleLoaders = import.meta.glob('/pages/**/*.mdx');
|
|
2
|
+
const tsrxPrerenderWakeModuleLoaders = import.meta.glob('/pages/**/*.tsrx', {
|
|
3
|
+
query: '?markless-prerender-wake',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export async function resumeContainerEvent(input: {
|
|
7
|
+
readonly root: ParentNode;
|
|
8
|
+
readonly [key: string]: unknown;
|
|
9
|
+
}) {
|
|
10
|
+
const file = routeFileFromRoot(input.root);
|
|
11
|
+
const loadRouteWakeModule = file && routeWakeModuleLoader(file);
|
|
12
|
+
const routeWakeModule = loadRouteWakeModule ? await loadRouteWakeModule() : undefined;
|
|
13
|
+
const resume = (routeWakeModule as RouteWakeModule | undefined)?.resumeContainerEvent;
|
|
14
|
+
if (typeof resume !== 'function') {
|
|
15
|
+
throw new Error(`Markless Router could not wake prerendered route module: ${file ?? '<unknown>'}`);
|
|
16
|
+
}
|
|
17
|
+
await resume(input);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RouteWakeModule {
|
|
21
|
+
readonly resumeContainerEvent?: (input: unknown) => unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function routeWakeModuleLoader(file: string): (() => Promise<unknown>) | undefined {
|
|
25
|
+
const loaders = file.endsWith('.mdx') ? mdxPageModuleLoaders : tsrxPrerenderWakeModuleLoaders;
|
|
26
|
+
return loaders[file] ?? loaders[`/${file}`];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function routeFileFromRoot(root: ParentNode): string | undefined {
|
|
30
|
+
const script = root.querySelector?.('script[type="@markless/core/route"]');
|
|
31
|
+
const text = script?.textContent;
|
|
32
|
+
if (!text) return undefined;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const route = JSON.parse(text) as { readonly file?: unknown };
|
|
36
|
+
return typeof route.file === 'string' ? route.file : undefined;
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';
|
|
2
2
|
import { resumeEntryPath } from 'virtual:markless-router/resume-entry-path';
|
|
3
|
+
import { prerenderWakeEntryPath } from 'virtual:markless-router/prerender-wake-entry-path';
|
|
3
4
|
import { navigationEntryPath } from 'virtual:markless-router/navigation-entry-path';
|
|
4
5
|
import {
|
|
5
6
|
routeModulePreloads,
|
|
@@ -12,6 +13,7 @@ const documentModuleLoaders = import.meta.glob(['/document.tsrx']);
|
|
|
12
13
|
const entry = createServerEntry({
|
|
13
14
|
dev: import.meta.env.DEV,
|
|
14
15
|
resumeEntryPath,
|
|
16
|
+
prerenderWakeEntryPath,
|
|
15
17
|
navigationEntryPath,
|
|
16
18
|
routeModulePreloads,
|
|
17
19
|
routeSsrModulePreloads,
|