@markless/router 0.2.2 → 0.3.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/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 +34 -14
- package/dist/vite/runtime/mdx-route.d.ts +17 -4
- package/dist/vite/runtime/mdx-route.js +118 -16
- package/dist/vite.d.ts +3 -2
- package/dist/vite.js +165 -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
|
|
@@ -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,6 +29,20 @@ 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;
|
|
31
46
|
type MdxStatePayload = {
|
|
32
47
|
readonly version: unknown;
|
|
33
48
|
readonly cells?: readonly unknown[];
|
|
@@ -59,10 +74,8 @@ type MdxViewPayload = {
|
|
|
59
74
|
readonly asyncBoundaries?: readonly unknown[];
|
|
60
75
|
};
|
|
61
76
|
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
77
|
declare function composeMdxState(children: readonly MdxChild[]): MdxStatePayload | undefined;
|
|
65
78
|
declare function composeMdxView(parts: readonly MdxRoutePart[], children: readonly MdxChild[], initialElementOffset: number): MdxViewPayload | undefined;
|
|
66
79
|
declare function loadMdxSymbol(symbolId: string, children: readonly MdxChild[], loaders: readonly MdxSymbolLoader[]): unknown;
|
|
67
80
|
//#endregion
|
|
68
|
-
export { MdxChild, MdxComponentArtifact, MdxRenderOutput, MdxRoutePart, MdxSymbolLoader, composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
81
|
+
export { MdxChild, MdxComponentArtifact, MdxRenderOutput, MdxRoutePart, MdxSymbolLoader, composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild };
|
|
@@ -1,4 +1,121 @@
|
|
|
1
1
|
//#region src/vite/runtime/mdx-route.ts
|
|
2
|
+
function createMdxRenderDataSurface(parts, children) {
|
|
3
|
+
const componentName = "MarklessMdxRoute";
|
|
4
|
+
const rootChunkId = `template:${componentName}`;
|
|
5
|
+
const childrenByIndex = new Map(children.map((child) => [child.componentIndex, child]));
|
|
6
|
+
const statics = [];
|
|
7
|
+
const slots = [];
|
|
8
|
+
const hosts = [{
|
|
9
|
+
hostNodeId: "__mdx:root",
|
|
10
|
+
tagName: "main",
|
|
11
|
+
coordinate: {
|
|
12
|
+
kind: "child-index",
|
|
13
|
+
path: [0]
|
|
14
|
+
}
|
|
15
|
+
}];
|
|
16
|
+
const edges = [];
|
|
17
|
+
let staticHtml = "<main data-markless-mdx-root>";
|
|
18
|
+
let slotIndex = 0;
|
|
19
|
+
for (const [partIndex, part] of parts.entries()) {
|
|
20
|
+
if (part.kind === "html") {
|
|
21
|
+
staticHtml += part.html ?? "";
|
|
22
|
+
for (const [tagIndex, tagName] of (part.elementTags ?? []).entries()) hosts.push({
|
|
23
|
+
hostNodeId: `__mdx:static:${partIndex}:${tagIndex}`,
|
|
24
|
+
tagName,
|
|
25
|
+
coordinate: {
|
|
26
|
+
kind: "child-index",
|
|
27
|
+
path: [
|
|
28
|
+
0,
|
|
29
|
+
partIndex,
|
|
30
|
+
tagIndex
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const child = childrenByIndex.get(part.componentIndex);
|
|
37
|
+
if (!child) throw new Error(`MARKLESS_MDX_RENDER_DATA_CHILD_MISSING: ${part.componentIndex}`);
|
|
38
|
+
const childComponentName = child.surface.rootComponentName;
|
|
39
|
+
if (!childComponentName) throw new Error(`MARKLESS_MDX_RENDER_DATA_ROOT_MISSING: ${part.componentIndex}`);
|
|
40
|
+
const edgeId = `mdx:edge:${part.componentIndex}`;
|
|
41
|
+
staticHtml += `<!--markless-slot:${slotIndex}-->`;
|
|
42
|
+
statics.push(staticHtml);
|
|
43
|
+
staticHtml = "";
|
|
44
|
+
slots.push({
|
|
45
|
+
kind: "child-component",
|
|
46
|
+
componentEdgeId: edgeId,
|
|
47
|
+
childComponentName,
|
|
48
|
+
childTemplateId: `template:${childComponentName}`,
|
|
49
|
+
coordinate: {
|
|
50
|
+
kind: "comment-anchor",
|
|
51
|
+
path: [0, partIndex]
|
|
52
|
+
},
|
|
53
|
+
staticIndex: slotIndex
|
|
54
|
+
});
|
|
55
|
+
edges.push({
|
|
56
|
+
id: edgeId,
|
|
57
|
+
childComponentName,
|
|
58
|
+
hostPrefix: child.hostPrefix,
|
|
59
|
+
symbolPrefix: child.symbolPrefix,
|
|
60
|
+
props: Object.entries(child.props).map(([name, value]) => ({
|
|
61
|
+
name,
|
|
62
|
+
kind: "serializable",
|
|
63
|
+
value
|
|
64
|
+
}))
|
|
65
|
+
});
|
|
66
|
+
slotIndex++;
|
|
67
|
+
}
|
|
68
|
+
statics.push(`${staticHtml}</main>`);
|
|
69
|
+
const state = {
|
|
70
|
+
version: 1,
|
|
71
|
+
cells: [],
|
|
72
|
+
computed: []
|
|
73
|
+
};
|
|
74
|
+
const view = {
|
|
75
|
+
version: 1,
|
|
76
|
+
locators: [],
|
|
77
|
+
events: [],
|
|
78
|
+
domUpdates: [],
|
|
79
|
+
behaviors: [],
|
|
80
|
+
elementHandles: [],
|
|
81
|
+
asyncBoundaries: []
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
rootComponentName: componentName,
|
|
85
|
+
renderData: {
|
|
86
|
+
root: {
|
|
87
|
+
componentName,
|
|
88
|
+
templateId: rootChunkId
|
|
89
|
+
},
|
|
90
|
+
chunks: [{
|
|
91
|
+
id: rootChunkId,
|
|
92
|
+
kind: "template",
|
|
93
|
+
componentName,
|
|
94
|
+
statics,
|
|
95
|
+
hosts,
|
|
96
|
+
slots
|
|
97
|
+
}],
|
|
98
|
+
initialValues: [],
|
|
99
|
+
branches: [],
|
|
100
|
+
repeats: [],
|
|
101
|
+
boundaries: [],
|
|
102
|
+
interactions: []
|
|
103
|
+
},
|
|
104
|
+
components: { [componentName]: {
|
|
105
|
+
name: componentName,
|
|
106
|
+
state,
|
|
107
|
+
view,
|
|
108
|
+
rootChunkId,
|
|
109
|
+
stateGraphNodeIds: [],
|
|
110
|
+
initialValues: [],
|
|
111
|
+
branches: [],
|
|
112
|
+
boundaries: [],
|
|
113
|
+
edges,
|
|
114
|
+
propCellId: null
|
|
115
|
+
} },
|
|
116
|
+
imports: Object.fromEntries(children.flatMap((child) => child.surface.rootComponentName ? [[child.surface.rootComponentName, child.surface]] : []))
|
|
117
|
+
};
|
|
118
|
+
}
|
|
2
119
|
async function renderMdxChild(children, component, props, child) {
|
|
3
120
|
const output = await component.renderSsr?.(props);
|
|
4
121
|
if (output) children.push({
|
|
@@ -7,21 +124,6 @@ async function renderMdxChild(children, component, props, child) {
|
|
|
7
124
|
});
|
|
8
125
|
return output?.html ?? "";
|
|
9
126
|
}
|
|
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
127
|
function composeMdxState(children) {
|
|
26
128
|
const childStates = children.map((child) => child.output?.state).filter(isDefined);
|
|
27
129
|
if (childStates.length === 0) return;
|
|
@@ -110,4 +212,4 @@ function isDefined(value) {
|
|
|
110
212
|
return value !== void 0;
|
|
111
213
|
}
|
|
112
214
|
//#endregion
|
|
113
|
-
export { composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
215
|
+
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,11 @@ 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,",
|
|
681
731
|
" renderSsr() {",
|
|
682
732
|
` return { html: ${JSON.stringify(html)} };`,
|
|
683
733
|
" }",
|
|
@@ -685,43 +735,28 @@ async function transformMdxRoute(source, id) {
|
|
|
685
735
|
"export default marklessMdxPage;",
|
|
686
736
|
""
|
|
687
737
|
].join("\n");
|
|
688
|
-
return emitComposedMdxRoute(route);
|
|
738
|
+
return emitComposedMdxRoute(route, id);
|
|
689
739
|
}
|
|
690
|
-
function emitComposedMdxRoute(route) {
|
|
740
|
+
function emitComposedMdxRoute(route, id) {
|
|
691
741
|
return [
|
|
692
742
|
`import { resumeFromPayloadDocument } from '@markless/core/web/resume';`,
|
|
693
|
-
`import { composeMdxState, composeMdxView, loadMdxSymbol, renderMdxChild
|
|
743
|
+
`import { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild } from '@markless/router/vite/runtime/mdx-route';`,
|
|
694
744
|
...route.imports,
|
|
695
745
|
"",
|
|
696
746
|
`const marklessMdxParts = ${JSON.stringify(route.parts)};`,
|
|
697
747
|
`const marklessMdxSymbolLoaders = ${renderSymbolLoaders(route.components)};`,
|
|
748
|
+
`const marklessMdxRenderData = ${renderMdxRenderDataLoader(route.components, id)};`,
|
|
698
749
|
"",
|
|
699
750
|
"const marklessMdxPage = {",
|
|
751
|
+
" renderData: marklessMdxRenderData,",
|
|
752
|
+
" loadSymbol: marklessMdxLoadSymbol,",
|
|
700
753
|
" async renderSsr(props = {}) {",
|
|
701
754
|
" const marklessMdxChildren = [];",
|
|
702
|
-
` const html = ${renderHtmlExpression(route
|
|
755
|
+
` const html = ${renderHtmlExpression(route)};`,
|
|
703
756
|
" const state = composeMdxState(marklessMdxChildren);",
|
|
704
757
|
" const view = composeMdxView(marklessMdxParts, marklessMdxChildren, 0);",
|
|
705
758
|
" return { html, ...(state ? { state } : {}), ...(view ? { view } : {}) };",
|
|
706
759
|
" },",
|
|
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
760
|
...renderMdxPreload(route.components),
|
|
726
761
|
"};",
|
|
727
762
|
"export default marklessMdxPage;",
|
|
@@ -776,7 +811,7 @@ function parseMdxRoute(source, id) {
|
|
|
776
811
|
return {
|
|
777
812
|
imports,
|
|
778
813
|
components,
|
|
779
|
-
parts: routePartsFromHast(root.children
|
|
814
|
+
parts: routePartsFromHast("children" in root ? root.children : [], {
|
|
780
815
|
components,
|
|
781
816
|
id,
|
|
782
817
|
importedComponents
|
|
@@ -826,6 +861,7 @@ function componentPart(node, context) {
|
|
|
826
861
|
prefix: `m${componentIndex}:`,
|
|
827
862
|
props: children ? [...props, {
|
|
828
863
|
name: "children",
|
|
864
|
+
value: children,
|
|
829
865
|
valueExpression: JSON.stringify(children)
|
|
830
866
|
}] : props
|
|
831
867
|
});
|
|
@@ -841,11 +877,15 @@ function appendHtmlPart(parts, nodes, id) {
|
|
|
841
877
|
type: "root",
|
|
842
878
|
children: staticNodes
|
|
843
879
|
}, { allowDangerousHtml: true });
|
|
844
|
-
if (html)
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
880
|
+
if (html) {
|
|
881
|
+
const elementTags = hastElementTags(staticNodes);
|
|
882
|
+
parts.push({
|
|
883
|
+
kind: "html",
|
|
884
|
+
html,
|
|
885
|
+
elementCount: elementTags.length,
|
|
886
|
+
elementTags
|
|
887
|
+
});
|
|
888
|
+
}
|
|
849
889
|
}
|
|
850
890
|
function renderStaticHtml(nodes, id) {
|
|
851
891
|
if (nodes.length === 0) return "";
|
|
@@ -867,6 +907,7 @@ function normalizeStaticHastNode(node, id) {
|
|
|
867
907
|
value: value == null ? "" : String(value)
|
|
868
908
|
}];
|
|
869
909
|
}
|
|
910
|
+
if (node.type === "root") return normalizeStaticHastNodes(node.children, id);
|
|
870
911
|
if ("children" in node && Array.isArray(node.children)) return [{
|
|
871
912
|
...node,
|
|
872
913
|
children: normalizeStaticHastNodes(node.children, id)
|
|
@@ -881,15 +922,19 @@ function componentProp(attribute, id) {
|
|
|
881
922
|
const value = attribute.value;
|
|
882
923
|
if (value == null) return {
|
|
883
924
|
name: attribute.name,
|
|
925
|
+
value: true,
|
|
884
926
|
valueExpression: "true"
|
|
885
927
|
};
|
|
886
928
|
if (typeof value === "string") return {
|
|
887
929
|
name: attribute.name,
|
|
930
|
+
value,
|
|
888
931
|
valueExpression: JSON.stringify(value)
|
|
889
932
|
};
|
|
933
|
+
const literal = literalSafeExpressionValue(value.value, id);
|
|
890
934
|
return {
|
|
891
935
|
name: attribute.name,
|
|
892
|
-
|
|
936
|
+
value: literal,
|
|
937
|
+
valueExpression: literalValueExpression(literal, id)
|
|
893
938
|
};
|
|
894
939
|
}
|
|
895
940
|
function literalSafeExpressionValue(source, id) {
|
|
@@ -918,8 +963,15 @@ function parseMdxExpressionValue(source, id) {
|
|
|
918
963
|
dropHandle(handle);
|
|
919
964
|
}
|
|
920
965
|
}
|
|
966
|
+
function literalPropertyName(key) {
|
|
967
|
+
if (key.type === "Identifier" && typeof key.name === "string") return key.name;
|
|
968
|
+
if (key.type === "Literal" && typeof key.value === "string") return key.value;
|
|
969
|
+
}
|
|
970
|
+
function isEstreeNode(value) {
|
|
971
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
972
|
+
}
|
|
921
973
|
function evaluateLiteralExpression(node, id) {
|
|
922
|
-
if (!node) throw literalExpressionError(id);
|
|
974
|
+
if (!isEstreeNode(node)) throw literalExpressionError(id);
|
|
923
975
|
if (node.type === "Literal") {
|
|
924
976
|
const value = node.value;
|
|
925
977
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
@@ -938,7 +990,7 @@ function evaluateLiteralExpression(node, id) {
|
|
|
938
990
|
for (const property of node.properties) {
|
|
939
991
|
if (property.type !== "Property" || property.kind !== "init" || property.method || property.computed) throw literalExpressionError(id);
|
|
940
992
|
const key = property.key;
|
|
941
|
-
const name = key
|
|
993
|
+
const name = isEstreeNode(key) ? literalPropertyName(key) : void 0;
|
|
942
994
|
if (!name) throw literalExpressionError(id);
|
|
943
995
|
value[name] = evaluateLiteralExpression(property.value, id);
|
|
944
996
|
}
|
|
@@ -956,10 +1008,9 @@ function literalValueExpression(value, id) {
|
|
|
956
1008
|
function literalExpressionError(id) {
|
|
957
1009
|
return /* @__PURE__ */ new Error(`Markless Router MDX only supports literal-safe expressions in attributes and markdown: ${id}`);
|
|
958
1010
|
}
|
|
959
|
-
function renderHtmlExpression(route
|
|
1011
|
+
function renderHtmlExpression(route) {
|
|
960
1012
|
return joinExpressions(route.parts.map((part) => {
|
|
961
1013
|
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
1014
|
const component = route.components[part.componentIndex];
|
|
964
1015
|
return `(await renderMdxChild(marklessMdxChildren, ${component.localName}, ${componentPropsExpression(component)}, { componentIndex: ${part.componentIndex}, hostPrefix: ${JSON.stringify(component.prefix)}, symbolPrefix: ${JSON.stringify(component.prefix)} }))`;
|
|
965
1016
|
}));
|
|
@@ -968,24 +1019,40 @@ function componentPropsExpression(component) {
|
|
|
968
1019
|
if (component.props.length === 0) return "{}";
|
|
969
1020
|
return `{ ${component.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.valueExpression}`).join(", ")} }`;
|
|
970
1021
|
}
|
|
971
|
-
function renderCsrHtml(route) {
|
|
972
|
-
return `"<main data-markless-mdx-root>" + ${renderHtmlExpression(route, "csr")} + "</main>"`;
|
|
973
|
-
}
|
|
974
1022
|
function joinExpressions(expressions) {
|
|
975
1023
|
const filtered = expressions.filter((expression) => expression !== "\"\"");
|
|
976
1024
|
return filtered.length === 0 ? "\"\"" : filtered.join(" + ");
|
|
977
1025
|
}
|
|
978
|
-
function
|
|
979
|
-
|
|
1026
|
+
function hastElementTags(nodes) {
|
|
1027
|
+
const tags = [];
|
|
980
1028
|
for (const node of nodes) {
|
|
981
|
-
if (node.type === "element")
|
|
982
|
-
if ("children" in node && Array.isArray(node.children))
|
|
1029
|
+
if (node.type === "element") tags.push(String(node.tagName));
|
|
1030
|
+
if ("children" in node && Array.isArray(node.children)) tags.push(...hastElementTags(node.children));
|
|
983
1031
|
}
|
|
984
|
-
return
|
|
1032
|
+
return tags;
|
|
985
1033
|
}
|
|
986
1034
|
function renderSymbolLoaders(components) {
|
|
987
1035
|
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
1036
|
}
|
|
1037
|
+
function renderMdxRenderDataLoader(components, id) {
|
|
1038
|
+
const reachedFrom = mdxMaterializedReachRoot(id);
|
|
1039
|
+
const imports = components.map((component) => {
|
|
1040
|
+
const source = withQuery(component.specifier, {
|
|
1041
|
+
"markless-render-data": null,
|
|
1042
|
+
...reachedFrom ? { "markless-reached-from": reachedFrom } : {}
|
|
1043
|
+
});
|
|
1044
|
+
return `import(${JSON.stringify(source)})`;
|
|
1045
|
+
});
|
|
1046
|
+
const exportName = reachedFrom ? "marklessPrerenderData" : "marklessRenderData";
|
|
1047
|
+
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} }`);
|
|
1048
|
+
return `() => Promise.all([${imports.join(", ")}]).then(modules => createMdxRenderDataSurface(marklessMdxParts, [${children.join(", ")}]))`;
|
|
1049
|
+
}
|
|
1050
|
+
function mdxMaterializedReachRoot(id) {
|
|
1051
|
+
const query = new URLSearchParams(parsePath(id).search);
|
|
1052
|
+
const reachedFrom = query.get("markless-reached-from");
|
|
1053
|
+
if (reachedFrom) return reachedFrom;
|
|
1054
|
+
return query.has("markless-route") ? decodePath(parseURL(id).pathname) : void 0;
|
|
1055
|
+
}
|
|
989
1056
|
function isMdxEsm(node) {
|
|
990
1057
|
return node.type === "mdxjsEsm";
|
|
991
1058
|
}
|
|
@@ -1005,6 +1072,9 @@ const CLIENT_ENTRY_ID = "virtual:markless-router/client-entry";
|
|
|
1005
1072
|
const RESUME_ENTRY_ID = "virtual:markless-router/resume-entry";
|
|
1006
1073
|
const RESUME_ENTRY_ORIGIN = "/entries/resume-entry.ts";
|
|
1007
1074
|
const RESUME_ENTRY_PATH_ID = "virtual:markless-router/resume-entry-path";
|
|
1075
|
+
const PRERENDER_WAKE_ENTRY_ID = "virtual:markless-router/prerender-wake-entry";
|
|
1076
|
+
const PRERENDER_WAKE_ENTRY_ORIGIN = "/entries/prerender-wake-entry.ts";
|
|
1077
|
+
const PRERENDER_WAKE_ENTRY_PATH_ID = "virtual:markless-router/prerender-wake-entry-path";
|
|
1008
1078
|
const NAVIGATION_ENTRY_ID = "virtual:markless-router/navigation-entry";
|
|
1009
1079
|
const NAVIGATION_ENTRY_ORIGIN = "/entries/client-entry.ts";
|
|
1010
1080
|
const NAVIGATION_ENTRY_PATH_ID = "virtual:markless-router/navigation-entry-path";
|
|
@@ -1013,7 +1083,7 @@ const ROUTE_PRELOADS_PLACEHOLDER = "__MARKLESS_ROUTER_ROUTE_PRELOADS__";
|
|
|
1013
1083
|
const SERVER_ENTRY_ID = "virtual:markless-router/server-entry";
|
|
1014
1084
|
const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
1015
1085
|
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)(?:\?.*)?$/;
|
|
1086
|
+
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
1087
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
1018
1088
|
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
1089
|
const DEFAULT_WATCH_IGNORES = [
|
|
@@ -1026,11 +1096,13 @@ const virtualEntryFiles = {
|
|
|
1026
1096
|
[ROUTE_DISCOVERY_ID]: "route-discovery.ts",
|
|
1027
1097
|
[CLIENT_ENTRY_ID]: "client-entry.ts",
|
|
1028
1098
|
[RESUME_ENTRY_ID]: "resume-entry.ts",
|
|
1099
|
+
[PRERENDER_WAKE_ENTRY_ID]: "prerender-wake-entry.ts",
|
|
1029
1100
|
[NAVIGATION_ENTRY_ID]: "client-entry.ts",
|
|
1030
1101
|
[SERVER_ENTRY_ID]: "server-entry.ts",
|
|
1031
1102
|
[ROUTE_HREF_ID]: "route-href.ts"
|
|
1032
1103
|
};
|
|
1033
1104
|
function router(options = {}) {
|
|
1105
|
+
prerenderWakeChannelCaptured = process.env.MARKLESS_PRERENDER_WAKE === "1";
|
|
1034
1106
|
if (options.nitro === false) return [
|
|
1035
1107
|
mdxTransformPlugin(),
|
|
1036
1108
|
requestFileTransformPlugin(),
|
|
@@ -1041,17 +1113,18 @@ function router(options = {}) {
|
|
|
1041
1113
|
];
|
|
1042
1114
|
const nitroPlugins = nitro();
|
|
1043
1115
|
const resumeEntry = resumeEntryState();
|
|
1116
|
+
const prerenderWakeEntry = resumeEntryState();
|
|
1044
1117
|
const navigationEntry = resumeEntryState();
|
|
1045
1118
|
const routePreloads = routePreloadState();
|
|
1046
1119
|
return [
|
|
1047
|
-
routerConfigPlugin(nitroPlugins, resumeEntry, navigationEntry, routePreloads),
|
|
1120
|
+
routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1048
1121
|
devSourceModuleRequestPlugin(),
|
|
1049
1122
|
mdxTransformPlugin(),
|
|
1050
1123
|
requestFileTransformPlugin(),
|
|
1051
1124
|
routeTypegenPlugin(),
|
|
1052
1125
|
anchorTransformPlugin(),
|
|
1053
1126
|
htmlTransformPlugin(),
|
|
1054
|
-
virtualModulesPlugin(resumeEntry, navigationEntry, routePreloads),
|
|
1127
|
+
virtualModulesPlugin(resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads),
|
|
1055
1128
|
nitroPlugins
|
|
1056
1129
|
];
|
|
1057
1130
|
}
|
|
@@ -1075,7 +1148,7 @@ function devSourceModuleRequestPlugin() {
|
|
|
1075
1148
|
}
|
|
1076
1149
|
};
|
|
1077
1150
|
}
|
|
1078
|
-
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry, routePreloads) {
|
|
1151
|
+
function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads) {
|
|
1079
1152
|
let clientOutDir = "";
|
|
1080
1153
|
let clientEnvironmentName = "";
|
|
1081
1154
|
let clientEnvironmentConfig;
|
|
@@ -1115,6 +1188,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1115
1188
|
}
|
|
1116
1189
|
for (const entry of [
|
|
1117
1190
|
resumeEntry,
|
|
1191
|
+
prerenderWakeEntry,
|
|
1118
1192
|
navigationEntry,
|
|
1119
1193
|
routePreloads
|
|
1120
1194
|
]) entry.base = config.base;
|
|
@@ -1132,6 +1206,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1132
1206
|
if (isClientEnvironment) {
|
|
1133
1207
|
pendingClientAssets = void 0;
|
|
1134
1208
|
resumeEntry.fileName = void 0;
|
|
1209
|
+
prerenderWakeEntry.fileName = void 0;
|
|
1135
1210
|
navigationEntry.fileName = void 0;
|
|
1136
1211
|
routePreloads.routes = {
|
|
1137
1212
|
navigation: {},
|
|
@@ -1145,6 +1220,7 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1145
1220
|
if (!isRouterServerEnvironment) return;
|
|
1146
1221
|
const manifest = await readClientAssetsManifest(clientOutDir, routePreloads.base);
|
|
1147
1222
|
resumeEntry.fileName = clientAssetFileName(manifest.entries.resume, manifest.base);
|
|
1223
|
+
prerenderWakeEntry.fileName = manifest.entries.prerenderWake ? clientAssetFileName(manifest.entries.prerenderWake, manifest.base) : void 0;
|
|
1148
1224
|
navigationEntry.fileName = clientAssetFileName(manifest.entries.navigation, manifest.base);
|
|
1149
1225
|
routePreloads.routes = manifest.routes;
|
|
1150
1226
|
routePreloads.persisted = true;
|
|
@@ -1155,21 +1231,26 @@ function routerConfigPlugin(nitroPluginsFromRouter, resumeEntry, navigationEntry
|
|
|
1155
1231
|
if (this.environment?.config.consumer !== "client") return;
|
|
1156
1232
|
const resumeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, RESUME_ENTRY_ORIGIN));
|
|
1157
1233
|
if (resumeChunk) resumeEntry.fileName = resumeChunk.fileName;
|
|
1234
|
+
const prerenderWakeChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, PRERENDER_WAKE_ENTRY_ORIGIN));
|
|
1235
|
+
if (prerenderWakeChunk) prerenderWakeEntry.fileName = prerenderWakeChunk.fileName;
|
|
1158
1236
|
const navigationChunk = Object.values(bundle).find((item) => item.type === "chunk" && isVirtualEntryChunk(item, NAVIGATION_ENTRY_ORIGIN));
|
|
1159
1237
|
if (navigationChunk) navigationEntry.fileName = navigationChunk.fileName;
|
|
1160
1238
|
routePreloads.routes = routeModulePreloadsFromBundle({
|
|
1161
1239
|
base: routePreloads.base,
|
|
1162
1240
|
bundle,
|
|
1163
1241
|
navigationChunk,
|
|
1242
|
+
prerenderWakeChunk,
|
|
1164
1243
|
resumeChunk,
|
|
1165
1244
|
root: routePreloads.root
|
|
1166
1245
|
});
|
|
1167
1246
|
patchRoutePreloadsInBundle(bundle, routePreloads.routes);
|
|
1168
1247
|
if (productionBuild) {
|
|
1169
|
-
|
|
1248
|
+
const wakeRequired = prerenderWakeChannelEnabled();
|
|
1249
|
+
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
1250
|
pendingClientAssets = createClientAssetsManifest({
|
|
1171
1251
|
base: routePreloads.base,
|
|
1172
1252
|
resumeEntry: joinURL(routePreloads.base, resumeEntry.fileName),
|
|
1253
|
+
...prerenderWakeEntry.fileName ? { prerenderWakeEntry: joinURL(routePreloads.base, prerenderWakeEntry.fileName) } : {},
|
|
1173
1254
|
navigationEntry: joinURL(routePreloads.base, navigationEntry.fileName),
|
|
1174
1255
|
routes: routePreloads.routes
|
|
1175
1256
|
});
|
|
@@ -1275,15 +1356,18 @@ function withWatchIgnores(watchOptions, extraIgnored = []) {
|
|
|
1275
1356
|
...watchOptionsObject,
|
|
1276
1357
|
followSymlinks: watchOptionsObject.followSymlinks ?? false,
|
|
1277
1358
|
ignored: [
|
|
1278
|
-
...
|
|
1359
|
+
...toWatchIgnores(watchOptionsObject.ignored),
|
|
1279
1360
|
...extraIgnored,
|
|
1280
1361
|
...DEFAULT_WATCH_IGNORES
|
|
1281
1362
|
]
|
|
1282
1363
|
};
|
|
1283
1364
|
}
|
|
1284
|
-
function
|
|
1365
|
+
function toWatchIgnores(value) {
|
|
1285
1366
|
if (value === void 0) return [];
|
|
1286
|
-
return
|
|
1367
|
+
return isPatternList(value) ? [...value] : [value];
|
|
1368
|
+
}
|
|
1369
|
+
function isPatternList(value) {
|
|
1370
|
+
return Array.isArray(value);
|
|
1287
1371
|
}
|
|
1288
1372
|
function withRequestFileBuildPlugin(config, root) {
|
|
1289
1373
|
const configObject = isRecord(config) ? config : {};
|
|
@@ -1318,18 +1402,30 @@ function routePreloadState() {
|
|
|
1318
1402
|
}
|
|
1319
1403
|
};
|
|
1320
1404
|
}
|
|
1405
|
+
let prerenderWakeChannelCaptured = false;
|
|
1406
|
+
function prerenderWakeChannelEnabled() {
|
|
1407
|
+
return prerenderWakeChannelCaptured;
|
|
1408
|
+
}
|
|
1321
1409
|
function routerClientInput(input, root) {
|
|
1322
1410
|
const resumeEntryId = scopedVirtualEntryId(RESUME_ENTRY_ID, root);
|
|
1411
|
+
const prerenderWakeEntryId = scopedVirtualEntryId(PRERENDER_WAKE_ENTRY_ID, root);
|
|
1323
1412
|
const navigationEntryId = scopedVirtualEntryId(NAVIGATION_ENTRY_ID, root);
|
|
1324
|
-
|
|
1413
|
+
const wakeEntries = prerenderWakeChannelEnabled() ? [prerenderWakeEntryId] : [];
|
|
1414
|
+
if (input === void 0) return [
|
|
1415
|
+
resumeEntryId,
|
|
1416
|
+
...wakeEntries,
|
|
1417
|
+
navigationEntryId
|
|
1418
|
+
];
|
|
1325
1419
|
if (typeof input === "string" || Array.isArray(input)) return [
|
|
1326
1420
|
resumeEntryId,
|
|
1421
|
+
...wakeEntries,
|
|
1327
1422
|
navigationEntryId,
|
|
1328
1423
|
...Array.isArray(input) ? input : [input]
|
|
1329
1424
|
];
|
|
1330
1425
|
if (isRecord(input)) return {
|
|
1331
1426
|
...input,
|
|
1332
1427
|
"markless-router-resume": resumeEntryId,
|
|
1428
|
+
...prerenderWakeChannelEnabled() ? { "markless-router-prerender-wake": prerenderWakeEntryId } : {},
|
|
1333
1429
|
"markless-router-navigation": navigationEntryId
|
|
1334
1430
|
};
|
|
1335
1431
|
return input;
|
|
@@ -1337,7 +1433,7 @@ function routerClientInput(input, root) {
|
|
|
1337
1433
|
function isVirtualEntryChunk(chunk, origin) {
|
|
1338
1434
|
return [chunk.facadeModuleId, ...chunk.moduleIds ?? []].some((id) => id ? decodePath(parseURL(id).pathname).endsWith(origin) : false);
|
|
1339
1435
|
}
|
|
1340
|
-
function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1436
|
+
function virtualModulesPlugin(resumeEntry = resumeEntryState(), prerenderWakeEntry = resumeEntryState(), navigationEntry = resumeEntryState(), routePreloads = routePreloadState()) {
|
|
1341
1437
|
let root = "";
|
|
1342
1438
|
return {
|
|
1343
1439
|
name: "markless-router:routes",
|
|
@@ -1349,7 +1445,7 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry
|
|
|
1349
1445
|
filter: { id: PUBLIC_VIRTUAL_MODULE_ID_RE },
|
|
1350
1446
|
handler(id) {
|
|
1351
1447
|
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)}`;
|
|
1448
|
+
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
1449
|
const entryFile = virtualEntryFiles[baseId];
|
|
1354
1450
|
if (!entryFile) return;
|
|
1355
1451
|
return `${join(VIRTUAL_ENTRY_DIR, entryFile)}${rootScopeQuery(root, id)}`;
|
|
@@ -1358,6 +1454,10 @@ function virtualModulesPlugin(resumeEntry = resumeEntryState(), navigationEntry
|
|
|
1358
1454
|
load(id) {
|
|
1359
1455
|
if (id.startsWith(`\0${SERVER_ENTRY_ID}`)) return serverEntrySource(root);
|
|
1360
1456
|
if (id.startsWith(`\0${RESUME_ENTRY_PATH_ID}`)) return entryPathSource("resumeEntryPath", resumeEntry, RESUME_ENTRY_ID, root);
|
|
1457
|
+
if (id.startsWith(`\0${PRERENDER_WAKE_ENTRY_PATH_ID}`)) {
|
|
1458
|
+
if (!prerenderWakeChannelEnabled() || !prerenderWakeEntry.fileName) return "export const prerenderWakeEntryPath = undefined;";
|
|
1459
|
+
return entryPathSource("prerenderWakeEntryPath", prerenderWakeEntry, PRERENDER_WAKE_ENTRY_ID, root);
|
|
1460
|
+
}
|
|
1361
1461
|
if (id.startsWith(`\0${NAVIGATION_ENTRY_PATH_ID}`)) return entryPathSource("navigationEntryPath", navigationEntry, NAVIGATION_ENTRY_ID, root);
|
|
1362
1462
|
if (id.startsWith(`\0${ROUTE_PRELOADS_ID}`)) return routePreloadsSource(routePreloads, this?.environment?.config.consumer === "client");
|
|
1363
1463
|
if (id.startsWith(`\0${ROUTER_OPTIONS_ID}`)) return "export const routerMode = \"path\"; // retained for compat; hash paths are always first-class";
|
|
@@ -1373,6 +1473,7 @@ function serverEntrySource(root) {
|
|
|
1373
1473
|
return [
|
|
1374
1474
|
`import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';`,
|
|
1375
1475
|
`import { resumeEntryPath } from '${RESUME_ENTRY_PATH_ID}${query}';`,
|
|
1476
|
+
`import { prerenderWakeEntryPath } from '${PRERENDER_WAKE_ENTRY_PATH_ID}${query}';`,
|
|
1376
1477
|
`import { navigationEntryPath } from '${NAVIGATION_ENTRY_PATH_ID}${query}';`,
|
|
1377
1478
|
`import { routeModulePreloads, routeSsrModulePreloads, routeStylesheets } from '${ROUTE_PRELOADS_ID}${query}';`,
|
|
1378
1479
|
`import { pageModuleLoaders, routeFileIds } from '${ROUTE_DISCOVERY_ID}${query}';`,
|
|
@@ -1380,6 +1481,7 @@ function serverEntrySource(root) {
|
|
|
1380
1481
|
`const entry = createServerEntry({`,
|
|
1381
1482
|
` dev: import.meta.env.DEV,`,
|
|
1382
1483
|
` resumeEntryPath,`,
|
|
1484
|
+
` prerenderWakeEntryPath,`,
|
|
1383
1485
|
` navigationEntryPath,`,
|
|
1384
1486
|
` routeModulePreloads,`,
|
|
1385
1487
|
` routeSsrModulePreloads,`,
|
|
@@ -1475,6 +1577,10 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1475
1577
|
includeChunk(ssrFileNames, chunksByFileName, input.resumeChunk.fileName);
|
|
1476
1578
|
for (const fileName of routeScopedDynamicImports(input.resumeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
|
|
1477
1579
|
}
|
|
1580
|
+
if (input.prerenderWakeChunk) {
|
|
1581
|
+
includeChunk(ssrFileNames, chunksByFileName, input.prerenderWakeChunk.fileName);
|
|
1582
|
+
for (const fileName of routeScopedDynamicImports(input.prerenderWakeChunk, routeFile)) includeChunk(ssrFileNames, chunksByFileName, fileName, true);
|
|
1583
|
+
}
|
|
1478
1584
|
includeChunk(ssrFileNames, chunksByFileName, routeChunk.fileName, true);
|
|
1479
1585
|
const routeSourceFiles = sourceFilesForChunks(new Set([...navigationFileNames, ...ssrFileNames]), chunksByFileName);
|
|
1480
1586
|
for (const [sourceFile, symbolFileNames] of symbolChunks) {
|
|
@@ -1486,7 +1592,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1486
1592
|
}
|
|
1487
1593
|
navigation[routeFile] = [...navigationFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1488
1594
|
ssr[routeFile] = [...ssrFileNames].map((fileName) => joinURL(input.base, fileName));
|
|
1489
|
-
styles[routeFile] =
|
|
1595
|
+
styles[routeFile] = routeStylesheetsForChunks(routeFileChunks, chunksByFileName, input.base);
|
|
1490
1596
|
}
|
|
1491
1597
|
return {
|
|
1492
1598
|
navigation,
|
|
@@ -1494,7 +1600,7 @@ function routeModulePreloadsFromBundle(input) {
|
|
|
1494
1600
|
styles
|
|
1495
1601
|
};
|
|
1496
1602
|
}
|
|
1497
|
-
function
|
|
1603
|
+
function routeStylesheetsForChunks(routeFileChunks, chunksByFileName, base) {
|
|
1498
1604
|
const styles = /* @__PURE__ */ new Set();
|
|
1499
1605
|
const visited = /* @__PURE__ */ new Set();
|
|
1500
1606
|
const visit = (chunk) => {
|
|
@@ -1506,7 +1612,7 @@ function routeStylesheetsForChunk(routeChunk, chunksByFileName, base) {
|
|
|
1506
1612
|
}
|
|
1507
1613
|
for (const stylesheet of chunk.viteMetadata?.importedCss ?? []) styles.add(joinURL(base, stylesheet));
|
|
1508
1614
|
};
|
|
1509
|
-
visit(
|
|
1615
|
+
for (const chunk of routeFileChunks) visit(chunk);
|
|
1510
1616
|
return [...styles];
|
|
1511
1617
|
}
|
|
1512
1618
|
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.0",
|
|
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/bundler": "0.
|
|
68
|
-
"@markless/web": "0.
|
|
67
|
+
"@markless/bundler": "0.3.0",
|
|
68
|
+
"@markless/web": "0.3.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@markless/analyzer": "0.
|
|
71
|
+
"@markless/analyzer": "0.3.0"
|
|
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,
|