@reckona/mreact-router 0.0.208 → 0.0.210
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 +2 -2
- package/dist/adapters/cloudflare.d.ts +3 -3
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +11 -4
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/boundaries.d.ts +37 -1
- package/dist/boundaries.d.ts.map +1 -1
- package/dist/boundaries.js +125 -4
- package/dist/boundaries.js.map +1 -1
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +265 -24
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +1 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +4 -1
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -11
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/reactive-devtools-stub.d.ts +3 -0
- package/dist/reactive-devtools-stub.d.ts.map +1 -0
- package/dist/reactive-devtools-stub.js +10 -0
- package/dist/reactive-devtools-stub.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +23 -23
- package/dist/render.js.map +1 -1
- package/dist/request-header-tracking.d.ts +19 -0
- package/dist/request-header-tracking.d.ts.map +1 -1
- package/dist/request-header-tracking.js +48 -0
- package/dist/request-header-tracking.js.map +1 -1
- package/dist/tailwind-source.d.ts.map +1 -1
- package/dist/tailwind-source.js +147 -1
- package/dist/tailwind-source.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +2 -7
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/cloudflare.ts +14 -5
- package/src/boundaries.ts +224 -4
- package/src/build.ts +379 -24
- package/src/bundle-pipeline.ts +9 -4
- package/src/client.ts +19 -12
- package/src/config.ts +32 -0
- package/src/index.ts +10 -0
- package/src/reactive-devtools-stub.ts +9 -0
- package/src/render.ts +30 -16
- package/src/request-header-tracking.ts +61 -0
- package/src/tailwind-source.ts +164 -1
- package/src/types.ts +15 -2
- package/src/vite.ts +3 -7
package/src/client.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type CompilerModuleContext,
|
|
27
27
|
} from "@reckona/mreact-compiler/internal";
|
|
28
28
|
import { assetPath } from "./assets.js";
|
|
29
|
+
import { reactiveDevtoolsStubSource } from "./reactive-devtools-stub.js";
|
|
29
30
|
import {
|
|
30
31
|
bundleRouterModule,
|
|
31
32
|
bundleRouterModules,
|
|
@@ -53,6 +54,7 @@ const nodeBuiltinPackages = new Set(builtinModules.flatMap((name) => [name, `nod
|
|
|
53
54
|
export interface ClientRouteManifestEntry {
|
|
54
55
|
bytes?: number;
|
|
55
56
|
css?: readonly string[];
|
|
57
|
+
dynamicImports?: readonly string[];
|
|
56
58
|
path: string;
|
|
57
59
|
kind: AppRoute["kind"];
|
|
58
60
|
client: boolean;
|
|
@@ -842,14 +844,14 @@ function clientRouteComponentOrigin(options: {
|
|
|
842
844
|
return "server-only-import";
|
|
843
845
|
}
|
|
844
846
|
|
|
845
|
-
if (options.analysis.hasUseClientDirective) {
|
|
846
|
-
return "use-client-directive";
|
|
847
|
-
}
|
|
848
|
-
|
|
849
847
|
if (/\.compat(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
850
848
|
return "compat-filename";
|
|
851
849
|
}
|
|
852
850
|
|
|
851
|
+
if (options.analysis.hasUseClientDirective) {
|
|
852
|
+
return "use-client-directive";
|
|
853
|
+
}
|
|
854
|
+
|
|
853
855
|
if (/\.client(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
854
856
|
return "client-filename";
|
|
855
857
|
}
|
|
@@ -3491,7 +3493,18 @@ ${routeOutOfOrderFragmentHydration} const __mreactMarker = document.querySelect
|
|
|
3491
3493
|
const __mreactProps = __mreactPropsText === undefined
|
|
3492
3494
|
? {}
|
|
3493
3495
|
: JSON.parse(__mreactPropsText);
|
|
3494
|
-
${
|
|
3496
|
+
${
|
|
3497
|
+
restoreRequestUrl
|
|
3498
|
+
? ` if (__mreactProps.request) {
|
|
3499
|
+
const __mreactRouteUrl = new URL(document.URL);
|
|
3500
|
+
__mreactProps.request.url = __mreactRouteUrl.href;
|
|
3501
|
+
__mreactProps.request.pathname = __mreactRouteUrl.pathname;
|
|
3502
|
+
__mreactProps.request.search = __mreactRouteUrl.search;
|
|
3503
|
+
__mreactProps.request.hash = __mreactRouteUrl.hash;
|
|
3504
|
+
}
|
|
3505
|
+
`
|
|
3506
|
+
: ""
|
|
3507
|
+
}
|
|
3495
3508
|
const __mreactClientReferences = __mreactClientReferencesElement?.textContent === undefined
|
|
3496
3509
|
? []
|
|
3497
3510
|
: JSON.parse(__mreactClientReferencesElement.textContent);
|
|
@@ -5528,13 +5541,7 @@ export function cell(initial) {
|
|
|
5528
5541
|
resolveDir: reactiveCoreDir,
|
|
5529
5542
|
}));
|
|
5530
5543
|
buildApi.onLoad({ filter: /^devtools$/, namespace: "mreact-devtools-stub" }, () => ({
|
|
5531
|
-
contents:
|
|
5532
|
-
export function emitReactiveEffectRunDevtoolsEvent() {}
|
|
5533
|
-
export function hasReactiveDevtoolsEmitter() { return false; }
|
|
5534
|
-
export function currentDevtoolsEmitter() { return undefined; }
|
|
5535
|
-
export function currentReactiveDevtools() { return undefined; }
|
|
5536
|
-
export function invalidateReactiveDevtoolsCache() {}
|
|
5537
|
-
export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`,
|
|
5544
|
+
contents: reactiveDevtoolsStubSource,
|
|
5538
5545
|
loader: "ts",
|
|
5539
5546
|
}));
|
|
5540
5547
|
if (options.sourceRegionModulePaths !== undefined) {
|
package/src/config.ts
CHANGED
|
@@ -32,6 +32,12 @@ export interface AppRouterProductionOptions {
|
|
|
32
32
|
dropClientConsole?: boolean | readonly AppRouterClientConsoleMethod[] | undefined;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** Opt-in compile-time constraints for route and component execution modes. */
|
|
36
|
+
export interface AppRouterExecutionContracts {
|
|
37
|
+
noCompatComponents?: readonly string[] | undefined;
|
|
38
|
+
serverOnlyRoutes?: readonly string[] | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
export interface AppRouterProjectOptions {
|
|
36
42
|
assetBaseUrl?: string | undefined;
|
|
37
43
|
buildConcurrency?: number | undefined;
|
|
@@ -39,6 +45,7 @@ export interface AppRouterProjectOptions {
|
|
|
39
45
|
clientSourceMaps?: AppRouterClientSourceMapOption | undefined;
|
|
40
46
|
/** Project-local server module exporting a named `dehydrateOptions` policy. */
|
|
41
47
|
dehydratePolicyModule?: string | undefined;
|
|
48
|
+
executionContracts?: AppRouterExecutionContracts | undefined;
|
|
42
49
|
/**
|
|
43
50
|
* Legacy route root. When provided without routesDir/projectRoot, this keeps
|
|
44
51
|
* the historical "appDir is the whole app boundary" behavior.
|
|
@@ -64,6 +71,7 @@ export interface ResolvedAppRouterProject {
|
|
|
64
71
|
clientSourceMaps: AppRouterClientSourceMapMode;
|
|
65
72
|
clientConsolePureFunctions?: readonly string[] | undefined;
|
|
66
73
|
dehydratePolicyModule?: string | undefined;
|
|
74
|
+
executionContracts?: AppRouterExecutionContracts | undefined;
|
|
67
75
|
projectRoot: string;
|
|
68
76
|
publicAssetBaseUrl?: string | undefined;
|
|
69
77
|
publicDir: string;
|
|
@@ -105,6 +113,9 @@ export function resolveAppRouterProjectOptions(
|
|
|
105
113
|
allowedSourceDirs,
|
|
106
114
|
),
|
|
107
115
|
}),
|
|
116
|
+
...(options.executionContracts === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { executionContracts: normalizeExecutionContracts(options.executionContracts) }),
|
|
108
119
|
projectRoot: appDir,
|
|
109
120
|
...(options.publicAssetBaseUrl === undefined
|
|
110
121
|
? {}
|
|
@@ -140,6 +151,9 @@ export function resolveAppRouterProjectOptions(
|
|
|
140
151
|
allowedSourceDirs,
|
|
141
152
|
),
|
|
142
153
|
}),
|
|
154
|
+
...(options.executionContracts === undefined
|
|
155
|
+
? {}
|
|
156
|
+
: { executionContracts: normalizeExecutionContracts(options.executionContracts) }),
|
|
143
157
|
projectRoot,
|
|
144
158
|
...(options.publicAssetBaseUrl === undefined
|
|
145
159
|
? {}
|
|
@@ -149,6 +163,24 @@ export function resolveAppRouterProjectOptions(
|
|
|
149
163
|
};
|
|
150
164
|
}
|
|
151
165
|
|
|
166
|
+
function normalizeExecutionContracts(
|
|
167
|
+
contracts: AppRouterExecutionContracts,
|
|
168
|
+
): AppRouterExecutionContracts {
|
|
169
|
+
const noCompatComponents = uniquePatterns(contracts.noCompatComponents);
|
|
170
|
+
const serverOnlyRoutes = uniquePatterns(contracts.serverOnlyRoutes);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
...(noCompatComponents.length === 0 ? {} : { noCompatComponents }),
|
|
174
|
+
...(serverOnlyRoutes.length === 0 ? {} : { serverOnlyRoutes }),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function uniquePatterns(patterns: readonly string[] | undefined): string[] {
|
|
179
|
+
return [...new Set((patterns ?? []).filter((pattern) => pattern.length > 0))].sort(
|
|
180
|
+
(left, right) => left.localeCompare(right),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
152
184
|
function resolvePolicyModule(
|
|
153
185
|
projectRoot: string,
|
|
154
186
|
modulePath: string,
|
package/src/index.ts
CHANGED
|
@@ -5,13 +5,20 @@ export {
|
|
|
5
5
|
createBoundaryReport,
|
|
6
6
|
formatBoundaryReport,
|
|
7
7
|
formatBoundaryReportJson,
|
|
8
|
+
validateBoundaryExecutionContracts,
|
|
8
9
|
} from "./boundaries.js";
|
|
9
10
|
export type {
|
|
10
11
|
AnalyzeAppBoundariesOptions,
|
|
12
|
+
BoundaryExecutionMode,
|
|
13
|
+
BoundaryReportByteCost,
|
|
11
14
|
BoundaryReport,
|
|
12
15
|
BoundaryReportComponent,
|
|
16
|
+
BoundaryReportCost,
|
|
17
|
+
BoundaryReportDecision,
|
|
13
18
|
BoundaryReportRoute,
|
|
14
19
|
BoundaryReportSummary,
|
|
20
|
+
BoundarySourcePosition,
|
|
21
|
+
BoundarySourceRange,
|
|
15
22
|
CreateBoundaryReportInput,
|
|
16
23
|
CreateBoundaryReportRouteInput,
|
|
17
24
|
} from "./boundaries.js";
|
|
@@ -142,6 +149,7 @@ export async function rotateSession<TData>(
|
|
|
142
149
|
export type {
|
|
143
150
|
AwsLambdaArtifactManifest,
|
|
144
151
|
AwsLambdaGeneratedHandlerPreloadMode,
|
|
152
|
+
BoundaryCostOptions,
|
|
145
153
|
BuildAppPhase,
|
|
146
154
|
BuildAppPhaseTiming,
|
|
147
155
|
BuildAppOptions,
|
|
@@ -195,6 +203,7 @@ export type {
|
|
|
195
203
|
RobotsRule,
|
|
196
204
|
RouteHeadDescriptor,
|
|
197
205
|
RouteHandlerContext,
|
|
206
|
+
RouteLocation,
|
|
198
207
|
RouteLoader,
|
|
199
208
|
RouteMetadata,
|
|
200
209
|
RouteParams,
|
|
@@ -208,6 +217,7 @@ export type {
|
|
|
208
217
|
AppRouterClientConsoleMethod,
|
|
209
218
|
AppRouterClientSourceMapMode,
|
|
210
219
|
AppRouterClientSourceMapOption,
|
|
220
|
+
AppRouterExecutionContracts,
|
|
211
221
|
AppRouterProductionOptions,
|
|
212
222
|
} from "./config.js";
|
|
213
223
|
export type {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Shared by the Vite dev server and production client bundler. */
|
|
2
|
+
export const reactiveDevtoolsStubSource = `export function emitReactiveDevtoolsEvent() {}
|
|
3
|
+
export function emitReactiveEffectRunDevtoolsEvent() {}
|
|
4
|
+
export function hasReactiveDevtoolsEmitter() { return false; }
|
|
5
|
+
export function currentDevtoolsEmitter() { return undefined; }
|
|
6
|
+
export function currentReactiveDevtools() { return undefined; }
|
|
7
|
+
export function registerReactiveDevtoolsResource() { return { dispose() {}, update() {} }; }
|
|
8
|
+
export function invalidateReactiveDevtoolsCache() {}
|
|
9
|
+
export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`;
|
package/src/render.ts
CHANGED
|
@@ -64,7 +64,11 @@ import {
|
|
|
64
64
|
} from "./cache.js";
|
|
65
65
|
import {
|
|
66
66
|
trackRequestHeaderReads,
|
|
67
|
+
routeLocationFromRequest,
|
|
68
|
+
routeLocationForHydration,
|
|
69
|
+
routeLocationFromUrl,
|
|
67
70
|
type TrackedHeaderRequest,
|
|
71
|
+
withTrackedRouteLocation,
|
|
68
72
|
withTrackedRequest,
|
|
69
73
|
} from "./request-header-tracking.js";
|
|
70
74
|
import { configuredHstsHeader, hasInvalidConfiguredHsts } from "./security-headers.js";
|
|
@@ -108,6 +112,7 @@ import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key
|
|
|
108
112
|
import type {
|
|
109
113
|
ManifestDescriptor,
|
|
110
114
|
RobotsManifest,
|
|
115
|
+
RouteLocation,
|
|
111
116
|
RouteParams,
|
|
112
117
|
RouteMetadata,
|
|
113
118
|
SitemapEntry,
|
|
@@ -543,7 +548,7 @@ interface ServerComponentProps {
|
|
|
543
548
|
data: unknown;
|
|
544
549
|
params: RouteParams;
|
|
545
550
|
queryClient: QueryClient;
|
|
546
|
-
request:
|
|
551
|
+
request: RouteLocation;
|
|
547
552
|
}
|
|
548
553
|
|
|
549
554
|
type ServerComponent = (props: ServerComponentProps) => string | PromiseLike<string>;
|
|
@@ -1185,7 +1190,7 @@ async function renderAppRequestInternal(
|
|
|
1185
1190
|
clientRoute,
|
|
1186
1191
|
props: {
|
|
1187
1192
|
params: matched.params,
|
|
1188
|
-
request:
|
|
1193
|
+
request: routeLocationForHydration(url),
|
|
1189
1194
|
},
|
|
1190
1195
|
routePath: matched.route.path,
|
|
1191
1196
|
script: clientScript,
|
|
@@ -1252,7 +1257,7 @@ async function renderAppRequestInternal(
|
|
|
1252
1257
|
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1253
1258
|
runServerModuleWithSlots(
|
|
1254
1259
|
stringOutput.code,
|
|
1255
|
-
|
|
1260
|
+
withTrackedRouteLocation(
|
|
1256
1261
|
{
|
|
1257
1262
|
data,
|
|
1258
1263
|
params: matched.params,
|
|
@@ -1260,6 +1265,7 @@ async function renderAppRequestInternal(
|
|
|
1260
1265
|
},
|
|
1261
1266
|
appRequest,
|
|
1262
1267
|
trackedRequest,
|
|
1268
|
+
routeLocationFromUrl(url),
|
|
1263
1269
|
),
|
|
1264
1270
|
matched.route.file,
|
|
1265
1271
|
options.serverModules,
|
|
@@ -1281,7 +1287,7 @@ async function renderAppRequestInternal(
|
|
|
1281
1287
|
script: clientScript,
|
|
1282
1288
|
props: {
|
|
1283
1289
|
params: matched.params,
|
|
1284
|
-
request:
|
|
1290
|
+
request: routeLocationForHydration(url),
|
|
1285
1291
|
data,
|
|
1286
1292
|
},
|
|
1287
1293
|
})
|
|
@@ -1296,7 +1302,7 @@ async function renderAppRequestInternal(
|
|
|
1296
1302
|
appDir: options.appDir,
|
|
1297
1303
|
pageFile: matched.route.file,
|
|
1298
1304
|
html: pageHtmlForLayout,
|
|
1299
|
-
props:
|
|
1305
|
+
props: withTrackedRouteLocation(
|
|
1300
1306
|
{
|
|
1301
1307
|
data,
|
|
1302
1308
|
params: matched.params,
|
|
@@ -1304,6 +1310,7 @@ async function renderAppRequestInternal(
|
|
|
1304
1310
|
},
|
|
1305
1311
|
appRequest,
|
|
1306
1312
|
trackedRequest,
|
|
1313
|
+
routeLocationFromUrl(url),
|
|
1307
1314
|
),
|
|
1308
1315
|
slots: renderedPage.slots,
|
|
1309
1316
|
serverModules: options.serverModules,
|
|
@@ -1437,7 +1444,7 @@ async function renderAppRequestInternal(
|
|
|
1437
1444
|
params: matched.params,
|
|
1438
1445
|
queryClient,
|
|
1439
1446
|
request: appRequest,
|
|
1440
|
-
requestUrl: url.
|
|
1447
|
+
requestUrl: url.href,
|
|
1441
1448
|
trackedRequest,
|
|
1442
1449
|
routePath: matched.route.path,
|
|
1443
1450
|
routeScripts: options.clientScripts,
|
|
@@ -1470,7 +1477,7 @@ async function renderAppRequestInternal(
|
|
|
1470
1477
|
}
|
|
1471
1478
|
|
|
1472
1479
|
const data = streamData;
|
|
1473
|
-
const props =
|
|
1480
|
+
const props = withTrackedRouteLocation(
|
|
1474
1481
|
{
|
|
1475
1482
|
data,
|
|
1476
1483
|
params: matched.params,
|
|
@@ -1478,6 +1485,7 @@ async function renderAppRequestInternal(
|
|
|
1478
1485
|
},
|
|
1479
1486
|
appRequest,
|
|
1480
1487
|
trackedRequest,
|
|
1488
|
+
routeLocationFromUrl(url),
|
|
1481
1489
|
);
|
|
1482
1490
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1483
1491
|
const stream = runServerStreamModule(output.code, {
|
|
@@ -1487,7 +1495,7 @@ async function renderAppRequestInternal(
|
|
|
1487
1495
|
markerRequest: options.request,
|
|
1488
1496
|
prerenderVariantCapture: options.prerenderVariantCapture,
|
|
1489
1497
|
props,
|
|
1490
|
-
requestUrl: url.
|
|
1498
|
+
requestUrl: url.href,
|
|
1491
1499
|
routePath: matched.route.path,
|
|
1492
1500
|
routeScripts: options.clientScripts,
|
|
1493
1501
|
serverModules: options.serverModules,
|
|
@@ -1557,7 +1565,7 @@ async function renderAppRequestInternal(
|
|
|
1557
1565
|
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1558
1566
|
runServerModuleWithSlots(
|
|
1559
1567
|
output.code,
|
|
1560
|
-
|
|
1568
|
+
withTrackedRouteLocation(
|
|
1561
1569
|
{
|
|
1562
1570
|
data,
|
|
1563
1571
|
params: matched.params,
|
|
@@ -1565,6 +1573,7 @@ async function renderAppRequestInternal(
|
|
|
1565
1573
|
},
|
|
1566
1574
|
appRequest,
|
|
1567
1575
|
trackedRequest,
|
|
1576
|
+
routeLocationFromUrl(url),
|
|
1568
1577
|
),
|
|
1569
1578
|
matched.route.file,
|
|
1570
1579
|
options.serverModules,
|
|
@@ -1592,7 +1601,7 @@ async function renderAppRequestInternal(
|
|
|
1592
1601
|
script: clientScript,
|
|
1593
1602
|
props: {
|
|
1594
1603
|
params: matched.params,
|
|
1595
|
-
request:
|
|
1604
|
+
request: routeLocationForHydration(url),
|
|
1596
1605
|
data,
|
|
1597
1606
|
},
|
|
1598
1607
|
})
|
|
@@ -1608,7 +1617,7 @@ async function renderAppRequestInternal(
|
|
|
1608
1617
|
appDir: options.appDir,
|
|
1609
1618
|
pageFile: matched.route.file,
|
|
1610
1619
|
html: pageHtmlForLayout,
|
|
1611
|
-
props:
|
|
1620
|
+
props: withTrackedRouteLocation(
|
|
1612
1621
|
{
|
|
1613
1622
|
data,
|
|
1614
1623
|
params: matched.params,
|
|
@@ -1616,6 +1625,7 @@ async function renderAppRequestInternal(
|
|
|
1616
1625
|
},
|
|
1617
1626
|
appRequest,
|
|
1618
1627
|
trackedRequest,
|
|
1628
|
+
routeLocationFromUrl(url),
|
|
1619
1629
|
),
|
|
1620
1630
|
slots: renderedPage.slots,
|
|
1621
1631
|
serverModules: options.serverModules,
|
|
@@ -2300,7 +2310,7 @@ function errorBoundaryProps(error: unknown, request: Request, routePath: string
|
|
|
2300
2310
|
error: normalizeErrorForProps(error),
|
|
2301
2311
|
params: {},
|
|
2302
2312
|
queryClient: createQueryClient(),
|
|
2303
|
-
request,
|
|
2313
|
+
request: routeLocationFromRequest(request),
|
|
2304
2314
|
requestId: requestIdForErrorContext(request),
|
|
2305
2315
|
routeId: routeIdForPath(routePath ?? new URL(request.url).pathname),
|
|
2306
2316
|
traceId: traceContextFromRequest(request)?.traceId,
|
|
@@ -3550,7 +3560,9 @@ function runServerStreamModule(
|
|
|
3550
3560
|
script: options.script,
|
|
3551
3561
|
props: {
|
|
3552
3562
|
params: options.props.params,
|
|
3553
|
-
request:
|
|
3563
|
+
request: routeLocationForHydration(
|
|
3564
|
+
new URL(options.requestUrl, options.markerRequest.url),
|
|
3565
|
+
),
|
|
3554
3566
|
data: options.props.data,
|
|
3555
3567
|
},
|
|
3556
3568
|
})
|
|
@@ -3797,7 +3809,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3797
3809
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
3798
3810
|
},
|
|
3799
3811
|
): Promise<ReadableStream<Uint8Array> | Response> {
|
|
3800
|
-
const loadingProps =
|
|
3812
|
+
const loadingProps = withTrackedRouteLocation(
|
|
3801
3813
|
{
|
|
3802
3814
|
data: undefined,
|
|
3803
3815
|
params: options.params,
|
|
@@ -3805,6 +3817,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3805
3817
|
},
|
|
3806
3818
|
options.request,
|
|
3807
3819
|
options.trackedRequest,
|
|
3820
|
+
routeLocationFromUrl(new URL(options.requestUrl)),
|
|
3808
3821
|
);
|
|
3809
3822
|
const layoutShells = await layoutShellsForPage(
|
|
3810
3823
|
options.appDir,
|
|
@@ -3836,7 +3849,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3836
3849
|
script: options.script,
|
|
3837
3850
|
props: {
|
|
3838
3851
|
params: options.params,
|
|
3839
|
-
request:
|
|
3852
|
+
request: routeLocationForHydration(new URL(options.requestUrl)),
|
|
3840
3853
|
},
|
|
3841
3854
|
})
|
|
3842
3855
|
: serverOnlyRouteMarkerParts(
|
|
@@ -3879,7 +3892,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3879
3892
|
await appendServerStreamModule(
|
|
3880
3893
|
code,
|
|
3881
3894
|
boundarySink,
|
|
3882
|
-
|
|
3895
|
+
withTrackedRouteLocation(
|
|
3883
3896
|
{
|
|
3884
3897
|
data,
|
|
3885
3898
|
params: options.params,
|
|
@@ -3887,6 +3900,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3887
3900
|
},
|
|
3888
3901
|
options.request,
|
|
3889
3902
|
options.trackedRequest,
|
|
3903
|
+
routeLocationFromUrl(new URL(options.requestUrl)),
|
|
3890
3904
|
),
|
|
3891
3905
|
options.pageFile,
|
|
3892
3906
|
options.serverModules,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { RouteLocation } from "./types.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Observes request inputs read by application code during a render.
|
|
3
5
|
*
|
|
@@ -159,3 +161,62 @@ export function withTrackedRequest<T extends object>(
|
|
|
159
161
|
|
|
160
162
|
return context;
|
|
161
163
|
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Adds the serializable location view used by shared page and layout props.
|
|
167
|
+
* The full Request remains available to loaders, handlers, and metadata code.
|
|
168
|
+
*/
|
|
169
|
+
export function withTrackedRouteLocation<T extends object>(
|
|
170
|
+
values: T,
|
|
171
|
+
request: Request,
|
|
172
|
+
tracked: TrackedHeaderRequest | undefined,
|
|
173
|
+
location?: RouteLocation,
|
|
174
|
+
): T & { request: RouteLocation } {
|
|
175
|
+
const context = values as T & { request: RouteLocation };
|
|
176
|
+
let currentLocation = location ?? routeLocationFromRequest(request);
|
|
177
|
+
|
|
178
|
+
Object.defineProperty(context, "request", {
|
|
179
|
+
configurable: true,
|
|
180
|
+
enumerable: true,
|
|
181
|
+
get() {
|
|
182
|
+
tracked?.markRequestAccess();
|
|
183
|
+
return currentLocation;
|
|
184
|
+
},
|
|
185
|
+
set(value: RouteLocation) {
|
|
186
|
+
tracked?.markRequestAccess();
|
|
187
|
+
currentLocation = value;
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return context;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Returns the URL fields that are safe to serialize into route HTML. */
|
|
195
|
+
export function routeLocationFromRequest(request: Request): RouteLocation {
|
|
196
|
+
return routeLocationFromUrl(new URL(request.url));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Returns a serializable route location from a URL without request metadata. */
|
|
200
|
+
export function routeLocationFromUrl(url: URL): RouteLocation {
|
|
201
|
+
return {
|
|
202
|
+
hash: url.hash,
|
|
203
|
+
pathname: url.pathname,
|
|
204
|
+
search: url.search,
|
|
205
|
+
url: url.href,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns the route location placeholder embedded in cached HTML. The client
|
|
211
|
+
* hydration runtime replaces it with document.URL before invoking shared code.
|
|
212
|
+
* Query values stay out of reusable HTML because they can contain credentials
|
|
213
|
+
* or other request-specific data.
|
|
214
|
+
*/
|
|
215
|
+
export function routeLocationForHydration(url: URL): RouteLocation {
|
|
216
|
+
return {
|
|
217
|
+
hash: "",
|
|
218
|
+
pathname: url.pathname,
|
|
219
|
+
search: "",
|
|
220
|
+
url: url.pathname,
|
|
221
|
+
};
|
|
222
|
+
}
|
package/src/tailwind-source.ts
CHANGED
|
@@ -14,7 +14,170 @@ export function prependTailwindSourceDirectives(options: {
|
|
|
14
14
|
.map((sourceDir) => `${tailwindSourceDirective(cssDir, sourceDir)}\n`)
|
|
15
15
|
.join("");
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
if (directives.length === 0) {
|
|
18
|
+
return options.code;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const insertionIndex = leadingCssPreludeEnd(options.code);
|
|
22
|
+
const prefix = options.code.slice(0, insertionIndex);
|
|
23
|
+
const suffix = options.code.slice(insertionIndex);
|
|
24
|
+
const lineEnding = suffix.startsWith("\r\n") ? "\r\n" : "\n";
|
|
25
|
+
const existingLineEnding = suffix.startsWith(lineEnding) ? lineEnding : "";
|
|
26
|
+
const separator = insertionIndex === 0 ? "" : existingLineEnding || lineEnding;
|
|
27
|
+
const formattedDirectives = directives.replaceAll("\n", lineEnding);
|
|
28
|
+
return `${prefix}${separator}${formattedDirectives}${suffix.slice(existingLineEnding.length)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function leadingCssPreludeEnd(code: string): number {
|
|
32
|
+
let cursor = 0;
|
|
33
|
+
let lastRuleEnd = 0;
|
|
34
|
+
|
|
35
|
+
for (;;) {
|
|
36
|
+
cursor = skipCssTrivia(code, cursor);
|
|
37
|
+
const ruleEnd = consumeLeadingCssRule(code, cursor);
|
|
38
|
+
if (ruleEnd === undefined) {
|
|
39
|
+
return lastRuleEnd;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
lastRuleEnd = ruleEnd;
|
|
43
|
+
cursor = ruleEnd;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function skipCssTrivia(code: string, start: number): number {
|
|
48
|
+
let cursor = start;
|
|
49
|
+
while (cursor < code.length) {
|
|
50
|
+
if (/\s/u.test(code[cursor] ?? "")) {
|
|
51
|
+
cursor += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (code.startsWith("/*", cursor)) {
|
|
56
|
+
const commentEnd = code.indexOf("*/", cursor + 2);
|
|
57
|
+
if (commentEnd < 0) {
|
|
58
|
+
return code.length;
|
|
59
|
+
}
|
|
60
|
+
cursor = commentEnd + 2;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return cursor;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function consumeLeadingCssRule(code: string, start: number): number | undefined {
|
|
71
|
+
const match = /^@(charset|import|layer)(?=\s|[;{]|\/\*)/iu.exec(code.slice(start));
|
|
72
|
+
const ruleName = match?.[1]?.toLowerCase();
|
|
73
|
+
if (match === null || ruleName === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let quote: '"' | "'" | undefined;
|
|
78
|
+
let parentheses = 0;
|
|
79
|
+
for (let cursor = start + match[0].length; cursor < code.length; cursor += 1) {
|
|
80
|
+
const character = code[cursor];
|
|
81
|
+
if (character === undefined) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (quote !== undefined) {
|
|
86
|
+
if (character === "\\") {
|
|
87
|
+
cursor += 1;
|
|
88
|
+
} else if (character === quote) {
|
|
89
|
+
quote = undefined;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (code.startsWith("/*", cursor)) {
|
|
95
|
+
const commentEnd = code.indexOf("*/", cursor + 2);
|
|
96
|
+
if (commentEnd < 0) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
cursor = commentEnd + 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (character === '"' || character === "'") {
|
|
104
|
+
quote = character;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (character === "(") {
|
|
108
|
+
parentheses += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (character === ")") {
|
|
112
|
+
parentheses = Math.max(0, parentheses - 1);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (character === ";" && parentheses === 0) {
|
|
116
|
+
return cursor + 1;
|
|
117
|
+
}
|
|
118
|
+
if (character === "{" && parentheses === 0) {
|
|
119
|
+
if (ruleName !== "layer") {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const blockEnd = consumeCssBlock(code, cursor);
|
|
124
|
+
if (blockEnd === undefined || !isCssTrivia(code.slice(cursor + 1, blockEnd - 1))) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return blockEnd;
|
|
128
|
+
}
|
|
129
|
+
if (character === "}" && parentheses === 0) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function consumeCssBlock(code: string, start: number): number | undefined {
|
|
138
|
+
let depth = 1;
|
|
139
|
+
let quote: '"' | "'" | undefined;
|
|
140
|
+
for (let cursor = start + 1; cursor < code.length; cursor += 1) {
|
|
141
|
+
const character = code[cursor];
|
|
142
|
+
if (character === undefined) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (quote !== undefined) {
|
|
147
|
+
if (character === "\\") {
|
|
148
|
+
cursor += 1;
|
|
149
|
+
} else if (character === quote) {
|
|
150
|
+
quote = undefined;
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (code.startsWith("/*", cursor)) {
|
|
156
|
+
const commentEnd = code.indexOf("*/", cursor + 2);
|
|
157
|
+
if (commentEnd < 0) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
cursor = commentEnd + 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (character === '"' || character === "'") {
|
|
165
|
+
quote = character;
|
|
166
|
+
} else if (character === "{") {
|
|
167
|
+
depth += 1;
|
|
168
|
+
} else if (character === "}") {
|
|
169
|
+
depth -= 1;
|
|
170
|
+
if (depth === 0) {
|
|
171
|
+
return cursor + 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isCssTrivia(value: string): boolean {
|
|
180
|
+
return skipCssTrivia(value, 0) === value.length;
|
|
18
181
|
}
|
|
19
182
|
|
|
20
183
|
function isTailwindCssEntry(code: string): boolean {
|
package/src/types.ts
CHANGED
|
@@ -62,13 +62,26 @@ export interface RouteHandlerContext<TParams extends RouteParams = RouteParams>
|
|
|
62
62
|
request: Request;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Serializable location data passed to shared page and layout components.
|
|
67
|
+
*
|
|
68
|
+
* Server-only request data such as headers, body, credentials, and abort
|
|
69
|
+
* signals is available through loader and handler contexts instead.
|
|
70
|
+
*/
|
|
71
|
+
export interface RouteLocation {
|
|
72
|
+
readonly hash: string;
|
|
73
|
+
readonly pathname: string;
|
|
74
|
+
readonly search: string;
|
|
75
|
+
readonly url: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* Provides loader data, params, and request context to page components.
|
|
67
80
|
*/
|
|
68
81
|
export interface PageProps<TData = unknown, TParams extends RouteParams = RouteParams> {
|
|
69
82
|
data: TData;
|
|
70
83
|
params: TParams;
|
|
71
|
-
request:
|
|
84
|
+
request: RouteLocation;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
/**
|
|
@@ -95,7 +108,7 @@ export function definePage<TLoader extends RouteLoader>(
|
|
|
95
108
|
export interface LayoutProps<TParams extends RouteParams = RouteParams> {
|
|
96
109
|
children: ReactCompatNode;
|
|
97
110
|
params: TParams;
|
|
98
|
-
request:
|
|
111
|
+
request: RouteLocation;
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
/**
|