@markless/router 0.1.0 → 0.1.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/dist/index.d.ts +26 -1
- package/dist/index.js +152 -1
- package/dist/vite.js +1 -1
- package/package.json +5 -4
- package/src/vite/entries/client-entry.ts +67 -0
- package/src/vite/entries/resume-entry.ts +42 -0
- package/src/vite/entries/route-discovery.ts +8 -0
- package/src/vite/entries/route-href.ts +35 -0
- package/src/vite/entries/server-entry.ts +22 -0
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,31 @@ declare function transformRequestFileSource(fileId: string, sourceText: string):
|
|
|
79
79
|
//#region src/route-renderer.d.ts
|
|
80
80
|
declare function startRouteUpdateRenderer(document?: Document): void;
|
|
81
81
|
//#endregion
|
|
82
|
+
//#region src/spa-navigation.d.ts
|
|
83
|
+
type MarklessRouterNavigationRuntime = Pick<Navigation, 'addEventListener' | 'navigate'>;
|
|
84
|
+
interface MarklessRouterNavigationWindow {
|
|
85
|
+
readonly document: Document;
|
|
86
|
+
readonly location: Location;
|
|
87
|
+
navigation?: MarklessRouterNavigationRuntime;
|
|
88
|
+
addEventListener(type: 'click', listener: (event: MouseEvent) => void, options?: boolean | AddEventListenerOptions): void;
|
|
89
|
+
}
|
|
90
|
+
interface MarklessRouterNavigationPolyfillModule {
|
|
91
|
+
applyPolyfill(options: {
|
|
92
|
+
readonly interceptEvents: boolean;
|
|
93
|
+
readonly window: MarklessRouterNavigationWindow;
|
|
94
|
+
}): unknown;
|
|
95
|
+
}
|
|
96
|
+
interface StartSpaNavigationOptions {
|
|
97
|
+
readonly documentModuleLoader?: () => Promise<unknown>;
|
|
98
|
+
readonly loadPolyfill?: () => Promise<MarklessRouterNavigationPolyfillModule>;
|
|
99
|
+
readonly pageModuleLoaders: Record<string, () => Promise<unknown>>;
|
|
100
|
+
readonly preloadRouteModule?: (file: string) => unknown;
|
|
101
|
+
readonly routeFileIds: readonly string[];
|
|
102
|
+
readonly window?: MarklessRouterNavigationWindow;
|
|
103
|
+
}
|
|
104
|
+
declare function __marklessRouterStartSpaNavigation(options: StartSpaNavigationOptions): Promise<void>;
|
|
105
|
+
declare function ensureNavigationRuntime(runtimeWindow?: MarklessRouterNavigationWindow, loadPolyfill?: () => Promise<MarklessRouterNavigationPolyfillModule>): Promise<MarklessRouterNavigationRuntime>;
|
|
106
|
+
//#endregion
|
|
82
107
|
//#region src/route-state.d.ts
|
|
83
108
|
declare const MARKLESS_ROUTER_ROUTE_EVENT = "marklessrouternavigate";
|
|
84
109
|
interface RouteState {
|
|
@@ -172,4 +197,4 @@ declare const Link: ((props?: LinkProps) => unknown) & {
|
|
|
172
197
|
};
|
|
173
198
|
};
|
|
174
199
|
//#endregion
|
|
175
|
-
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, buildRouteManifestFromFileIds, dispatchRouteUpdate, matchRouteManifest, normalizeRequestFileId, normalizeRequestPathname, normalizeRouteFileId, parseRequestFile, routePageProps, startRouteUpdateRenderer, transformRequestFileSource };
|
|
200
|
+
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
|
@@ -60,6 +60,157 @@ async function renderRouteUpdate(document, update) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
//#endregion
|
|
63
|
+
//#region src/spa-navigation.ts
|
|
64
|
+
const STARTED = "__marklessRouterSpaNavigationStarted";
|
|
65
|
+
const LINK_ATTRIBUTE = "data-markless-router-link";
|
|
66
|
+
const REPLACE_ATTRIBUTE = "data-markless-router-replace";
|
|
67
|
+
const SCROLL_ATTRIBUTE = "data-markless-router-scroll";
|
|
68
|
+
const LINK_INFO = "__marklessRouterLink";
|
|
69
|
+
function hashRoutePath(url) {
|
|
70
|
+
return url.hash.startsWith("#/") ? url.hash.slice(1) : void 0;
|
|
71
|
+
}
|
|
72
|
+
function routePathname(url, _context) {
|
|
73
|
+
return hashRoutePath(url) ?? url.pathname;
|
|
74
|
+
}
|
|
75
|
+
async function __marklessRouterStartSpaNavigation(options) {
|
|
76
|
+
const runtimeWindow = options.window ?? browserWindow();
|
|
77
|
+
const state = runtimeWindow;
|
|
78
|
+
if (state[STARTED]) return;
|
|
79
|
+
state[STARTED] = true;
|
|
80
|
+
const context = {
|
|
81
|
+
documentModuleLoader: options.documentModuleLoader,
|
|
82
|
+
manifest: buildRouteManifestFromFileIds(options.routeFileIds),
|
|
83
|
+
pageModuleLoaders: options.pageModuleLoaders,
|
|
84
|
+
preloadRouteModule: options.preloadRouteModule,
|
|
85
|
+
window: runtimeWindow
|
|
86
|
+
};
|
|
87
|
+
const navigation = await ensureNavigationRuntime(runtimeWindow, options.loadPolyfill);
|
|
88
|
+
runtimeWindow.addEventListener("click", (event) => handleLinkClick(event, context, navigation), true);
|
|
89
|
+
navigation.addEventListener("navigate", (event) => {
|
|
90
|
+
handleNavigateEvent(event, context);
|
|
91
|
+
});
|
|
92
|
+
{
|
|
93
|
+
const bootUrl = parseSameOriginUrl(runtimeWindow.location.href, runtimeWindow.location.href);
|
|
94
|
+
const bootHashPath = bootUrl ? hashRoutePath(bootUrl) : void 0;
|
|
95
|
+
if (bootUrl && bootHashPath && matchRouteManifest(bootHashPath, context.manifest)) renderRoute(bootUrl, context);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function ensureNavigationRuntime(runtimeWindow = browserWindow(), loadPolyfill) {
|
|
99
|
+
if (!runtimeWindow.navigation) {
|
|
100
|
+
const { applyPolyfill } = await (loadPolyfill?.() ?? import("@virtualstate/navigation"));
|
|
101
|
+
runtimeWindow.navigation = applyPolyfill({
|
|
102
|
+
interceptEvents: false,
|
|
103
|
+
window: runtimeWindow
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return runtimeWindow.navigation;
|
|
107
|
+
}
|
|
108
|
+
function handleNavigateEvent(event, context) {
|
|
109
|
+
const url = routeUrl(event, context);
|
|
110
|
+
if (!url) return false;
|
|
111
|
+
event.intercept({
|
|
112
|
+
focusReset: "after-transition",
|
|
113
|
+
scroll: navigationScroll(event),
|
|
114
|
+
handler: () => renderRoute(url, context, event.signal)
|
|
115
|
+
});
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
async function renderRoute(url, context, signal) {
|
|
119
|
+
if (signal?.aborted) return;
|
|
120
|
+
const match = matchRouteManifest(routePathname(url, context), context.manifest);
|
|
121
|
+
const loadPageModule = match && context.pageModuleLoaders[match.route.file];
|
|
122
|
+
if (!match || !loadPageModule) {
|
|
123
|
+
if (hashRoutePath(url) !== void 0) warnUnknownHashRoute(url);
|
|
124
|
+
context.window.location.assign(url.href);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
preloadRouteModule(context, match.route.file);
|
|
128
|
+
const [page, document] = await Promise.all([loadPageModule(), context.documentModuleLoader?.()]);
|
|
129
|
+
if (signal?.aborted) return;
|
|
130
|
+
const update = {
|
|
131
|
+
document,
|
|
132
|
+
page,
|
|
133
|
+
route: {
|
|
134
|
+
file: match.route.file,
|
|
135
|
+
params: match.params,
|
|
136
|
+
status: 200,
|
|
137
|
+
url: url.href
|
|
138
|
+
},
|
|
139
|
+
signal
|
|
140
|
+
};
|
|
141
|
+
if (!(context.window.document["__marklessRouterRouteRendererStarted"] === true)) {
|
|
142
|
+
dispatchRouteUpdate(context.window.document, update);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await new Promise((resolve) => {
|
|
146
|
+
dispatchRouteUpdate(context.window.document, {
|
|
147
|
+
...update,
|
|
148
|
+
onRendered: resolve
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function warnUnknownHashRoute(url) {
|
|
153
|
+
if (!import.meta.env?.DEV) return;
|
|
154
|
+
console.error(`MARKLESS_ROUTER_UNKNOWN_HASH_ROUTE: Navigation to "${url.hash}" matched no route file. Hash URLs cannot fall back to a server document load, so this navigation does nothing. Add a page for "${url.hash.slice(1)}" or fix the link href.`);
|
|
155
|
+
}
|
|
156
|
+
function handleLinkClick(event, context, navigation) {
|
|
157
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return;
|
|
158
|
+
const anchor = sourceAnchor(event);
|
|
159
|
+
if (!anchor || !anchor.hasAttribute(LINK_ATTRIBUTE) || !isEligibleLink(anchor)) return;
|
|
160
|
+
const url = parseSameOriginUrl(anchor.href, context.window.location.href);
|
|
161
|
+
const match = url && matchRouteManifest(routePathname(url, context), context.manifest);
|
|
162
|
+
if (!match) return;
|
|
163
|
+
preloadRouteModule(context, match.route.file);
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
navigation.navigate(url.href, {
|
|
166
|
+
history: anchor.hasAttribute(REPLACE_ATTRIBUTE) ? "replace" : "push",
|
|
167
|
+
info: {
|
|
168
|
+
[LINK_INFO]: true,
|
|
169
|
+
scroll: anchor.getAttribute(SCROLL_ATTRIBUTE) === "manual" ? "manual" : void 0
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function preloadRouteModule(context, file) {
|
|
174
|
+
try {
|
|
175
|
+
context.preloadRouteModule?.(file);
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
178
|
+
function routeUrl(event, context) {
|
|
179
|
+
const destination = parseSameOriginUrl(event.destination.url, context.window.location.href);
|
|
180
|
+
const hashRouteNavigation = event.hashChange === true && !!destination && hashRoutePath(destination) !== void 0;
|
|
181
|
+
if (!isMarklessRouterNavigation(event) && !hashRouteNavigation || event.canIntercept === false || event.navigationType === "reload" || event.hashChange && !hashRouteNavigation || event.downloadRequest != null || event.formData != null) return;
|
|
182
|
+
const matched = destination && matchRouteManifest(routePathname(destination), context.manifest) ? destination : void 0;
|
|
183
|
+
if (!matched && hashRouteNavigation && destination) warnUnknownHashRoute(destination);
|
|
184
|
+
return matched;
|
|
185
|
+
}
|
|
186
|
+
function isMarklessRouterNavigation(event) {
|
|
187
|
+
return event.info?.[LINK_INFO] === true || event.navigationType === "traverse";
|
|
188
|
+
}
|
|
189
|
+
function navigationScroll(event) {
|
|
190
|
+
const info = event.info;
|
|
191
|
+
return info?.[LINK_INFO] === true && info.scroll === "manual" ? "manual" : "after-transition";
|
|
192
|
+
}
|
|
193
|
+
function parseSameOriginUrl(href, base) {
|
|
194
|
+
try {
|
|
195
|
+
const current = new URL(base);
|
|
196
|
+
const url = new URL(href, current);
|
|
197
|
+
return url.origin === current.origin ? url : void 0;
|
|
198
|
+
} catch {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function sourceAnchor(event) {
|
|
203
|
+
const target = event.composedPath?.()[0] ?? event.target;
|
|
204
|
+
return target?.closest?.("a[href]") ?? target?.parentElement?.closest?.("a[href]");
|
|
205
|
+
}
|
|
206
|
+
function isEligibleLink(anchor) {
|
|
207
|
+
const target = anchor.getAttribute("target");
|
|
208
|
+
return (!target || target === "_self") && !anchor.hasAttribute("download") && !anchor.relList?.contains("external");
|
|
209
|
+
}
|
|
210
|
+
function browserWindow() {
|
|
211
|
+
return window;
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
63
214
|
//#region src/index.ts
|
|
64
215
|
function __marklessCreateHttpContext(event) {
|
|
65
216
|
const context = event.context ?? {};
|
|
@@ -120,4 +271,4 @@ function escapeHtml(value) {
|
|
|
120
271
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
121
272
|
}
|
|
122
273
|
//#endregion
|
|
123
|
-
export { Html, Link, MARKLESS_ROUTER_ROUTE_EVENT, __marklessCreateHttpContext, buildRouteManifestFromFileIds, dispatchRouteUpdate, matchRouteManifest, normalizeRequestFileId, normalizeRequestPathname, normalizeRouteFileId, parseRequestFile, routePageProps, startRouteUpdateRenderer, transformRequestFileSource };
|
|
274
|
+
export { Html, Link, MARKLESS_ROUTER_ROUTE_EVENT, __marklessCreateHttpContext, __marklessRouterStartSpaNavigation, buildRouteManifestFromFileIds, dispatchRouteUpdate, ensureNavigationRuntime, matchRouteManifest, normalizeRequestFileId, normalizeRequestPathname, normalizeRouteFileId, parseRequestFile, routePageProps, startRouteUpdateRenderer, transformRequestFileSource };
|
package/dist/vite.js
CHANGED
|
@@ -851,7 +851,7 @@ const ROUTE_HREF_ID = "virtual:markless-router/route-href";
|
|
|
851
851
|
const ROUTER_OPTIONS_ID = "virtual:markless-router/options";
|
|
852
852
|
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)(?:\?.*)?$/;
|
|
853
853
|
const VITE_PLUGIN_FILE = decodePath(parseURL(import.meta.url).pathname);
|
|
854
|
-
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "entries");
|
|
854
|
+
const VIRTUAL_ENTRY_DIR = VITE_PLUGIN_FILE.endsWith(".ts") ? join(dirname(VITE_PLUGIN_FILE), "entries") : join(dirname(dirname(VITE_PLUGIN_FILE)), "src/vite/entries");
|
|
855
855
|
const DEFAULT_WATCH_IGNORES = [
|
|
856
856
|
"**/.nitro/**",
|
|
857
857
|
"**/.output/**",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markless/router",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Markless router: client-side navigation with SSR streaming and build-time preload maps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"src/vite/entries"
|
|
16
17
|
],
|
|
17
18
|
"type": "module",
|
|
18
19
|
"sideEffects": false,
|
|
@@ -54,8 +55,8 @@
|
|
|
54
55
|
"pathe": "^2.0.3",
|
|
55
56
|
"satteri": "0.6.3",
|
|
56
57
|
"ufo": "^1.6.4",
|
|
57
|
-
"@markless/bundler": "0.1.
|
|
58
|
-
"@markless/web": "0.1.
|
|
58
|
+
"@markless/bundler": "0.1.1",
|
|
59
|
+
"@markless/web": "0.1.1"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"vite": "8.0.16"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Package specifiers only: this file ships as source in the tarball and is
|
|
2
|
+
// compiled in the CONSUMER app's module graph (so import.meta.glob resolves
|
|
3
|
+
// against the app root) — relative paths into src/ would escape the package.
|
|
4
|
+
import { createRouteDiscovery } from '@markless/router/vite/runtime/create-route-discovery';
|
|
5
|
+
import {
|
|
6
|
+
__marklessRouterStartSpaNavigation,
|
|
7
|
+
buildRouteManifestFromFileIds,
|
|
8
|
+
ensureNavigationRuntime,
|
|
9
|
+
matchRouteManifest,
|
|
10
|
+
startRouteUpdateRenderer,
|
|
11
|
+
} from '@markless/router';
|
|
12
|
+
import { preloadRouteModule } from 'virtual:markless-router/route-preloads';
|
|
13
|
+
|
|
14
|
+
const routeDiscovery = createRouteDiscovery(
|
|
15
|
+
import.meta.glob(['/pages/**/*.tsrx', '/pages/**/*.mdx']),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const pageModules = routeDiscovery.pageModuleLoaders;
|
|
19
|
+
export const routeFileIds = routeDiscovery.routeFileIds;
|
|
20
|
+
const routeManifest = buildRouteManifestFromFileIds(routeFileIds);
|
|
21
|
+
|
|
22
|
+
void __marklessRouterStartSpaNavigation({
|
|
23
|
+
pageModuleLoaders: pageModules,
|
|
24
|
+
preloadRouteModule,
|
|
25
|
+
routeFileIds,
|
|
26
|
+
});
|
|
27
|
+
startRouteUpdateRenderer(document);
|
|
28
|
+
|
|
29
|
+
export async function navigateMarklessRouterLink(input: {
|
|
30
|
+
readonly href: string;
|
|
31
|
+
readonly replace?: boolean;
|
|
32
|
+
readonly scroll?: 'manual';
|
|
33
|
+
}) {
|
|
34
|
+
const url = parseSameOriginUrl(input.href, window.location.href);
|
|
35
|
+
const match = url && matchRouteManifest(url.pathname, routeManifest);
|
|
36
|
+
if (!url || !match) {
|
|
37
|
+
window.location.assign(input.href);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
startRouteUpdateRenderer(document);
|
|
42
|
+
preloadRouteModule(match.route.file);
|
|
43
|
+
await __marklessRouterStartSpaNavigation({
|
|
44
|
+
pageModuleLoaders: pageModules,
|
|
45
|
+
preloadRouteModule,
|
|
46
|
+
routeFileIds,
|
|
47
|
+
window,
|
|
48
|
+
});
|
|
49
|
+
const navigation = await ensureNavigationRuntime(window);
|
|
50
|
+
navigation.navigate(url.href, {
|
|
51
|
+
history: input.replace ? 'replace' : 'push',
|
|
52
|
+
info: {
|
|
53
|
+
__marklessRouterLink: true,
|
|
54
|
+
scroll: input.scroll,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseSameOriginUrl(href: string, base: string): URL | undefined {
|
|
60
|
+
try {
|
|
61
|
+
const current = new URL(base);
|
|
62
|
+
const url = new URL(href, current);
|
|
63
|
+
return url.origin === current.origin ? url : undefined;
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const mdxPageModuleLoaders = import.meta.glob('/pages/**/*.mdx');
|
|
2
|
+
const tsrxResumeModuleLoaders = import.meta.glob('/pages/**/*.tsrx', {
|
|
3
|
+
query: '?markless-resume',
|
|
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 loadRouteResumeModule = file && routeResumeModuleLoader(file);
|
|
12
|
+
const routeResumeModule = loadRouteResumeModule ? await loadRouteResumeModule() : undefined;
|
|
13
|
+
const resume = (routeResumeModule as RouteResumeModule | undefined)?.resumeContainerEvent;
|
|
14
|
+
if (typeof resume !== 'function') {
|
|
15
|
+
throw new Error(`Markless Router could not resume route module: ${file ?? '<unknown>'}`);
|
|
16
|
+
}
|
|
17
|
+
await resume(input);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RouteResumeModule {
|
|
21
|
+
readonly resumeContainerEvent?: (input: unknown) => unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function routeResumeModuleLoader(file: string): (() => Promise<unknown>) | undefined {
|
|
25
|
+
const loaders = file.endsWith('.mdx') ? mdxPageModuleLoaders : tsrxResumeModuleLoaders;
|
|
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) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const route = JSON.parse(text) as { readonly file?: unknown };
|
|
38
|
+
return typeof route.file === 'string' ? route.file : undefined;
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createRouteDiscovery } from '@markless/router/vite/runtime/create-route-discovery';
|
|
2
|
+
|
|
3
|
+
const routeDiscovery = createRouteDiscovery(
|
|
4
|
+
import.meta.glob(['/pages/**/*.tsrx', '/pages/**/*.mdx']),
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export const pageModuleLoaders = routeDiscovery.pageModuleLoaders;
|
|
8
|
+
export const routeFileIds = routeDiscovery.routeFileIds;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const routeParamPattern = /\[\.\.\.([A-Za-z_$][\w$]*)\]|\[([A-Za-z_$][\w$]*)\]/g;
|
|
2
|
+
|
|
3
|
+
export function __marklessRouteHref(
|
|
4
|
+
pattern: string,
|
|
5
|
+
params: Readonly<Record<string, unknown>>,
|
|
6
|
+
): string {
|
|
7
|
+
return pattern.replace(routeParamPattern, (_match, catchAll, dynamic) => {
|
|
8
|
+
const name = catchAll || dynamic;
|
|
9
|
+
const value = params?.[name];
|
|
10
|
+
|
|
11
|
+
if (catchAll) {
|
|
12
|
+
return encodeCatchAllParam(pattern, value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (value === null || value === undefined) {
|
|
16
|
+
throw new Error(`Typed route error: ${pattern} requires params:\n- ${name}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return encodeURIComponent(String(value));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function encodeCatchAllParam(pattern: string, value: unknown): string {
|
|
24
|
+
const segments = Array.isArray(value)
|
|
25
|
+
? value
|
|
26
|
+
: value === null || value === undefined
|
|
27
|
+
? []
|
|
28
|
+
: String(value).split('/');
|
|
29
|
+
|
|
30
|
+
if (segments.length === 0 || segments.some((segment) => String(segment) === '')) {
|
|
31
|
+
throw new Error(`Typed route error: ${pattern} requires a non-empty catch-all param.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return segments.map((segment) => encodeURIComponent(String(segment))).join('/');
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createServerEntry } from '@markless/router/vite/runtime/create-server-entry';
|
|
2
|
+
import { resumeEntryPath } from 'virtual:markless-router/resume-entry-path';
|
|
3
|
+
import { navigationEntryPath } from 'virtual:markless-router/navigation-entry-path';
|
|
4
|
+
import {
|
|
5
|
+
routeModulePreloads,
|
|
6
|
+
routeSsrModulePreloads,
|
|
7
|
+
} from 'virtual:markless-router/route-preloads';
|
|
8
|
+
import { pageModuleLoaders, routeFileIds } from 'virtual:markless-router/routes';
|
|
9
|
+
|
|
10
|
+
const documentModuleLoaders = import.meta.glob(['/document.tsrx']);
|
|
11
|
+
const entry = createServerEntry({
|
|
12
|
+
resumeEntryPath,
|
|
13
|
+
navigationEntryPath,
|
|
14
|
+
routeModulePreloads,
|
|
15
|
+
routeSsrModulePreloads,
|
|
16
|
+
documentModuleLoader: documentModuleLoaders['/document.tsrx'],
|
|
17
|
+
pageModuleLoaders,
|
|
18
|
+
routeFileIds,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const fetch = entry.fetch;
|
|
22
|
+
export default entry;
|