@reckona/mreact-router 0.0.189 → 0.0.191
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 +17 -4
- package/bin/mreact-router.js +23 -0
- package/dist/adapters/aws-lambda.d.ts +33 -13
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +128 -20
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +18 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +2 -0
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +16 -2
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +57 -12
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts +8 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +34 -13
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +4 -1
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +63 -3
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +22 -1
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +130 -5
- package/dist/link.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +514 -517
- package/dist/render.js.map +1 -1
- package/dist/request.d.ts +10 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +6 -0
- package/dist/request.js.map +1 -0
- package/dist/typed-routes.d.ts +4 -5
- package/dist/typed-routes.d.ts.map +1 -1
- package/dist/typed-routes.js.map +1 -1
- package/dist/vite.d.ts +6 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +4 -1
- package/dist/vite.js.map +1 -1
- package/package.json +19 -13
- package/src/adapters/aws-lambda.ts +227 -56
- package/src/adapters/cloudflare.ts +59 -0
- package/src/adapters/edge.ts +14 -0
- package/src/adapters/node.ts +44 -2
- package/src/build.ts +113 -16
- package/src/cache.ts +39 -17
- package/src/cli-options.ts +92 -6
- package/src/cli.ts +12 -0
- package/src/client.ts +2 -1
- package/src/config.ts +10 -2
- package/src/index.ts +57 -10
- package/src/link.ts +205 -10
- package/src/render.ts +5 -7
- package/src/request.ts +35 -0
- package/src/typed-routes.ts +4 -4
- package/src/vite.ts +30 -1
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { defer, isDeferredLoaderData } from "./deferred.js";
|
|
|
7
7
|
export type { DeferredLoaderData } from "./deferred.js";
|
|
8
8
|
export { definePage } from "./types.js";
|
|
9
9
|
export { Link, linkProps } from "./link.js";
|
|
10
|
+
export type { LinkSerializableAttribute, LinkSinkChild, LinkSinkProps } from "./link.js";
|
|
10
11
|
export { href } from "./typed-routes.js";
|
|
11
12
|
export { parseMultipartStream } from "./multipart.js";
|
|
12
13
|
export type {
|
|
@@ -35,7 +36,7 @@ export {
|
|
|
35
36
|
textError,
|
|
36
37
|
throwNotFound,
|
|
37
38
|
} from "./navigation.js";
|
|
38
|
-
export type { ParseSchema } from "./navigation.js";
|
|
39
|
+
export type { MiddlewareNext, ParseSchema, RedirectOptions, RequestCookies } from "./navigation.js";
|
|
39
40
|
export { createMemoryPrerenderStore } from "./prerender-store.js";
|
|
40
41
|
export { getServerRuntimeState } from "./runtime-state.js";
|
|
41
42
|
import {
|
|
@@ -57,43 +58,80 @@ import type {
|
|
|
57
58
|
*
|
|
58
59
|
* @deprecated Import session helpers from `@reckona/mreact-auth` instead.
|
|
59
60
|
*/
|
|
60
|
-
export
|
|
61
|
+
export function createMemorySessionStore<TData = unknown>(
|
|
62
|
+
options: MemorySessionStoreOptionsInternal = {},
|
|
63
|
+
): SessionStoreInternal<TData> {
|
|
64
|
+
return createMemorySessionStoreInternal<TData>(options);
|
|
65
|
+
}
|
|
61
66
|
/**
|
|
62
67
|
* Creates a deprecated session record and cookie alias.
|
|
63
68
|
*
|
|
64
69
|
* @deprecated Import session helpers from `@reckona/mreact-auth` instead.
|
|
65
70
|
*/
|
|
66
|
-
export
|
|
71
|
+
export async function createSession<TData>(
|
|
72
|
+
response: Response,
|
|
73
|
+
store: SessionStoreInternal<TData>,
|
|
74
|
+
data: TData,
|
|
75
|
+
options: SessionCookieOptionsInternal = {},
|
|
76
|
+
): Promise<SessionRecordInternal<TData>> {
|
|
77
|
+
return createSessionInternal(response, store, data, options);
|
|
78
|
+
}
|
|
67
79
|
/**
|
|
68
80
|
* Destroys a session through a deprecated router session alias.
|
|
69
81
|
*
|
|
70
82
|
* @deprecated Import session helpers from `@reckona/mreact-auth` instead.
|
|
71
83
|
*/
|
|
72
|
-
export
|
|
84
|
+
export async function destroySession<TData>(
|
|
85
|
+
request: Request,
|
|
86
|
+
response: Response,
|
|
87
|
+
store: SessionStoreInternal<TData>,
|
|
88
|
+
options: SessionCookieOptionsInternal = {},
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
return destroySessionInternal(request, response, store, options);
|
|
91
|
+
}
|
|
73
92
|
/**
|
|
74
93
|
* Reads a session through a deprecated router session alias.
|
|
75
94
|
*
|
|
76
95
|
* @deprecated Import session helpers from `@reckona/mreact-auth` instead.
|
|
77
96
|
*/
|
|
78
|
-
export
|
|
97
|
+
export async function getSession<TData>(
|
|
98
|
+
request: Request,
|
|
99
|
+
store: SessionStoreInternal<TData>,
|
|
100
|
+
options: SessionCookieOptionsInternal = {},
|
|
101
|
+
): Promise<SessionRecordInternal<TData> | undefined> {
|
|
102
|
+
return getSessionInternal(request, store, options);
|
|
103
|
+
}
|
|
79
104
|
/**
|
|
80
105
|
* Rotates a session through a deprecated router session alias.
|
|
81
106
|
*
|
|
82
107
|
* @deprecated Import session helpers from `@reckona/mreact-auth` instead.
|
|
83
108
|
*/
|
|
84
|
-
export
|
|
109
|
+
export async function rotateSession<TData>(
|
|
110
|
+
request: Request,
|
|
111
|
+
response: Response,
|
|
112
|
+
store: SessionStoreInternal<TData>,
|
|
113
|
+
options: SessionCookieOptionsInternal = {},
|
|
114
|
+
): Promise<SessionRecordInternal<TData> | undefined> {
|
|
115
|
+
return rotateSessionInternal(request, response, store, options);
|
|
116
|
+
}
|
|
85
117
|
export type {
|
|
86
118
|
AwsLambdaArtifactManifest,
|
|
119
|
+
AwsLambdaGeneratedHandlerPreloadMode,
|
|
87
120
|
BuildAppPhase,
|
|
88
121
|
BuildAppPhaseTiming,
|
|
89
122
|
BuildAppOptions,
|
|
90
123
|
BuildAppProgressEvent,
|
|
91
124
|
BuildAppResult,
|
|
92
125
|
BuiltImportPolicyArtifact,
|
|
126
|
+
BuiltPrerenderedRoute,
|
|
127
|
+
BuiltRouteSourceAnalysisSummary,
|
|
128
|
+
BuiltServerModuleArtifact,
|
|
129
|
+
BuiltServerModuleOutput,
|
|
93
130
|
CloudflarePagesArtifactManifest,
|
|
94
131
|
PackageAwsLambdaArtifactOptions,
|
|
95
132
|
PackageCloudflarePagesArtifactOptions,
|
|
96
133
|
} from "./build.js";
|
|
134
|
+
export type { AppRouterProjectOptions } from "./config.js";
|
|
97
135
|
export type { ServerActionContext } from "./actions.js";
|
|
98
136
|
export type {
|
|
99
137
|
AppRouteHref,
|
|
@@ -106,7 +144,11 @@ export type {
|
|
|
106
144
|
RouteParamsFor,
|
|
107
145
|
RouteSearchParams,
|
|
108
146
|
RouteSearchValue,
|
|
147
|
+
SegmentRouteParam,
|
|
109
148
|
StaticHrefOptions,
|
|
149
|
+
ExtractRouteParams,
|
|
150
|
+
HasRouteParams,
|
|
151
|
+
Simplify,
|
|
110
152
|
} from "./typed-routes.js";
|
|
111
153
|
export type {
|
|
112
154
|
InferLoaderData,
|
|
@@ -180,7 +222,7 @@ export type {
|
|
|
180
222
|
} from "./navigation-state.js";
|
|
181
223
|
export type { RouterRuntimeCacheStat } from "./runtime-cache.js";
|
|
182
224
|
/** Configures the deprecated router memory session store alias. */
|
|
183
|
-
export type {
|
|
225
|
+
export type { MemorySessionStoreOptions } from "./session.js";
|
|
184
226
|
export type {
|
|
185
227
|
AppRouterCspInlineNonceWarningLogEvent,
|
|
186
228
|
AppRouterLogError,
|
|
@@ -211,23 +253,24 @@ export type {
|
|
|
211
253
|
*
|
|
212
254
|
* @deprecated Import session helpers and types from `@reckona/mreact-auth` instead.
|
|
213
255
|
*/
|
|
214
|
-
export type SessionCookieOptions
|
|
256
|
+
export type { SessionCookieOptions } from "./session.js";
|
|
215
257
|
/**
|
|
216
258
|
* Stores session data through the deprecated router session record alias.
|
|
217
259
|
*
|
|
218
260
|
* @deprecated Import session helpers and types from `@reckona/mreact-auth` instead.
|
|
219
261
|
*/
|
|
220
|
-
export type SessionRecord
|
|
262
|
+
export type { SessionRecord } from "./session.js";
|
|
221
263
|
/**
|
|
222
264
|
* Defines session persistence through the deprecated router session store alias.
|
|
223
265
|
*
|
|
224
266
|
* @deprecated Import session helpers and types from `@reckona/mreact-auth` instead.
|
|
225
267
|
*/
|
|
226
|
-
export type SessionStore
|
|
268
|
+
export type { SessionStore } from "./session.js";
|
|
227
269
|
export { startDevServer } from "./dev-server.js";
|
|
228
270
|
export type { StartDevServerOptions } from "./dev-server.js";
|
|
229
271
|
export { renderAppRequest } from "./render.js";
|
|
230
272
|
export type {
|
|
273
|
+
AppRouterRenderPreload,
|
|
231
274
|
AppRouterResponseHook,
|
|
232
275
|
AppRouterResponseHookContext,
|
|
233
276
|
RenderAppRequestOptions,
|
|
@@ -250,6 +293,7 @@ export type {
|
|
|
250
293
|
FileSystemPrerenderStoreOptions,
|
|
251
294
|
KeyValuePrerenderStoreAdapter,
|
|
252
295
|
KeyValuePrerenderStoreOptions,
|
|
296
|
+
MemoryPrerenderStoreEntry,
|
|
253
297
|
MemoryPrerenderStoreOptions,
|
|
254
298
|
} from "./prerender-store.js";
|
|
255
299
|
export { createFileSystemPrerenderStore, createKeyValuePrerenderStore } from "./prerender-store.js";
|
|
@@ -260,9 +304,12 @@ export type {
|
|
|
260
304
|
BuiltAppRuntimePreloadStrategy,
|
|
261
305
|
RenderBuiltAppRequestOptions,
|
|
262
306
|
RequestHostPolicy,
|
|
307
|
+
ResponseSinkStrategy,
|
|
263
308
|
StartServerOptions,
|
|
264
309
|
} from "./serve.js";
|
|
265
310
|
export { matchRoute, scanAppRoutes } from "./routes.js";
|
|
311
|
+
export type { CachedClientRouteSource, ClientRouteInferenceCache } from "./client.js";
|
|
312
|
+
export type { RouteMatcher, ScanAppRoutesOptions } from "./routes.js";
|
|
266
313
|
export type { AppFileConvention } from "./file-conventions.js";
|
|
267
314
|
export type {
|
|
268
315
|
AppAssetRoute,
|
package/src/link.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type {
|
|
|
13
13
|
} from "./typed-routes.js";
|
|
14
14
|
|
|
15
15
|
const TRUSTED_LINK_HTML = Symbol.for("modular.react.router.trusted_link_html");
|
|
16
|
+
const VALID_LINK_ATTRIBUTE_NAME = /^[A-Za-z_][\w.\-:]*$/;
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Selects when the app router should prefetch a linked route.
|
|
@@ -70,6 +71,37 @@ export interface LinkProps<Href extends string = LinkHref> extends LinkOptions<H
|
|
|
70
71
|
[attribute: string]: unknown;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/** Represents attributes that can be serialized when Link writes to an HtmlSink. */
|
|
75
|
+
export type LinkSerializableAttribute = boolean | number | string | null | undefined;
|
|
76
|
+
|
|
77
|
+
/** Represents child values that an HtmlSink Link can serialize without a browser DOM. */
|
|
78
|
+
export type LinkSinkChild =
|
|
79
|
+
| boolean
|
|
80
|
+
| number
|
|
81
|
+
| string
|
|
82
|
+
| null
|
|
83
|
+
| undefined
|
|
84
|
+
| TrustedLinkHtml
|
|
85
|
+
| readonly LinkSinkChild[];
|
|
86
|
+
|
|
87
|
+
/** Configures an HtmlSink Link without browser-only event handlers or refs. */
|
|
88
|
+
export interface LinkSinkProps extends LinkOptions<string> {
|
|
89
|
+
children?: LinkSinkChild;
|
|
90
|
+
class?: string | undefined;
|
|
91
|
+
className?: string | undefined;
|
|
92
|
+
download?: string | boolean | undefined;
|
|
93
|
+
id?: string | undefined;
|
|
94
|
+
name?: string | undefined;
|
|
95
|
+
rel?: string | undefined;
|
|
96
|
+
role?: string | undefined;
|
|
97
|
+
style?: Readonly<Record<string, boolean | number | string | null | undefined>> | undefined;
|
|
98
|
+
tabIndex?: number | undefined;
|
|
99
|
+
target?: string | undefined;
|
|
100
|
+
title?: string | undefined;
|
|
101
|
+
[ariaAttribute: `aria-${string}`]: LinkSerializableAttribute;
|
|
102
|
+
[dataAttribute: `data-${string}`]: LinkSerializableAttribute;
|
|
103
|
+
}
|
|
104
|
+
|
|
73
105
|
/**
|
|
74
106
|
* Converts router link options into anchor attributes consumed by the client navigation runtime.
|
|
75
107
|
*
|
|
@@ -85,7 +117,9 @@ export function linkProps(options: LinkOptions<string>): Record<string, string>
|
|
|
85
117
|
...(options.scroll === undefined || options.scroll === "top"
|
|
86
118
|
? {}
|
|
87
119
|
: { "data-mreact-scroll": options.scroll }),
|
|
88
|
-
...(options.transition === undefined ||
|
|
120
|
+
...(options.transition === undefined ||
|
|
121
|
+
options.transition === false ||
|
|
122
|
+
options.transition === "none"
|
|
89
123
|
? {}
|
|
90
124
|
: { "data-mreact-transition": options.transition }),
|
|
91
125
|
};
|
|
@@ -108,19 +142,51 @@ export type ConcreteLinkHrefGuard<Href extends string> = [RegisteredAppRoutePath
|
|
|
108
142
|
export function Link<const Href extends LinkHref>(
|
|
109
143
|
props: LinkProps<Href> & ConcreteLinkHrefGuard<Href>,
|
|
110
144
|
): ReactCompatElement;
|
|
111
|
-
export function Link(sink: HtmlSink, props:
|
|
145
|
+
export function Link(sink: HtmlSink, props: LinkSinkProps): void;
|
|
112
146
|
export function Link(
|
|
113
147
|
sinkOrProps: HtmlSink | LinkProps<string>,
|
|
114
|
-
maybeProps?: LinkProps<string
|
|
148
|
+
maybeProps?: LinkProps<string> | LinkSinkProps,
|
|
115
149
|
): ReactCompatElement | string | HTMLAnchorElement | void {
|
|
116
150
|
if (maybeProps !== undefined) {
|
|
117
|
-
(
|
|
151
|
+
reportUnsupportedSinkProps(maybeProps);
|
|
152
|
+
(sinkOrProps as HtmlSink).append(renderLinkString(maybeProps as LinkSinkProps));
|
|
118
153
|
return;
|
|
119
154
|
}
|
|
120
155
|
|
|
121
156
|
return renderLink(sinkOrProps as LinkProps);
|
|
122
157
|
}
|
|
123
158
|
|
|
159
|
+
function reportUnsupportedSinkProps(props: LinkProps<string> | LinkSinkProps): void {
|
|
160
|
+
const nodeEnv = (
|
|
161
|
+
globalThis as typeof globalThis & {
|
|
162
|
+
process?: { env?: { NODE_ENV?: string | undefined } | undefined } | undefined;
|
|
163
|
+
}
|
|
164
|
+
).process?.env?.NODE_ENV;
|
|
165
|
+
|
|
166
|
+
if (nodeEnv === "production") {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const unsupported = Object.entries(props)
|
|
171
|
+
.filter(
|
|
172
|
+
([name, value]) =>
|
|
173
|
+
name === "ref" ||
|
|
174
|
+
typeof value === "function" ||
|
|
175
|
+
typeof value === "symbol" ||
|
|
176
|
+
isLinkEventAttributeName(name) ||
|
|
177
|
+
(name === "children" && !isSerializableSinkChild(value)) ||
|
|
178
|
+
(name !== "children" && name !== "style" && typeof value === "object" && value !== null) ||
|
|
179
|
+
(name !== "children" && !isSafeLinkAttributeName(attributeName(name))),
|
|
180
|
+
)
|
|
181
|
+
.map(([name]) => name);
|
|
182
|
+
|
|
183
|
+
if (unsupported.length > 0) {
|
|
184
|
+
console.warn(
|
|
185
|
+
`[MR_LINK_SINK_UNSUPPORTED_PROP] HtmlSink Link cannot serialize browser-only props: ${unsupported.join(", ")}. Render Link through JSX for event handlers and refs.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
124
190
|
(Link as typeof Link & { trustedHtml(html: string): TrustedLinkHtml }).trustedHtml = (
|
|
125
191
|
html: string,
|
|
126
192
|
): TrustedLinkHtml => ({
|
|
@@ -141,13 +207,14 @@ function renderLink(props: LinkProps<string>): string | HTMLAnchorElement {
|
|
|
141
207
|
return renderAnchorString(propsWithLinkAttrs);
|
|
142
208
|
}
|
|
143
209
|
|
|
144
|
-
function renderLinkString(props:
|
|
210
|
+
function renderLinkString(props: LinkSinkProps): string {
|
|
145
211
|
const { href, prefetch, reload, scroll, transition, ...rest } = props;
|
|
146
|
-
|
|
147
|
-
return renderAnchorString({
|
|
212
|
+
const propsWithLinkAttrs = {
|
|
148
213
|
...rest,
|
|
149
214
|
...linkProps({ href, prefetch, reload, scroll, transition }),
|
|
150
|
-
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return `<a${renderAnchorAttributes(propsWithLinkAttrs)}>${renderSinkChildren(props.children)}</a>`;
|
|
151
218
|
}
|
|
152
219
|
|
|
153
220
|
function createAnchorElement(props: Record<string, unknown>): HTMLAnchorElement {
|
|
@@ -159,18 +226,53 @@ function createAnchorElement(props: Record<string, unknown>): HTMLAnchorElement
|
|
|
159
226
|
continue;
|
|
160
227
|
}
|
|
161
228
|
|
|
229
|
+
if (name === "ref") {
|
|
230
|
+
if (typeof value === "function") {
|
|
231
|
+
(value as (element: HTMLAnchorElement) => void)(anchor);
|
|
232
|
+
} else if (typeof value === "object" && value !== null && "current" in value) {
|
|
233
|
+
(value as { current: HTMLAnchorElement | null }).current = anchor;
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (/^on[A-Z]/.test(name) && typeof value === "function") {
|
|
239
|
+
const capture = name.endsWith("Capture");
|
|
240
|
+
const eventName = name.slice(2, capture ? -"Capture".length : undefined).toLowerCase();
|
|
241
|
+
anchor.addEventListener(eventName, value as EventListener, capture);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (isLinkEventAttributeName(name)) {
|
|
246
|
+
reportRejectedBrowserEventProp(name);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (name === "style" && typeof value === "object" && value !== null) {
|
|
251
|
+
for (const [property, styleValue] of Object.entries(value as Record<string, unknown>)) {
|
|
252
|
+
const cssProperty = linkStylePropertyName(property);
|
|
253
|
+
const cssValue = linkStyleValue(styleValue);
|
|
254
|
+
if (cssProperty !== undefined && cssValue !== undefined) {
|
|
255
|
+
anchor.style.setProperty(cssProperty, cssValue);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
162
261
|
if (!shouldSetAttribute(name, value)) {
|
|
163
262
|
continue;
|
|
164
263
|
}
|
|
165
264
|
|
|
166
265
|
const attrName = attributeName(name);
|
|
266
|
+
if (!isSafeLinkAttributeName(attrName)) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
167
269
|
const safeValue = safeUrlAttributeValue(attrName, String(value));
|
|
168
270
|
|
|
169
271
|
if (safeValue === undefined) {
|
|
170
272
|
continue;
|
|
171
273
|
}
|
|
172
274
|
|
|
173
|
-
anchor.setAttribute(attrName, safeValue);
|
|
275
|
+
anchor.setAttribute(attrName, value === true ? "" : safeValue);
|
|
174
276
|
}
|
|
175
277
|
|
|
176
278
|
return anchor;
|
|
@@ -204,11 +306,33 @@ function renderAnchorAttributes(props: Record<string, unknown>): string {
|
|
|
204
306
|
const attrs: string[] = [];
|
|
205
307
|
|
|
206
308
|
for (const [name, value] of Object.entries(props)) {
|
|
207
|
-
if (name === "children" || !shouldSetAttribute(name, value)) {
|
|
309
|
+
if (name === "children" || isLinkEventAttributeName(name) || !shouldSetAttribute(name, value)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (name !== "style" && typeof value === "object" && value !== null) {
|
|
208
314
|
continue;
|
|
209
315
|
}
|
|
210
316
|
|
|
211
317
|
const attrName = attributeName(name);
|
|
318
|
+
if (!isSafeLinkAttributeName(attrName)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (name === "style" && typeof value === "object" && value !== null) {
|
|
322
|
+
const style = Object.entries(value as Record<string, unknown>)
|
|
323
|
+
.flatMap(([property, styleValue]) => {
|
|
324
|
+
const cssProperty = linkStylePropertyName(property);
|
|
325
|
+
const cssValue = linkStyleValue(styleValue);
|
|
326
|
+
return cssProperty === undefined || cssValue === undefined
|
|
327
|
+
? []
|
|
328
|
+
: [`${cssProperty}:${cssValue}`];
|
|
329
|
+
})
|
|
330
|
+
.join(";");
|
|
331
|
+
if (style !== "") {
|
|
332
|
+
attrs.push(`${escapeHtmlAttribute(attrName)}="${escapeHtmlAttribute(style)}"`);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
212
336
|
const attrValue = String(value);
|
|
213
337
|
const safeValue = safeUrlAttributeValue(attrName, attrValue);
|
|
214
338
|
|
|
@@ -238,6 +362,42 @@ function attributeName(name: string): string {
|
|
|
238
362
|
return name === "className" ? "class" : name;
|
|
239
363
|
}
|
|
240
364
|
|
|
365
|
+
function isSafeLinkAttributeName(name: string): boolean {
|
|
366
|
+
return VALID_LINK_ATTRIBUTE_NAME.test(name);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function isLinkEventAttributeName(name: string): boolean {
|
|
370
|
+
return /^on/i.test(name);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function reportRejectedBrowserEventProp(name: string): void {
|
|
374
|
+
const nodeEnv = (
|
|
375
|
+
globalThis as typeof globalThis & {
|
|
376
|
+
process?: { env?: { NODE_ENV?: string | undefined } | undefined } | undefined;
|
|
377
|
+
}
|
|
378
|
+
).process?.env?.NODE_ENV;
|
|
379
|
+
|
|
380
|
+
if (nodeEnv !== "production") {
|
|
381
|
+
console.warn(
|
|
382
|
+
`[MR_LINK_UNSAFE_EVENT_PROP] Link rejected event-like prop ${JSON.stringify(name)}. Pass a supported camel-cased function handler such as onClick instead.`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function linkStylePropertyName(property: string): string | undefined {
|
|
388
|
+
const cssProperty = property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
389
|
+
return /^(?:--[a-zA-Z0-9_-]+|[a-zA-Z][a-zA-Z0-9-]*)$/.test(cssProperty) ? cssProperty : undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function linkStyleValue(value: unknown): string | undefined {
|
|
393
|
+
if (value === null || value === undefined || value === false) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const cssValue = String(value);
|
|
398
|
+
return /[;{}]/.test(cssValue) ? undefined : cssValue;
|
|
399
|
+
}
|
|
400
|
+
|
|
241
401
|
function renderChildren(child: LinkChild): string {
|
|
242
402
|
if (child === null || child === undefined || typeof child === "boolean") {
|
|
243
403
|
return "";
|
|
@@ -254,6 +414,41 @@ function renderChildren(child: LinkChild): string {
|
|
|
254
414
|
return escapeHtmlText(child);
|
|
255
415
|
}
|
|
256
416
|
|
|
417
|
+
function renderSinkChildren(child: LinkSinkChild): string {
|
|
418
|
+
if (child === null || child === undefined || typeof child === "boolean") {
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (Array.isArray(child)) {
|
|
423
|
+
return child.map(renderSinkChildren).join("");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (isTrustedLinkHtml(child)) {
|
|
427
|
+
return child[TRUSTED_LINK_HTML];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (typeof child === "object") {
|
|
431
|
+
return "";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return escapeHtmlText(String(child));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isSerializableSinkChild(value: unknown): value is LinkSinkChild {
|
|
438
|
+
if (
|
|
439
|
+
value === null ||
|
|
440
|
+
value === undefined ||
|
|
441
|
+
typeof value === "boolean" ||
|
|
442
|
+
typeof value === "number" ||
|
|
443
|
+
typeof value === "string" ||
|
|
444
|
+
isTrustedLinkHtml(value)
|
|
445
|
+
) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return Array.isArray(value) && value.every(isSerializableSinkChild);
|
|
450
|
+
}
|
|
451
|
+
|
|
257
452
|
function isTrustedLinkHtml(value: unknown): value is TrustedLinkHtml {
|
|
258
453
|
return (
|
|
259
454
|
typeof value === "object" &&
|
package/src/render.ts
CHANGED
|
@@ -53,7 +53,8 @@ import {
|
|
|
53
53
|
import { serverActionCookie } from "./csrf.js";
|
|
54
54
|
import {
|
|
55
55
|
type AppRouterCache,
|
|
56
|
-
|
|
56
|
+
activeRouteCacheContext,
|
|
57
|
+
withRouteCacheContext,
|
|
57
58
|
cachedRouteResponse,
|
|
58
59
|
cacheRouteResponse,
|
|
59
60
|
routeCacheKey,
|
|
@@ -981,8 +982,7 @@ async function renderAppRequestInternal(
|
|
|
981
982
|
script: string | undefined;
|
|
982
983
|
}
|
|
983
984
|
| undefined;
|
|
984
|
-
|
|
985
|
-
|
|
985
|
+
return (await withRouteCacheContext(options.routeCache, async () => {
|
|
986
986
|
try {
|
|
987
987
|
if (matched.route.kind === "asset") {
|
|
988
988
|
return await dispatchConventionAssetRoute({
|
|
@@ -1031,7 +1031,6 @@ async function renderAppRequestInternal(
|
|
|
1031
1031
|
return methodResponse;
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
|
-
routeCacheContext = beginRouteCacheContext(options.routeCache);
|
|
1035
1034
|
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
1036
1035
|
const clientStyleSheets = options.clientStyles?.get(matched.route.path);
|
|
1037
1036
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
@@ -1607,7 +1606,7 @@ async function renderAppRequestInternal(
|
|
|
1607
1606
|
preparedActions.csrfTokenIsNew === true,
|
|
1608
1607
|
);
|
|
1609
1608
|
|
|
1610
|
-
const effectiveCachePolicy = cachePolicy ??
|
|
1609
|
+
const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
|
|
1611
1610
|
|
|
1612
1611
|
const finalResponse = preparedActions.hasFormActions
|
|
1613
1612
|
? withRouteCacheHeader(response, effectiveCachePolicy)
|
|
@@ -1686,9 +1685,8 @@ async function renderAppRequestInternal(
|
|
|
1686
1685
|
});
|
|
1687
1686
|
emitRenderTiming(options, timing, response.status);
|
|
1688
1687
|
return response;
|
|
1689
|
-
} finally {
|
|
1690
|
-
await routeCacheContext?.dispose();
|
|
1691
1688
|
}
|
|
1689
|
+
})).value;
|
|
1692
1690
|
}
|
|
1693
1691
|
|
|
1694
1692
|
async function applyAppRouterResponseHook(
|
package/src/request.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Lightweight request/control-plane helpers that do not import router build or Vite modules. */
|
|
2
|
+
export { cacheControl, createMemoryRouteCache, revalidatePath } from "./cache.js";
|
|
3
|
+
export type {
|
|
4
|
+
AppRouterCache,
|
|
5
|
+
AppRouterCacheEntry,
|
|
6
|
+
CacheControlOptions,
|
|
7
|
+
MemoryRouteCacheOptions,
|
|
8
|
+
} from "./cache.js";
|
|
9
|
+
export { deleteCookie, parseCookieHeader, serializeCookie, setCookie } from "./cookies.js";
|
|
10
|
+
export type { CookieOptions } from "./cookies.js";
|
|
11
|
+
export { defer, isDeferredLoaderData } from "./deferred.js";
|
|
12
|
+
export type { DeferredLoaderData } from "./deferred.js";
|
|
13
|
+
export {
|
|
14
|
+
cookies,
|
|
15
|
+
headers,
|
|
16
|
+
html,
|
|
17
|
+
isNotFoundError,
|
|
18
|
+
isRedirectError,
|
|
19
|
+
json,
|
|
20
|
+
next,
|
|
21
|
+
notFound,
|
|
22
|
+
parseForm,
|
|
23
|
+
redirect,
|
|
24
|
+
redirect303,
|
|
25
|
+
redirectExternal,
|
|
26
|
+
rewrite,
|
|
27
|
+
textError,
|
|
28
|
+
throwNotFound,
|
|
29
|
+
} from "./navigation.js";
|
|
30
|
+
export type {
|
|
31
|
+
MiddlewareNext,
|
|
32
|
+
ParseSchema,
|
|
33
|
+
RedirectOptions,
|
|
34
|
+
RequestCookies,
|
|
35
|
+
} from "./navigation.js";
|
package/src/typed-routes.ts
CHANGED
|
@@ -43,21 +43,21 @@ export interface DynamicHrefOptions<Path extends `/${string}`> extends StaticHre
|
|
|
43
43
|
params: RouteParamsFor<Path>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
type HasRouteParams<Path extends `/${string}`> = keyof RouteParamsFor<Path> extends never
|
|
46
|
+
export type HasRouteParams<Path extends `/${string}`> = keyof RouteParamsFor<Path> extends never
|
|
47
47
|
? false
|
|
48
48
|
: true;
|
|
49
49
|
|
|
50
|
-
type ExtractRouteParams<Path extends string> = Path extends `${infer Segment}/${infer Rest}`
|
|
50
|
+
export type ExtractRouteParams<Path extends string> = Path extends `${infer Segment}/${infer Rest}`
|
|
51
51
|
? SegmentRouteParam<Segment> & ExtractRouteParams<Rest>
|
|
52
52
|
: SegmentRouteParam<Path>;
|
|
53
53
|
|
|
54
|
-
type SegmentRouteParam<Segment extends string> = Segment extends `:...${infer Name}`
|
|
54
|
+
export type SegmentRouteParam<Segment extends string> = Segment extends `:...${infer Name}`
|
|
55
55
|
? { [Key in Name]: readonly string[] }
|
|
56
56
|
: Segment extends `:${infer Name}`
|
|
57
57
|
? { [Key in Name]: string }
|
|
58
58
|
: Record<never, never>;
|
|
59
59
|
|
|
60
|
-
type Simplify<T> = { [Key in keyof T]: T[Key] } & {};
|
|
60
|
+
export type Simplify<T> = { [Key in keyof T]: T[Key] } & {};
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Represents the search and hash suffix portion of a typed route href.
|
package/src/vite.ts
CHANGED
|
@@ -54,6 +54,32 @@ import { hasJsxSyntax } from "./source-jsx.js";
|
|
|
54
54
|
import { prependTailwindSourceDirectives } from "./tailwind-source.js";
|
|
55
55
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
56
56
|
|
|
57
|
+
export type { AppRouterAllowedServerAction, AppRouterServerActionOptions } from "./actions.js";
|
|
58
|
+
export type { AppRouterCache, AppRouterCacheEntry } from "./cache.js";
|
|
59
|
+
export type {
|
|
60
|
+
AppRouterBuildTarget,
|
|
61
|
+
AppRouterClientConsoleMethod,
|
|
62
|
+
AppRouterClientSourceMapMode,
|
|
63
|
+
AppRouterClientSourceMapOption,
|
|
64
|
+
AppRouterProductionOptions,
|
|
65
|
+
AppRouterProjectOptions,
|
|
66
|
+
ResolvedAppRouterProject,
|
|
67
|
+
} from "./config.js";
|
|
68
|
+
export type { AppRouterImportPolicy } from "./import-policy.js";
|
|
69
|
+
export type {
|
|
70
|
+
AppRouterCspInlineNonceWarningLogEvent,
|
|
71
|
+
AppRouterLogError,
|
|
72
|
+
AppRouterLogEvent,
|
|
73
|
+
AppRouterLogger,
|
|
74
|
+
AppRouterRuntime,
|
|
75
|
+
AppRouterRenderTimingLogEvent,
|
|
76
|
+
AppRouterRequestEndLogEvent,
|
|
77
|
+
AppRouterRequestErrorLogEvent,
|
|
78
|
+
AppRouterRequestStartLogEvent,
|
|
79
|
+
AppRouterRequestTimingLogEvent,
|
|
80
|
+
} from "./logger.js";
|
|
81
|
+
export type { RequestHostPolicy } from "./serve.js";
|
|
82
|
+
|
|
57
83
|
/**
|
|
58
84
|
* Configures the app-router Vite middleware used by development servers.
|
|
59
85
|
*/
|
|
@@ -316,9 +342,12 @@ export function cell(initial) {
|
|
|
316
342
|
|
|
317
343
|
if (id === virtualReactiveDevtoolsId) {
|
|
318
344
|
return `export function emitReactiveDevtoolsEvent() {}
|
|
345
|
+
export function emitReactiveEffectRunDevtoolsEvent() {}
|
|
319
346
|
export function hasReactiveDevtoolsEmitter() { return false; }
|
|
320
347
|
export function currentDevtoolsEmitter() { return undefined; }
|
|
321
|
-
export function currentReactiveDevtools() { return undefined; }
|
|
348
|
+
export function currentReactiveDevtools() { return undefined; }
|
|
349
|
+
export function invalidateReactiveDevtoolsCache() {}
|
|
350
|
+
export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`;
|
|
322
351
|
}
|
|
323
352
|
|
|
324
353
|
if (id.startsWith(virtualClientPrefix)) {
|