@reckona/mreact-router 0.0.65 → 0.0.67
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/package.json +13 -12
- package/src/actions.ts +1130 -0
- package/src/adapters/aws-lambda.ts +993 -0
- package/src/adapters/cloudflare.ts +1286 -0
- package/src/adapters/devtools.ts +5 -0
- package/src/adapters/edge.ts +70 -0
- package/src/adapters/node.ts +126 -0
- package/src/adapters/static.ts +61 -0
- package/src/app-router-globals.ts +19 -0
- package/src/assets.ts +113 -0
- package/src/build.ts +2948 -0
- package/src/bundle-pipeline.ts +496 -0
- package/src/cache-config.ts +35 -0
- package/src/cache-stats.ts +54 -0
- package/src/cache.ts +418 -0
- package/src/cli-options.ts +296 -0
- package/src/cli.ts +94 -0
- package/src/client.ts +3398 -0
- package/src/config.ts +146 -0
- package/src/cookies.ts +113 -0
- package/src/csp.ts +103 -0
- package/src/csrf.ts +132 -0
- package/src/deferred.ts +52 -0
- package/src/dev-server.ts +262 -0
- package/src/file-conventions.ts +88 -0
- package/src/http.ts +128 -0
- package/src/i18n.ts +98 -0
- package/src/import-policy.ts +261 -0
- package/src/index.ts +221 -0
- package/src/link.ts +47 -0
- package/src/logger.ts +149 -0
- package/src/module-runner.ts +554 -0
- package/src/multipart.ts +577 -0
- package/src/native-escape.ts +53 -0
- package/src/native-route-matcher.ts +173 -0
- package/src/navigation-state.ts +98 -0
- package/src/navigation.ts +215 -0
- package/src/prerender-store.ts +233 -0
- package/src/render.ts +5187 -0
- package/src/route-path.ts +5 -0
- package/src/route-shells.ts +47 -0
- package/src/route-source.ts +224 -0
- package/src/route-styles.ts +91 -0
- package/src/routes.ts +362 -0
- package/src/runtime-cache.ts +8 -0
- package/src/runtime-state.ts +37 -0
- package/src/security-headers.ts +134 -0
- package/src/serve.ts +1265 -0
- package/src/session.ts +238 -0
- package/src/source-jsx.ts +30 -0
- package/src/source-modules.ts +69 -0
- package/src/stream-list.ts +45 -0
- package/src/trace.ts +114 -0
- package/src/types.ts +155 -0
- package/src/upgrade.ts +8 -0
- package/src/vite-config.ts +63 -0
- package/src/vite-plugin-cache-key.ts +53 -0
- package/src/vite.ts +690 -0
- package/src/workspace-packages.ts +67 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { emitRouterDevtoolsEvent } from "./devtools.js";
|
|
2
|
+
import {
|
|
3
|
+
emitRouterLog,
|
|
4
|
+
logDurationMs,
|
|
5
|
+
logError,
|
|
6
|
+
logNow,
|
|
7
|
+
requestLogFields,
|
|
8
|
+
type AppRouterLogger,
|
|
9
|
+
} from "../logger.js";
|
|
10
|
+
|
|
11
|
+
export type EdgeRequestHandler = (request: Request) => Response | Promise<Response>;
|
|
12
|
+
|
|
13
|
+
export interface EdgeRequestHandlerOptions {
|
|
14
|
+
logger?: AppRouterLogger | undefined;
|
|
15
|
+
onError?: ((error: unknown, request: Request) => Response | Promise<Response>) | undefined;
|
|
16
|
+
render: EdgeRequestHandler;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createEdgeRequestHandler(options: EdgeRequestHandlerOptions): EdgeRequestHandler {
|
|
20
|
+
return async (request) => {
|
|
21
|
+
const startedAt = logNow();
|
|
22
|
+
const logFields = requestLogFields(request, "edge");
|
|
23
|
+
emitRouterLog(options.logger, "info", {
|
|
24
|
+
...logFields,
|
|
25
|
+
type: "router:request:start",
|
|
26
|
+
});
|
|
27
|
+
emitRouterDevtoolsEvent({
|
|
28
|
+
method: request.method,
|
|
29
|
+
type: "router:request:start",
|
|
30
|
+
url: request.url,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await options.render(request);
|
|
35
|
+
emitRouterDevtoolsEvent({
|
|
36
|
+
method: request.method,
|
|
37
|
+
status: response.status,
|
|
38
|
+
type: "router:request:end",
|
|
39
|
+
url: request.url,
|
|
40
|
+
});
|
|
41
|
+
emitRouterLog(options.logger, "info", {
|
|
42
|
+
...logFields,
|
|
43
|
+
durationMs: logDurationMs(startedAt),
|
|
44
|
+
status: response.status,
|
|
45
|
+
type: "router:request:end",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return response;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
emitRouterDevtoolsEvent({
|
|
51
|
+
method: request.method,
|
|
52
|
+
type: "router:request:error",
|
|
53
|
+
url: request.url,
|
|
54
|
+
});
|
|
55
|
+
emitRouterLog(options.logger, "error", {
|
|
56
|
+
...logFields,
|
|
57
|
+
durationMs: logDurationMs(startedAt),
|
|
58
|
+
error: logError(error),
|
|
59
|
+
type: "router:request:error",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return options.onError === undefined
|
|
63
|
+
? new Response("Internal Server Error", {
|
|
64
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
65
|
+
status: 500,
|
|
66
|
+
})
|
|
67
|
+
: await options.onError(error, request);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { AppRouterCache } from "../cache.js";
|
|
3
|
+
import { nodeRequestToWebRequest, sendResponse } from "../http.js";
|
|
4
|
+
import type { AppRouterServerActionOptions } from "../actions.js";
|
|
5
|
+
import type { AppRouterImportPolicy } from "../import-policy.js";
|
|
6
|
+
import {
|
|
7
|
+
emitRouterLog,
|
|
8
|
+
logDurationMs,
|
|
9
|
+
logError,
|
|
10
|
+
logNow,
|
|
11
|
+
nodeRequestPath,
|
|
12
|
+
requestLogFields,
|
|
13
|
+
type AppRouterLogger,
|
|
14
|
+
} from "../logger.js";
|
|
15
|
+
import type { AppRouterResponseHook } from "../render.js";
|
|
16
|
+
import type { RouterInstrumentation } from "../trace.js";
|
|
17
|
+
|
|
18
|
+
export type { RouterInstrumentation } from "../trace.js";
|
|
19
|
+
import {
|
|
20
|
+
renderBuiltAppRequest,
|
|
21
|
+
resolveRequestHost,
|
|
22
|
+
warnIfImplicitHostTrust,
|
|
23
|
+
type AppRouterPrerenderStore,
|
|
24
|
+
type RequestHostPolicy,
|
|
25
|
+
type ResponseSinkStrategy,
|
|
26
|
+
} from "../serve.js";
|
|
27
|
+
|
|
28
|
+
export interface NodeRequestHandlerOptions {
|
|
29
|
+
allowedHosts?: readonly string[] | undefined;
|
|
30
|
+
errorHandler?:
|
|
31
|
+
| ((error: unknown) => {
|
|
32
|
+
body: string;
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
status: number;
|
|
35
|
+
})
|
|
36
|
+
| undefined;
|
|
37
|
+
hostPolicy?: RequestHostPolicy | undefined;
|
|
38
|
+
hostname?: string | undefined;
|
|
39
|
+
importPolicy?: AppRouterImportPolicy | undefined;
|
|
40
|
+
instrumentation?: RouterInstrumentation | undefined;
|
|
41
|
+
logger?: AppRouterLogger | undefined;
|
|
42
|
+
onResponse?: AppRouterResponseHook | undefined;
|
|
43
|
+
outDir: string;
|
|
44
|
+
port?: number | undefined;
|
|
45
|
+
prerenderStore?: AppRouterPrerenderStore | undefined;
|
|
46
|
+
routeCache?: AppRouterCache | undefined;
|
|
47
|
+
serverActions?: AppRouterServerActionOptions | undefined;
|
|
48
|
+
sinkStrategy?: ResponseSinkStrategy | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type NodeRequestHandler = (
|
|
52
|
+
incoming: IncomingMessage,
|
|
53
|
+
outgoing: ServerResponse,
|
|
54
|
+
) => Promise<void>;
|
|
55
|
+
|
|
56
|
+
export function createNodeRequestHandler(options: NodeRequestHandlerOptions): NodeRequestHandler {
|
|
57
|
+
warnIfImplicitHostTrust(options);
|
|
58
|
+
|
|
59
|
+
return async (incoming, outgoing) => {
|
|
60
|
+
const startedAt = logNow();
|
|
61
|
+
const fallbackRequestFields = {
|
|
62
|
+
method: incoming.method ?? "GET",
|
|
63
|
+
path: nodeRequestPath(incoming.url),
|
|
64
|
+
runtime: "node" as const,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const fallbackHost = `${options.hostname ?? "127.0.0.1"}:${options.port ?? 80}`;
|
|
69
|
+
const host = resolveRequestHost({
|
|
70
|
+
allowedHosts: options.allowedHosts,
|
|
71
|
+
fallbackHost,
|
|
72
|
+
hostPolicy: options.hostPolicy,
|
|
73
|
+
rawHost: incoming.headers.host,
|
|
74
|
+
});
|
|
75
|
+
const request = nodeRequestToWebRequest(incoming, `http://${host}`);
|
|
76
|
+
const logFields = requestLogFields(request, "node");
|
|
77
|
+
emitRouterLog(options.logger, "info", {
|
|
78
|
+
...logFields,
|
|
79
|
+
type: "router:request:start",
|
|
80
|
+
});
|
|
81
|
+
const response = await renderBuiltAppRequest({
|
|
82
|
+
outDir: options.outDir,
|
|
83
|
+
importPolicy: options.importPolicy,
|
|
84
|
+
instrumentation: options.instrumentation,
|
|
85
|
+
logger: options.logger,
|
|
86
|
+
onResponse: options.onResponse,
|
|
87
|
+
prerenderStore: options.prerenderStore,
|
|
88
|
+
request,
|
|
89
|
+
routeCache: options.routeCache,
|
|
90
|
+
serverActions: options.serverActions,
|
|
91
|
+
...(options.sinkStrategy === undefined ? {} : { sinkStrategy: options.sinkStrategy }),
|
|
92
|
+
});
|
|
93
|
+
emitRouterLog(options.logger, "info", {
|
|
94
|
+
...logFields,
|
|
95
|
+
durationMs: logDurationMs(startedAt),
|
|
96
|
+
status: response.status,
|
|
97
|
+
type: "router:request:end",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await sendResponse(outgoing, response);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
emitRouterLog(options.logger, "error", {
|
|
103
|
+
...fallbackRequestFields,
|
|
104
|
+
durationMs: logDurationMs(startedAt),
|
|
105
|
+
error: logError(error),
|
|
106
|
+
type: "router:request:error",
|
|
107
|
+
});
|
|
108
|
+
const payload = options.errorHandler
|
|
109
|
+
? options.errorHandler(error)
|
|
110
|
+
: { body: "Internal Server Error", status: 500 };
|
|
111
|
+
outgoing.statusCode = payload.status;
|
|
112
|
+
outgoing.setHeader(
|
|
113
|
+
"content-type",
|
|
114
|
+
payload.headers?.["content-type"] ?? "text/plain; charset=utf-8",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
for (const [name, value] of Object.entries(payload.headers ?? {})) {
|
|
118
|
+
if (name.toLowerCase() !== "content-type") {
|
|
119
|
+
outgoing.setHeader(name, value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
outgoing.end(payload.body);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { BuiltPrerenderedRoute, BuiltServerManifest } from "../build.js";
|
|
4
|
+
|
|
5
|
+
export interface StaticExportOptions {
|
|
6
|
+
exportDir: string;
|
|
7
|
+
outDir: string;
|
|
8
|
+
paths?: readonly string[] | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StaticExportResult {
|
|
12
|
+
routes: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function exportStaticApp(options: StaticExportOptions): Promise<StaticExportResult> {
|
|
16
|
+
const manifest = JSON.parse(
|
|
17
|
+
await readFile(join(options.outDir, "server", "manifest.json"), "utf8"),
|
|
18
|
+
) as BuiltServerManifest;
|
|
19
|
+
const prerenderedRoutes = manifest.prerenderedRoutes ?? {};
|
|
20
|
+
const routes = [...(options.paths ?? Object.keys(prerenderedRoutes))].sort();
|
|
21
|
+
|
|
22
|
+
await rm(options.exportDir, { force: true, recursive: true });
|
|
23
|
+
await mkdir(options.exportDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
for (const route of routes) {
|
|
26
|
+
const entry = prerenderedRoutes[route];
|
|
27
|
+
|
|
28
|
+
if (entry === undefined) {
|
|
29
|
+
throw new Error(`Cannot export non-prerendered route: ${route}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await writePrerenderedRoute(options.exportDir, route, entry);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await cp(join(options.outDir, "client"), join(options.exportDir, "_mreact", "client"), {
|
|
36
|
+
recursive: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return { routes };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function writePrerenderedRoute(
|
|
43
|
+
exportDir: string,
|
|
44
|
+
route: string,
|
|
45
|
+
entry: BuiltPrerenderedRoute,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
if (entry.status < 200 || entry.status >= 300) {
|
|
48
|
+
throw new Error(`Cannot export route ${route} with status ${entry.status}.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const file = join(exportDir, routeToHtmlFile(route));
|
|
52
|
+
await mkdir(dirname(file), { recursive: true });
|
|
53
|
+
await writeFile(file, entry.html);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function routeToHtmlFile(route: string): string {
|
|
57
|
+
const pathname = route.split("?")[0] ?? "/";
|
|
58
|
+
const normalized = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
59
|
+
|
|
60
|
+
return normalized === "" ? "index.html" : join(normalized, "index.html");
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactCompatElement, ReactCompatNode } from "@reckona/mreact-compat";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
function Await<T>(props: {
|
|
5
|
+
children?: (value: Awaited<T>) => ReactCompatNode;
|
|
6
|
+
catch?: (error: Error) => ReactCompatNode;
|
|
7
|
+
placeholder?: ReactCompatNode;
|
|
8
|
+
placeholderAs?: string;
|
|
9
|
+
value: T | PromiseLike<T>;
|
|
10
|
+
}): ReactCompatElement;
|
|
11
|
+
|
|
12
|
+
function Slot(props?: {
|
|
13
|
+
children?: ReactCompatNode;
|
|
14
|
+
name?: string;
|
|
15
|
+
[attribute: string]: unknown;
|
|
16
|
+
}): ReactCompatElement;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {};
|
package/src/assets.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export interface AssetManifestEntry {
|
|
2
|
+
assets?: readonly string[];
|
|
3
|
+
css?: readonly string[];
|
|
4
|
+
file: string;
|
|
5
|
+
imports?: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type AssetManifest = Readonly<Record<string, AssetManifestEntry>>;
|
|
9
|
+
|
|
10
|
+
export interface AssetHelperOptions {
|
|
11
|
+
base?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AssetLinkDescriptor {
|
|
15
|
+
attrs: Record<string, string>;
|
|
16
|
+
tag: "link";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function assetHref(
|
|
20
|
+
manifest: AssetManifest,
|
|
21
|
+
key: string,
|
|
22
|
+
options: AssetHelperOptions = {},
|
|
23
|
+
): string {
|
|
24
|
+
return assetPath(manifestEntry(manifest, key).file, options.base);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function assetPreloadLinks(
|
|
28
|
+
manifest: AssetManifest,
|
|
29
|
+
keys: readonly string[] | string,
|
|
30
|
+
options: AssetHelperOptions = {},
|
|
31
|
+
): AssetLinkDescriptor[] {
|
|
32
|
+
const seen = new Set<string>();
|
|
33
|
+
const links: AssetLinkDescriptor[] = [];
|
|
34
|
+
|
|
35
|
+
for (const key of Array.isArray(keys) ? keys : [keys]) {
|
|
36
|
+
const entry = manifestEntry(manifest, key);
|
|
37
|
+
|
|
38
|
+
pushUniqueLink(seen, links, {
|
|
39
|
+
attrs: {
|
|
40
|
+
href: assetPath(entry.file, options.base),
|
|
41
|
+
rel: entry.file.endsWith(".js") ? "modulepreload" : "preload",
|
|
42
|
+
},
|
|
43
|
+
tag: "link",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
for (const css of entry.css ?? []) {
|
|
47
|
+
pushUniqueLink(seen, links, {
|
|
48
|
+
attrs: {
|
|
49
|
+
href: assetPath(css, options.base),
|
|
50
|
+
rel: "stylesheet",
|
|
51
|
+
},
|
|
52
|
+
tag: "link",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const asset of entry.assets ?? []) {
|
|
57
|
+
pushUniqueLink(seen, links, {
|
|
58
|
+
attrs: {
|
|
59
|
+
as: preloadAs(asset),
|
|
60
|
+
href: assetPath(asset, options.base),
|
|
61
|
+
rel: "preload",
|
|
62
|
+
},
|
|
63
|
+
tag: "link",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return links;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function manifestEntry(manifest: AssetManifest, key: string): AssetManifestEntry {
|
|
72
|
+
const entry = manifest[key];
|
|
73
|
+
|
|
74
|
+
if (entry === undefined) {
|
|
75
|
+
throw new Error(`Asset manifest entry not found: ${key}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return entry;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function assetPath(file: string, base = "/"): string {
|
|
82
|
+
const normalizedBase = base.endsWith("/") ? base : `${base}/`;
|
|
83
|
+
const normalizedFile = file.startsWith("/") ? file.slice(1) : file;
|
|
84
|
+
|
|
85
|
+
return `${normalizedBase}${normalizedFile}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function pushUniqueLink(
|
|
89
|
+
seen: Set<string>,
|
|
90
|
+
links: AssetLinkDescriptor[],
|
|
91
|
+
link: AssetLinkDescriptor,
|
|
92
|
+
): void {
|
|
93
|
+
const key = JSON.stringify(link);
|
|
94
|
+
|
|
95
|
+
if (seen.has(key)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
seen.add(key);
|
|
100
|
+
links.push(link);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function preloadAs(file: string): string {
|
|
104
|
+
if (/\.(?:avif|gif|jpe?g|png|svg|webp)$/i.test(file)) {
|
|
105
|
+
return "image";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (/\.(?:woff2?|ttf|otf)$/i.test(file)) {
|
|
109
|
+
return "font";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return "fetch";
|
|
113
|
+
}
|