@react-router/dev 0.0.0-experimental-8e9d8ef63 → 0.0.0-experimental-8677247c0
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/CHANGELOG.md +86 -0
- package/dist/cli/index.js +29 -6
- package/dist/config.js +1 -1
- package/dist/routes.js +1 -1
- package/dist/static/refresh-utils.cjs +1 -1
- package/dist/vite/cloudflare.js +10 -6
- package/dist/vite.js +333 -126
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# `@react-router/dev`
|
|
2
2
|
|
|
3
|
+
## 7.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Generate a "SPA fallback" HTML file for scenarios where applications are prerendering the `/` route with `ssr:false` ([#12948](https://github.com/remix-run/react-router/pull/12948))
|
|
8
|
+
|
|
9
|
+
- If you specify `ssr:false` without a `prerender` config, this is considered "SPA Mode" and the generated `index.html` file will only render down to the root route and will be able to hydrate for any valid application path
|
|
10
|
+
- If you specify `ssr:false` with a `prerender` config but _do not_ include the `/` path (i.e., `prerender: ['/blog/post']`), then we still generate a "SPA Mode" `index.html` file that can hydrate for any path in the application
|
|
11
|
+
- However, previously if you specified `ssr:false` and included the `/` path in your `prerender` config, we would prerender the `/` route into `index.html` as a non-SPA page
|
|
12
|
+
- The generated HTML would include the root index route which prevented hydration for any other paths
|
|
13
|
+
- With this change, we now generate a "SPA Mode" file in `__spa-fallback.html` that will allow you to hydrate for any non-prerendered paths
|
|
14
|
+
- You can serve this file from your static file server for any paths that would otherwise 404 if you only want to pre-render _some_ routes in your `ssr:false` app and serve the others as a SPA
|
|
15
|
+
- `npx sirv-cli build/client --single __spa-fallback.html`
|
|
16
|
+
|
|
17
|
+
- Allow a `loader` in the root route in SPA mode because it can be called/server-rendered at build time ([#12948](https://github.com/remix-run/react-router/pull/12948))
|
|
18
|
+
|
|
19
|
+
- `Route.HydrateFallbackProps` now also receives `loaderData`
|
|
20
|
+
- This will be defined so long as the `HydrateFallback` is rendering while _children_ routes are loading
|
|
21
|
+
- This will be `undefined` if the `HydrateFallback` is rendering because the route has it's own hydrating `clientLoader`
|
|
22
|
+
- In SPA mode, this will allow you to render loader root data into the SPA `index.html`
|
|
23
|
+
|
|
24
|
+
- New type-safe `href` utility that guarantees links point to actual paths in your app ([#13012](https://github.com/remix-run/react-router/pull/13012))
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { href } from "react-router";
|
|
28
|
+
|
|
29
|
+
export default function Component() {
|
|
30
|
+
const link = href("/blog/:slug", { slug: "my-first-post" });
|
|
31
|
+
return (
|
|
32
|
+
<main>
|
|
33
|
+
<Link to={href("/products/:id", { id: "asdf" })} />
|
|
34
|
+
<NavLink to={href("/:lang?/about", { lang: "en" })} />
|
|
35
|
+
</main>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Handle custom `envDir` in Vite config ([#12969](https://github.com/remix-run/react-router/pull/12969))
|
|
43
|
+
|
|
44
|
+
- Fix typegen for repeated params ([#13012](https://github.com/remix-run/react-router/pull/13012))
|
|
45
|
+
|
|
46
|
+
In React Router, path parameters are keyed by their name.
|
|
47
|
+
So for a path pattern like `/a/:id/b/:id?/c/:id`, the last `:id` will set the value for `id` in `useParams` and the `params` prop.
|
|
48
|
+
For example, `/a/1/b/2/c/3` will result in the value `{ id: 3 }` at runtime.
|
|
49
|
+
|
|
50
|
+
Previously, generated types for params incorrectly modeled repeated params with an array.
|
|
51
|
+
So `/a/1/b/2/c/3` generated a type like `{ id: [1,2,3] }`.
|
|
52
|
+
|
|
53
|
+
To be consistent with runtime behavior, the generated types now correctly model the "last one wins" semantics of path parameters.
|
|
54
|
+
So `/a/1/b/2/c/3` now generates a type like `{ id: 3 }`.
|
|
55
|
+
|
|
56
|
+
- Fix CLI parsing to allow argumentless `npx react-router` usage ([#12925](https://github.com/remix-run/react-router/pull/12925))
|
|
57
|
+
|
|
58
|
+
- Fix `ArgError: unknown or unexpected option: --version` when running `react-router --version` ([#13012](https://github.com/remix-run/react-router/pull/13012))
|
|
59
|
+
|
|
60
|
+
- Skip action-only resource routes when using `prerender:true` ([#13004](https://github.com/remix-run/react-router/pull/13004))
|
|
61
|
+
|
|
62
|
+
- Enhance invalid export detection when using `ssr:false` ([#12948](https://github.com/remix-run/react-router/pull/12948))
|
|
63
|
+
|
|
64
|
+
- `headers`/`action` are prohibited in all routes with `ssr:false` because there will be no runtime server on which to run them
|
|
65
|
+
- `loader` functions are more nuanced and depend on whether a given route is prerendered
|
|
66
|
+
- When using `ssr:false` without a `prerender` config, only the `root` route can have a `loader`
|
|
67
|
+
- This is "SPA mode" which generates a single `index.html` file with the root route `HydrateFallback` so it is capable of hydrating for any path in your application - therefore we can only call a root route `loader` at build time
|
|
68
|
+
- When using `ssr:false` with a `prerender` config, you can export a `loader` from routes matched by one of the `prerender` paths because those routes will be server rendered at build time
|
|
69
|
+
- Exporting a `loader` from a route that is never matched by a `prerender` path will throw a build time error because there will be no runtime server to ever run the loader
|
|
70
|
+
|
|
71
|
+
- Limit prerendered resource route `.data` files to only the target route ([#13004](https://github.com/remix-run/react-router/pull/13004))
|
|
72
|
+
|
|
73
|
+
- Add unstable support for splitting route modules in framework mode via `future.unstable_splitRouteModules` ([#11871](https://github.com/remix-run/react-router/pull/11871))
|
|
74
|
+
|
|
75
|
+
- Fix prerendering of binary files ([#13039](https://github.com/remix-run/react-router/pull/13039))
|
|
76
|
+
|
|
77
|
+
- Add `future.unstable_viteEnvironmentApi` flag to enable experimental Vite Environment API support ([#12936](https://github.com/remix-run/react-router/pull/12936))
|
|
78
|
+
|
|
79
|
+
- Disable Lazy Route Discovery for all `ssr:false` apps and not just "SPA Mode" because there is no runtime server to serve the search-param-configured `__manifest` requests ([#12894](https://github.com/remix-run/react-router/pull/12894))
|
|
80
|
+
|
|
81
|
+
- We previously only disabled this for "SPA Mode" which is `ssr:false` and no `prerender` config but we realized it should apply to all `ssr:false` apps, including those prerendering multiple pages
|
|
82
|
+
- In those `prerender` scenarios we would prerender the `/__manifest` file assuming the static file server would serve it but that makes some unneccesary assumptions about the static file server behaviors
|
|
83
|
+
|
|
84
|
+
- Updated dependencies:
|
|
85
|
+
- `react-router@7.2.0`
|
|
86
|
+
- `@react-router/node@7.2.0`
|
|
87
|
+
- `@react-router/serve@7.2.0`
|
|
88
|
+
|
|
3
89
|
## 7.1.5
|
|
4
90
|
|
|
5
91
|
### Patch Changes
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @react-router/dev v0.0.0-experimental-
|
|
3
|
+
* @react-router/dev v0.0.0-experimental-8677247c0
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) Remix Software Inc.
|
|
6
6
|
*
|
|
@@ -944,7 +944,6 @@ var init_styles = __esm({
|
|
|
944
944
|
path6 = __toESM(require("path"));
|
|
945
945
|
import_react_router = require("react-router");
|
|
946
946
|
init_resolve_file_url();
|
|
947
|
-
init_vite();
|
|
948
947
|
cssFileRegExp = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
949
948
|
cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`);
|
|
950
949
|
}
|
|
@@ -1361,11 +1360,28 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
|
|
|
1361
1360
|
build: {
|
|
1362
1361
|
outDir: getServerBuildDirectory(ctx),
|
|
1363
1362
|
rollupOptions: {
|
|
1364
|
-
input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
|
|
1363
|
+
input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
|
|
1365
1364
|
}
|
|
1366
|
-
}
|
|
1365
|
+
},
|
|
1366
|
+
optimizeDeps: ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteUserConfig.environments?.ssr?.optimizeDeps?.noDiscovery === false ? {
|
|
1367
|
+
entries: [
|
|
1368
|
+
vite2.normalizePath(ctx.entryServerFilePath),
|
|
1369
|
+
...Object.values(ctx.reactRouterConfig.routes).map(
|
|
1370
|
+
(route) => resolveRelativeRouteFilePath(route, ctx.reactRouterConfig)
|
|
1371
|
+
)
|
|
1372
|
+
],
|
|
1373
|
+
include: [
|
|
1374
|
+
"react",
|
|
1375
|
+
"react/jsx-dev-runtime",
|
|
1376
|
+
"react-dom/server",
|
|
1377
|
+
"react-router"
|
|
1378
|
+
]
|
|
1379
|
+
} : void 0
|
|
1367
1380
|
});
|
|
1368
1381
|
}
|
|
1382
|
+
if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
|
|
1383
|
+
environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
|
|
1384
|
+
}
|
|
1369
1385
|
return environmentOptionsResolvers;
|
|
1370
1386
|
}
|
|
1371
1387
|
function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
|
|
@@ -1380,7 +1396,7 @@ function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
|
|
|
1380
1396
|
function isNonNullable(x) {
|
|
1381
1397
|
return x != null;
|
|
1382
1398
|
}
|
|
1383
|
-
var import_node_crypto, path7, url, fse, babel2, import_react_router2, import_es_module_lexer, import_pick3, import_jsesc, import_picocolors4, import_kebabCase, BUILD_CLIENT_ROUTE_QUERY_STRING, SSR_BUNDLE_PREFIX, virtualHmrRuntime, virtualInjectHmrRuntime, virtual, getServerBuildDirectory, getClientBuildDirectory, defaultEntriesDir, defaultEntries, REACT_REFRESH_HEADER;
|
|
1399
|
+
var import_node_crypto, path7, url, fse, babel2, import_react_router2, import_es_module_lexer, import_pick3, import_jsesc, import_picocolors4, import_kebabCase, BUILD_CLIENT_ROUTE_QUERY_STRING, SSR_BUNDLE_PREFIX, CSS_DEV_HELPER_ENVIRONMENT_NAME, virtualHmrRuntime, virtualInjectHmrRuntime, resolveRelativeRouteFilePath, virtual, getServerBuildDirectory, getClientBuildDirectory, defaultEntriesDir, defaultEntries, REACT_REFRESH_HEADER;
|
|
1384
1400
|
var init_plugin = __esm({
|
|
1385
1401
|
"vite/plugin.ts"() {
|
|
1386
1402
|
"use strict";
|
|
@@ -1410,8 +1426,15 @@ var init_plugin = __esm({
|
|
|
1410
1426
|
init_with_props();
|
|
1411
1427
|
BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
|
|
1412
1428
|
SSR_BUNDLE_PREFIX = "ssrBundle_";
|
|
1429
|
+
CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
|
|
1413
1430
|
virtualHmrRuntime = create("hmr-runtime");
|
|
1414
1431
|
virtualInjectHmrRuntime = create("inject-hmr-runtime");
|
|
1432
|
+
resolveRelativeRouteFilePath = (route, reactRouterConfig) => {
|
|
1433
|
+
let vite2 = getVite();
|
|
1434
|
+
let file = route.file;
|
|
1435
|
+
let fullPath = path7.resolve(reactRouterConfig.appDirectory, file);
|
|
1436
|
+
return vite2.normalizePath(fullPath);
|
|
1437
|
+
};
|
|
1415
1438
|
virtual = {
|
|
1416
1439
|
serverBuild: create("server-build"),
|
|
1417
1440
|
serverManifest: create("server-manifest"),
|
|
@@ -2058,7 +2081,7 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
2058
2081
|
return;
|
|
2059
2082
|
}
|
|
2060
2083
|
if (flags.version) {
|
|
2061
|
-
let version = require("
|
|
2084
|
+
let version = require("../../package.json").version;
|
|
2062
2085
|
console.log(version);
|
|
2063
2086
|
return;
|
|
2064
2087
|
}
|
package/dist/config.js
CHANGED
package/dist/routes.js
CHANGED
|
@@ -53,7 +53,7 @@ const enqueueUpdate = debounce(async () => {
|
|
|
53
53
|
needsRevalidation,
|
|
54
54
|
manifest.routes,
|
|
55
55
|
window.__reactRouterRouteModules,
|
|
56
|
-
window.__reactRouterContext.
|
|
56
|
+
window.__reactRouterContext.ssr,
|
|
57
57
|
window.__reactRouterContext.isSpaMode
|
|
58
58
|
);
|
|
59
59
|
__reactRouterDataRouter._internalSetRoutes(routes);
|
package/dist/vite/cloudflare.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @react-router/dev v0.0.0-experimental-
|
|
2
|
+
* @react-router/dev v0.0.0-experimental-8677247c0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -678,11 +678,14 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
|
|
|
678
678
|
}
|
|
679
679
|
},
|
|
680
680
|
configureServer: async (viteDevServer) => {
|
|
681
|
-
let
|
|
682
|
-
let
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
681
|
+
let context;
|
|
682
|
+
let getContext = async () => {
|
|
683
|
+
let { getPlatformProxy } = await importWrangler();
|
|
684
|
+
let { dispose, ...cloudflare } = await getPlatformProxy(
|
|
685
|
+
restOptions
|
|
686
|
+
);
|
|
687
|
+
return { cloudflare };
|
|
688
|
+
};
|
|
686
689
|
return () => {
|
|
687
690
|
if (!viteDevServer.config.server.middlewareMode) {
|
|
688
691
|
viteDevServer.middlewares.use(async (nodeReq, nodeRes, next) => {
|
|
@@ -692,6 +695,7 @@ var cloudflareDevProxyVitePlugin = (options = {}) => {
|
|
|
692
695
|
);
|
|
693
696
|
let handler = (0, import_react_router.createRequestHandler)(build, "development");
|
|
694
697
|
let req = fromNodeRequest(nodeReq, nodeRes);
|
|
698
|
+
context ??= await getContext();
|
|
695
699
|
let loadContext = getLoadContext ? await getLoadContext({ request: req, context }) : context;
|
|
696
700
|
let res = await handler(req, loadContext);
|
|
697
701
|
await toNodeRequest(res, nodeRes);
|
package/dist/vite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @react-router/dev v0.0.0-experimental-
|
|
2
|
+
* @react-router/dev v0.0.0-experimental-8677247c0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -963,15 +963,12 @@ var isCssUrlWithoutSideEffects = (url2) => {
|
|
|
963
963
|
}
|
|
964
964
|
return false;
|
|
965
965
|
};
|
|
966
|
-
var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
|
|
967
966
|
var getStylesForFiles = async ({
|
|
968
967
|
viteDevServer,
|
|
969
968
|
rootDirectory,
|
|
970
|
-
|
|
969
|
+
loadCssContents,
|
|
971
970
|
files
|
|
972
971
|
}) => {
|
|
973
|
-
let vite2 = getVite();
|
|
974
|
-
let viteMajor = parseInt(vite2.version.split(".")[0], 10);
|
|
975
972
|
let styles = {};
|
|
976
973
|
let deps = /* @__PURE__ */ new Set();
|
|
977
974
|
try {
|
|
@@ -1000,21 +997,9 @@ var getStylesForFiles = async ({
|
|
|
1000
997
|
for (let dep of deps) {
|
|
1001
998
|
if (dep.file && isCssFile(dep.file) && !isCssUrlWithoutSideEffects(dep.url)) {
|
|
1002
999
|
try {
|
|
1003
|
-
|
|
1004
|
-
// We need the ?inline query in Vite v6 when loading CSS in SSR
|
|
1005
|
-
// since it does not expose the default export for CSS in a
|
|
1006
|
-
// server environment. This is to align with non-SSR
|
|
1007
|
-
// environments. For backwards compatibility with v5 we keep
|
|
1008
|
-
// using the URL without ?inline query because the HMR code was
|
|
1009
|
-
// relying on the implicit SSR-client module graph relationship.
|
|
1010
|
-
viteMajor >= 6 ? injectQuery(dep.url, "inline") : dep.url
|
|
1011
|
-
)).default;
|
|
1012
|
-
if (css === void 0) {
|
|
1013
|
-
throw new Error();
|
|
1014
|
-
}
|
|
1015
|
-
styles[dep.url] = css;
|
|
1000
|
+
styles[dep.url] = await loadCssContents(viteDevServer, dep);
|
|
1016
1001
|
} catch {
|
|
1017
|
-
console.warn(`
|
|
1002
|
+
console.warn(`Failed to load CSS for ${dep.file}`);
|
|
1018
1003
|
}
|
|
1019
1004
|
}
|
|
1020
1005
|
}
|
|
@@ -1073,7 +1058,7 @@ var getStylesForUrl = async ({
|
|
|
1073
1058
|
rootDirectory,
|
|
1074
1059
|
reactRouterConfig,
|
|
1075
1060
|
entryClientFilePath,
|
|
1076
|
-
|
|
1061
|
+
loadCssContents,
|
|
1077
1062
|
build,
|
|
1078
1063
|
url: url2
|
|
1079
1064
|
}) => {
|
|
@@ -1088,7 +1073,7 @@ var getStylesForUrl = async ({
|
|
|
1088
1073
|
let styles = await getStylesForFiles({
|
|
1089
1074
|
viteDevServer,
|
|
1090
1075
|
rootDirectory,
|
|
1091
|
-
|
|
1076
|
+
loadCssContents,
|
|
1092
1077
|
files: [
|
|
1093
1078
|
// Always include the client entry file when crawling the module graph for CSS
|
|
1094
1079
|
path5.relative(rootDirectory, entryClientFilePath),
|
|
@@ -1857,6 +1842,8 @@ var plugin = {
|
|
|
1857
1842
|
return function Wrapped() {
|
|
1858
1843
|
const props = {
|
|
1859
1844
|
params: useParams(),
|
|
1845
|
+
loaderData: useLoaderData(),
|
|
1846
|
+
actionData: useActionData(),
|
|
1860
1847
|
};
|
|
1861
1848
|
return h(HydrateFallback, props);
|
|
1862
1849
|
};
|
|
@@ -1965,6 +1952,7 @@ var CLIENT_ROUTE_EXPORTS = [
|
|
|
1965
1952
|
];
|
|
1966
1953
|
var BUILD_CLIENT_ROUTE_QUERY_STRING = "?__react-router-build-client-route";
|
|
1967
1954
|
var SSR_BUNDLE_PREFIX = "ssrBundle_";
|
|
1955
|
+
var CSS_DEV_HELPER_ENVIRONMENT_NAME = "__react_router_css_dev_helper__";
|
|
1968
1956
|
function isSeverBundleEnvironmentName(name) {
|
|
1969
1957
|
return name.startsWith(SSR_BUNDLE_PREFIX);
|
|
1970
1958
|
}
|
|
@@ -2156,6 +2144,7 @@ var getServerBuildDirectory = (ctx, { serverBundleId } = {}) => path6.join(
|
|
|
2156
2144
|
...serverBundleId ? [serverBundleId] : []
|
|
2157
2145
|
);
|
|
2158
2146
|
var getClientBuildDirectory = (reactRouterConfig) => path6.join(reactRouterConfig.buildDirectory, "client");
|
|
2147
|
+
var injectQuery = (url2, query) => url2.includes("?") ? url2.replace("?", `?${query}&`) : `${url2}?${query}`;
|
|
2159
2148
|
var defaultEntriesDir = path6.resolve(
|
|
2160
2149
|
path6.dirname(require.resolve("@react-router/dev/package.json")),
|
|
2161
2150
|
"dist",
|
|
@@ -2232,6 +2221,11 @@ var reactRouterVitePlugin = () => {
|
|
|
2232
2221
|
// Otherwise, all routes are imported as usual
|
|
2233
2222
|
ctx.reactRouterConfig.routes
|
|
2234
2223
|
);
|
|
2224
|
+
let prerenderPaths = await getPrerenderPaths(
|
|
2225
|
+
ctx.reactRouterConfig.prerender,
|
|
2226
|
+
ctx.reactRouterConfig.ssr,
|
|
2227
|
+
routes
|
|
2228
|
+
);
|
|
2235
2229
|
return `
|
|
2236
2230
|
import * as entryServer from ${JSON.stringify(
|
|
2237
2231
|
resolveFileUrl(ctx, ctx.entryServerFilePath)
|
|
@@ -2258,6 +2252,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2258
2252
|
export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
|
|
2259
2253
|
export const ssr = ${ctx.reactRouterConfig.ssr};
|
|
2260
2254
|
export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
|
|
2255
|
+
export const prerender = ${JSON.stringify(prerenderPaths)};
|
|
2261
2256
|
export const publicPath = ${JSON.stringify(ctx.publicPath)};
|
|
2262
2257
|
export const entry = { module: entryServer };
|
|
2263
2258
|
export const routes = {
|
|
@@ -2406,6 +2401,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2406
2401
|
reactRouterServerManifest
|
|
2407
2402
|
};
|
|
2408
2403
|
};
|
|
2404
|
+
let currentReactRouterManifestForDev = null;
|
|
2409
2405
|
let getReactRouterManifestForDev = async () => {
|
|
2410
2406
|
let routes = {};
|
|
2411
2407
|
let routeManifestExports = await getRouteManifestModuleExports(
|
|
@@ -2462,7 +2458,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2462
2458
|
imports: []
|
|
2463
2459
|
};
|
|
2464
2460
|
}
|
|
2465
|
-
|
|
2461
|
+
let reactRouterManifestForDev = {
|
|
2466
2462
|
version: String(Math.random()),
|
|
2467
2463
|
url: combineURLs(ctx.publicPath, virtual.browserManifest.url),
|
|
2468
2464
|
hmr: {
|
|
@@ -2477,6 +2473,42 @@ var reactRouterVitePlugin = () => {
|
|
|
2477
2473
|
},
|
|
2478
2474
|
routes
|
|
2479
2475
|
};
|
|
2476
|
+
currentReactRouterManifestForDev = reactRouterManifestForDev;
|
|
2477
|
+
return reactRouterManifestForDev;
|
|
2478
|
+
};
|
|
2479
|
+
const loadCssContents = async (viteDevServer, dep) => {
|
|
2480
|
+
invariant(
|
|
2481
|
+
viteCommand === "serve",
|
|
2482
|
+
"loadCssContents is only available in dev mode"
|
|
2483
|
+
);
|
|
2484
|
+
if (dep.file && isCssModulesFile(dep.file)) {
|
|
2485
|
+
return cssModulesManifest[dep.file];
|
|
2486
|
+
}
|
|
2487
|
+
const vite2 = getVite();
|
|
2488
|
+
const viteMajor = parseInt(vite2.version.split(".")[0], 10);
|
|
2489
|
+
const url2 = viteMajor >= 6 ? (
|
|
2490
|
+
// We need the ?inline query in Vite v6 when loading CSS in SSR
|
|
2491
|
+
// since it does not expose the default export for CSS in a
|
|
2492
|
+
// server environment. This is to align with non-SSR
|
|
2493
|
+
// environments. For backwards compatibility with v5 we keep
|
|
2494
|
+
// using the URL without ?inline query because the HMR code was
|
|
2495
|
+
// relying on the implicit SSR-client module graph relationship.
|
|
2496
|
+
injectQuery(dep.url, "inline")
|
|
2497
|
+
) : dep.url;
|
|
2498
|
+
let cssMod;
|
|
2499
|
+
if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi) {
|
|
2500
|
+
const cssDevHelperEnvironment = viteDevServer.environments[CSS_DEV_HELPER_ENVIRONMENT_NAME];
|
|
2501
|
+
invariant(cssDevHelperEnvironment, "Missing CSS dev helper environment");
|
|
2502
|
+
invariant(vite2.isRunnableDevEnvironment(cssDevHelperEnvironment));
|
|
2503
|
+
cssMod = await cssDevHelperEnvironment.runner.import(url2);
|
|
2504
|
+
} else {
|
|
2505
|
+
cssMod = await viteDevServer.ssrLoadModule(url2);
|
|
2506
|
+
}
|
|
2507
|
+
invariant(
|
|
2508
|
+
typeof cssMod === "object" && cssMod !== null && "default" in cssMod && typeof cssMod.default === "string",
|
|
2509
|
+
`Failed to load CSS for ${dep.file ?? dep.url}`
|
|
2510
|
+
);
|
|
2511
|
+
return cssMod.default;
|
|
2480
2512
|
};
|
|
2481
2513
|
return [
|
|
2482
2514
|
{
|
|
@@ -2655,6 +2687,8 @@ var reactRouterVitePlugin = () => {
|
|
|
2655
2687
|
}
|
|
2656
2688
|
viteChildCompiler = await vite2.createServer({
|
|
2657
2689
|
...viteUserConfig,
|
|
2690
|
+
// Ensure child compiler cannot overwrite the default cache directory
|
|
2691
|
+
cacheDir: "node_modules/.vite-child-compiler",
|
|
2658
2692
|
mode: viteConfig.mode,
|
|
2659
2693
|
server: {
|
|
2660
2694
|
watch: viteConfig.command === "build" ? null : void 0,
|
|
@@ -2666,7 +2700,23 @@ var reactRouterVitePlugin = () => {
|
|
|
2666
2700
|
plugins: [
|
|
2667
2701
|
...(childCompilerConfigFile.config.plugins ?? []).flat().filter(
|
|
2668
2702
|
(plugin2) => typeof plugin2 === "object" && plugin2 !== null && "name" in plugin2 && plugin2.name !== "react-router" && plugin2.name !== "react-router:route-exports" && plugin2.name !== "react-router:hmr-updates"
|
|
2669
|
-
)
|
|
2703
|
+
),
|
|
2704
|
+
{
|
|
2705
|
+
name: "react-router:override-optimize-deps",
|
|
2706
|
+
config(userConfig) {
|
|
2707
|
+
if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && userConfig.environments) {
|
|
2708
|
+
for (const environmentName of Object.keys(
|
|
2709
|
+
userConfig.environments
|
|
2710
|
+
)) {
|
|
2711
|
+
userConfig.environments[environmentName].optimizeDeps = {
|
|
2712
|
+
noDiscovery: true
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
} else {
|
|
2716
|
+
userConfig.optimizeDeps = { noDiscovery: true };
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2670
2720
|
]
|
|
2671
2721
|
});
|
|
2672
2722
|
await viteChildCompiler.pluginContainer.buildStart({});
|
|
@@ -2703,7 +2753,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2703
2753
|
entryClientFilePath: ctx.entryClientFilePath,
|
|
2704
2754
|
reactRouterConfig: ctx.reactRouterConfig,
|
|
2705
2755
|
viteDevServer,
|
|
2706
|
-
|
|
2756
|
+
loadCssContents,
|
|
2707
2757
|
build,
|
|
2708
2758
|
url: url2
|
|
2709
2759
|
});
|
|
@@ -2818,7 +2868,7 @@ var reactRouterVitePlugin = () => {
|
|
|
2818
2868
|
].join("\n")
|
|
2819
2869
|
);
|
|
2820
2870
|
}
|
|
2821
|
-
if (ctx.reactRouterConfig
|
|
2871
|
+
if (isPrerenderingEnabled(ctx.reactRouterConfig)) {
|
|
2822
2872
|
await handlePrerender(
|
|
2823
2873
|
viteConfig,
|
|
2824
2874
|
ctx.reactRouterConfig,
|
|
@@ -2992,6 +3042,15 @@ var reactRouterVitePlugin = () => {
|
|
|
2992
3042
|
let reactRouterManifest = viteCommand === "build" ? (await generateReactRouterManifestsForBuild({
|
|
2993
3043
|
routeIds
|
|
2994
3044
|
})).reactRouterServerManifest : await getReactRouterManifestForDev();
|
|
3045
|
+
if (!ctx.reactRouterConfig.ssr) {
|
|
3046
|
+
invariant(viteConfig);
|
|
3047
|
+
validateSsrFalsePrerenderExports(
|
|
3048
|
+
viteConfig,
|
|
3049
|
+
ctx,
|
|
3050
|
+
reactRouterManifest,
|
|
3051
|
+
viteChildCompiler
|
|
3052
|
+
);
|
|
3053
|
+
}
|
|
2995
3054
|
return `export default ${(0, import_jsesc.default)(reactRouterManifest, {
|
|
2996
3055
|
es6: true
|
|
2997
3056
|
})};`;
|
|
@@ -3092,12 +3151,15 @@ var reactRouterVitePlugin = () => {
|
|
|
3092
3151
|
if (!route) return;
|
|
3093
3152
|
if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
|
|
3094
3153
|
let exportNames = getExportNames(code);
|
|
3095
|
-
let serverOnlyExports = exportNames.filter(
|
|
3096
|
-
(exp
|
|
3097
|
-
|
|
3154
|
+
let serverOnlyExports = exportNames.filter((exp) => {
|
|
3155
|
+
if (route.id === "root" && exp === "loader") {
|
|
3156
|
+
return false;
|
|
3157
|
+
}
|
|
3158
|
+
return SERVER_ONLY_ROUTE_EXPORTS.includes(exp);
|
|
3159
|
+
});
|
|
3098
3160
|
if (serverOnlyExports.length > 0) {
|
|
3099
3161
|
let str = serverOnlyExports.map((e) => `\`${e}\``).join(", ");
|
|
3100
|
-
let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://
|
|
3162
|
+
let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://reactrouter.com/how-to/spa for more information.`;
|
|
3101
3163
|
throw Error(message);
|
|
3102
3164
|
}
|
|
3103
3165
|
if (route.id !== "root") {
|
|
@@ -3105,7 +3167,7 @@ var reactRouterVitePlugin = () => {
|
|
|
3105
3167
|
(exp) => exp === "HydrateFallback"
|
|
3106
3168
|
);
|
|
3107
3169
|
if (hasHydrateFallback) {
|
|
3108
|
-
let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://
|
|
3170
|
+
let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://reactrouter.com/how-to/spa for more information.`;
|
|
3109
3171
|
throw Error(message);
|
|
3110
3172
|
}
|
|
3111
3173
|
}
|
|
@@ -3211,8 +3273,7 @@ var reactRouterVitePlugin = () => {
|
|
|
3211
3273
|
let route = getRoute(ctx.reactRouterConfig, file);
|
|
3212
3274
|
let hmrEventData = { route: null };
|
|
3213
3275
|
if (route) {
|
|
3214
|
-
let
|
|
3215
|
-
let oldRouteMetadata = serverManifest.routes[route.id];
|
|
3276
|
+
let oldRouteMetadata = currentReactRouterManifestForDev?.routes[route.id];
|
|
3216
3277
|
let newRouteMetadata = await getRouteMetadata(
|
|
3217
3278
|
cache,
|
|
3218
3279
|
ctx,
|
|
@@ -3398,8 +3459,11 @@ async function getRouteMetadata(cache, ctx, viteChildCompiler, route, readRouteF
|
|
|
3398
3459
|
};
|
|
3399
3460
|
return info;
|
|
3400
3461
|
}
|
|
3462
|
+
function isPrerenderingEnabled(reactRouterConfig) {
|
|
3463
|
+
return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
|
|
3464
|
+
}
|
|
3401
3465
|
function isSpaModeEnabled(reactRouterConfig) {
|
|
3402
|
-
return reactRouterConfig.ssr === false && (reactRouterConfig
|
|
3466
|
+
return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
|
|
3403
3467
|
}
|
|
3404
3468
|
async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, serverBuildFile) {
|
|
3405
3469
|
let serverBuildPath = path6.join(serverBuildDirectory, serverBuildFile);
|
|
@@ -3411,20 +3475,49 @@ async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, ser
|
|
|
3411
3475
|
};
|
|
3412
3476
|
}
|
|
3413
3477
|
async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildFile, clientBuildDirectory) {
|
|
3414
|
-
let { handler } = await getPrerenderBuildAndHandler(
|
|
3478
|
+
let { build, handler } = await getPrerenderBuildAndHandler(
|
|
3415
3479
|
viteConfig,
|
|
3416
3480
|
serverBuildDirectory,
|
|
3417
3481
|
serverBuildFile
|
|
3418
3482
|
);
|
|
3419
|
-
let request = new Request(`http://localhost${reactRouterConfig.basename}
|
|
3483
|
+
let request = new Request(`http://localhost${reactRouterConfig.basename}`, {
|
|
3484
|
+
headers: {
|
|
3485
|
+
// Enable SPA mode in the server runtime and only render down to the root
|
|
3486
|
+
"X-React-Router-SPA-Mode": "yes"
|
|
3487
|
+
}
|
|
3488
|
+
});
|
|
3420
3489
|
let response = await handler(request);
|
|
3421
3490
|
let html = await response.text();
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3491
|
+
let isPrerenderSpaFallback = build.prerender.includes("/");
|
|
3492
|
+
let filename3 = isPrerenderSpaFallback ? "__spa-fallback.html" : "index.html";
|
|
3493
|
+
if (response.status !== 200) {
|
|
3494
|
+
if (isPrerenderSpaFallback) {
|
|
3495
|
+
throw new Error(
|
|
3496
|
+
`Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
|
|
3497
|
+
` + html
|
|
3498
|
+
);
|
|
3499
|
+
} else {
|
|
3500
|
+
throw new Error(
|
|
3501
|
+
`SPA Mode: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
|
|
3502
|
+
` + html
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
|
|
3507
|
+
throw new Error(
|
|
3508
|
+
"SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`."
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
await fse.writeFile(path6.join(clientBuildDirectory, filename3), html);
|
|
3512
|
+
let prettyDir = path6.relative(process.cwd(), clientBuildDirectory);
|
|
3513
|
+
let prettyPath = path6.join(prettyDir, filename3);
|
|
3514
|
+
if (build.prerender.length > 0) {
|
|
3515
|
+
viteConfig.logger.info(
|
|
3516
|
+
`Prerender (html): SPA Fallback -> ${import_picocolors3.default.bold(prettyPath)}`
|
|
3517
|
+
);
|
|
3518
|
+
} else {
|
|
3519
|
+
viteConfig.logger.info(`SPA Mode: Generated ${import_picocolors3.default.bold(prettyPath)}`);
|
|
3520
|
+
}
|
|
3428
3521
|
}
|
|
3429
3522
|
async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildPath, clientBuildDirectory) {
|
|
3430
3523
|
let { build, handler } = await getPrerenderBuildAndHandler(
|
|
@@ -3433,53 +3526,62 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
|
|
|
3433
3526
|
serverBuildPath
|
|
3434
3527
|
);
|
|
3435
3528
|
let routes = createPrerenderRoutes(build.routes);
|
|
3436
|
-
let routesToPrerender;
|
|
3437
|
-
if (typeof reactRouterConfig.prerender === "boolean") {
|
|
3438
|
-
invariant(reactRouterConfig.prerender, "Expected prerender:true");
|
|
3439
|
-
routesToPrerender = determineStaticPrerenderRoutes(
|
|
3440
|
-
routes,
|
|
3441
|
-
viteConfig,
|
|
3442
|
-
true
|
|
3443
|
-
);
|
|
3444
|
-
} else if (typeof reactRouterConfig.prerender === "function") {
|
|
3445
|
-
routesToPrerender = await reactRouterConfig.prerender({
|
|
3446
|
-
getStaticPaths: () => determineStaticPrerenderRoutes(routes, viteConfig, false)
|
|
3447
|
-
});
|
|
3448
|
-
} else {
|
|
3449
|
-
routesToPrerender = reactRouterConfig.prerender || ["/"];
|
|
3450
|
-
}
|
|
3451
3529
|
let headers = {
|
|
3452
3530
|
// Header that can be used in the loader to know if you're running at
|
|
3453
3531
|
// build time or runtime
|
|
3454
3532
|
"X-React-Router-Prerender": "yes"
|
|
3455
3533
|
};
|
|
3456
|
-
for (let path7 of
|
|
3534
|
+
for (let path7 of build.prerender) {
|
|
3457
3535
|
let matches = (0, import_react_router2.matchRoutes)(routes, `/${path7}/`.replace(/^\/\/+/, "/"));
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
handler,
|
|
3463
|
-
path7,
|
|
3464
|
-
clientBuildDirectory,
|
|
3465
|
-
reactRouterConfig,
|
|
3466
|
-
viteConfig,
|
|
3467
|
-
{ headers }
|
|
3468
|
-
);
|
|
3469
|
-
}
|
|
3536
|
+
invariant(
|
|
3537
|
+
matches,
|
|
3538
|
+
`Unable to prerender path because it does not match any routes: ${path7}`
|
|
3539
|
+
);
|
|
3470
3540
|
let leafRoute = matches ? matches[matches.length - 1].route : null;
|
|
3471
3541
|
let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
|
|
3472
|
-
let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary
|
|
3542
|
+
let isResourceRoute = manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;
|
|
3473
3543
|
if (isResourceRoute) {
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3544
|
+
invariant(leafRoute);
|
|
3545
|
+
invariant(manifestRoute);
|
|
3546
|
+
if (manifestRoute.loader) {
|
|
3547
|
+
await prerenderData(
|
|
3548
|
+
handler,
|
|
3549
|
+
path7,
|
|
3550
|
+
[leafRoute.id],
|
|
3551
|
+
clientBuildDirectory,
|
|
3552
|
+
reactRouterConfig,
|
|
3553
|
+
viteConfig,
|
|
3554
|
+
{ headers }
|
|
3555
|
+
);
|
|
3556
|
+
await prerenderResourceRoute(
|
|
3557
|
+
handler,
|
|
3558
|
+
path7,
|
|
3559
|
+
clientBuildDirectory,
|
|
3560
|
+
reactRouterConfig,
|
|
3561
|
+
viteConfig,
|
|
3562
|
+
{ headers }
|
|
3563
|
+
);
|
|
3564
|
+
} else {
|
|
3565
|
+
viteConfig.logger.warn(
|
|
3566
|
+
`\u26A0\uFE0F Skipping prerendering for resource route without a loader: ${leafRoute?.id}`
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3482
3569
|
} else {
|
|
3570
|
+
let hasLoaders = matches.some(
|
|
3571
|
+
(m) => build.assets.routes[m.route.id]?.hasLoader
|
|
3572
|
+
);
|
|
3573
|
+
let data;
|
|
3574
|
+
if (!isResourceRoute && hasLoaders) {
|
|
3575
|
+
data = await prerenderData(
|
|
3576
|
+
handler,
|
|
3577
|
+
path7,
|
|
3578
|
+
null,
|
|
3579
|
+
clientBuildDirectory,
|
|
3580
|
+
reactRouterConfig,
|
|
3581
|
+
viteConfig,
|
|
3582
|
+
{ headers }
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3483
3585
|
await prerenderRoute(
|
|
3484
3586
|
handler,
|
|
3485
3587
|
path7,
|
|
@@ -3496,7 +3598,7 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
|
|
|
3496
3598
|
}
|
|
3497
3599
|
}
|
|
3498
3600
|
}
|
|
3499
|
-
function
|
|
3601
|
+
function getStaticPrerenderPaths(routes) {
|
|
3500
3602
|
let paths = ["/"];
|
|
3501
3603
|
let paramRoutes = [];
|
|
3502
3604
|
function recurse(subtree, prefix = "") {
|
|
@@ -3516,28 +3618,33 @@ function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = fal
|
|
|
3516
3618
|
}
|
|
3517
3619
|
}
|
|
3518
3620
|
recurse(routes);
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
"You may want to use the `prerender()` API to prerender the following paths:",
|
|
3524
|
-
...paramRoutes.map((p) => " - " + p)
|
|
3525
|
-
].join("\n")
|
|
3526
|
-
);
|
|
3527
|
-
}
|
|
3528
|
-
return paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1"));
|
|
3621
|
+
return {
|
|
3622
|
+
paths: paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1")),
|
|
3623
|
+
paramRoutes
|
|
3624
|
+
};
|
|
3529
3625
|
}
|
|
3530
|
-
async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
|
|
3626
|
+
async function prerenderData(handler, prerenderPath, onlyRoutes, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
|
|
3531
3627
|
let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
|
|
3532
|
-
let
|
|
3628
|
+
let url2 = new URL(`http://localhost${normalizedPath}`);
|
|
3629
|
+
if (onlyRoutes?.length) {
|
|
3630
|
+
url2.searchParams.set("_routes", onlyRoutes.join(","));
|
|
3631
|
+
}
|
|
3632
|
+
let request = new Request(url2, requestInit);
|
|
3533
3633
|
let response = await handler(request);
|
|
3534
3634
|
let data = await response.text();
|
|
3535
|
-
|
|
3635
|
+
if (response.status !== 200) {
|
|
3636
|
+
throw new Error(
|
|
3637
|
+
`Prerender (data): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path6}\` path.
|
|
3638
|
+
${normalizedPath}`
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3536
3641
|
let outdir = path6.relative(process.cwd(), clientBuildDirectory);
|
|
3537
3642
|
let outfile = path6.join(outdir, ...normalizedPath.split("/"));
|
|
3538
3643
|
await fse.ensureDir(path6.dirname(outfile));
|
|
3539
3644
|
await fse.outputFile(outfile, data);
|
|
3540
|
-
viteConfig.logger.info(
|
|
3645
|
+
viteConfig.logger.info(
|
|
3646
|
+
`Prerender (data): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
|
|
3647
|
+
);
|
|
3541
3648
|
return data;
|
|
3542
3649
|
}
|
|
3543
3650
|
async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
|
|
@@ -3548,42 +3655,65 @@ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reac
|
|
|
3548
3655
|
let request = new Request(`http://localhost${normalizedPath}`, requestInit);
|
|
3549
3656
|
let response = await handler(request);
|
|
3550
3657
|
let html = await response.text();
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3658
|
+
if (response.status !== 200) {
|
|
3659
|
+
throw new Error(
|
|
3660
|
+
`Prerender (html): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
|
|
3661
|
+
${html}`
|
|
3662
|
+
);
|
|
3554
3663
|
}
|
|
3555
3664
|
let outdir = path6.relative(process.cwd(), clientBuildDirectory);
|
|
3556
3665
|
let outfile = path6.join(outdir, ...normalizedPath.split("/"), "index.html");
|
|
3557
3666
|
await fse.ensureDir(path6.dirname(outfile));
|
|
3558
3667
|
await fse.outputFile(outfile, html);
|
|
3559
|
-
viteConfig.logger.info(
|
|
3668
|
+
viteConfig.logger.info(
|
|
3669
|
+
`Prerender (html): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
|
|
3670
|
+
);
|
|
3560
3671
|
}
|
|
3561
3672
|
async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
|
|
3562
3673
|
let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
|
|
3563
3674
|
let request = new Request(`http://localhost${normalizedPath}`, requestInit);
|
|
3564
3675
|
let response = await handler(request);
|
|
3565
|
-
let
|
|
3566
|
-
validatePrerenderedResponse(response, text, "Prerender", normalizedPath);
|
|
3567
|
-
let outdir = path6.relative(process.cwd(), clientBuildDirectory);
|
|
3568
|
-
let outfile = path6.join(outdir, ...normalizedPath.split("/"));
|
|
3569
|
-
await fse.ensureDir(path6.dirname(outfile));
|
|
3570
|
-
await fse.outputFile(outfile, text);
|
|
3571
|
-
viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
|
|
3572
|
-
}
|
|
3573
|
-
function validatePrerenderedResponse(response, html, prefix, path7) {
|
|
3676
|
+
let content = Buffer.from(await response.arrayBuffer());
|
|
3574
3677
|
if (response.status !== 200) {
|
|
3575
3678
|
throw new Error(
|
|
3576
|
-
|
|
3577
|
-
${
|
|
3679
|
+
`Prerender (resource): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
|
|
3680
|
+
${content.toString("utf8")}`
|
|
3578
3681
|
);
|
|
3579
3682
|
}
|
|
3683
|
+
let outdir = path6.relative(process.cwd(), clientBuildDirectory);
|
|
3684
|
+
let outfile = path6.join(outdir, ...normalizedPath.split("/"));
|
|
3685
|
+
await fse.ensureDir(path6.dirname(outfile));
|
|
3686
|
+
await fse.outputFile(outfile, content);
|
|
3687
|
+
viteConfig.logger.info(
|
|
3688
|
+
`Prerender (resource): ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
|
|
3689
|
+
);
|
|
3580
3690
|
}
|
|
3581
|
-
function
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
)
|
|
3691
|
+
async function getPrerenderPaths(prerender, ssr, routes, logWarning = false) {
|
|
3692
|
+
let prerenderPaths = [];
|
|
3693
|
+
if (prerender != null && prerender !== false) {
|
|
3694
|
+
let prerenderRoutes = createPrerenderRoutes(routes);
|
|
3695
|
+
if (prerender === true) {
|
|
3696
|
+
let { paths, paramRoutes } = getStaticPrerenderPaths(prerenderRoutes);
|
|
3697
|
+
if (logWarning && !ssr && paramRoutes.length > 0) {
|
|
3698
|
+
console.warn(
|
|
3699
|
+
import_picocolors3.default.yellow(
|
|
3700
|
+
[
|
|
3701
|
+
"\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`. You may want to use the `prerender()` API to prerender the following paths:",
|
|
3702
|
+
...paramRoutes.map((p) => " - " + p)
|
|
3703
|
+
].join("\n")
|
|
3704
|
+
)
|
|
3705
|
+
);
|
|
3706
|
+
}
|
|
3707
|
+
prerenderPaths = paths;
|
|
3708
|
+
} else if (typeof prerender === "function") {
|
|
3709
|
+
prerenderPaths = await prerender({
|
|
3710
|
+
getStaticPaths: () => getStaticPrerenderPaths(prerenderRoutes).paths
|
|
3711
|
+
});
|
|
3712
|
+
} else {
|
|
3713
|
+
prerenderPaths = prerender || ["/"];
|
|
3714
|
+
}
|
|
3586
3715
|
}
|
|
3716
|
+
return prerenderPaths;
|
|
3587
3717
|
}
|
|
3588
3718
|
function groupRoutesByParentId2(manifest) {
|
|
3589
3719
|
let routes = {};
|
|
@@ -3601,24 +3731,84 @@ function groupRoutesByParentId2(manifest) {
|
|
|
3601
3731
|
function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
|
|
3602
3732
|
return (routesByParentId[parentId] || []).map((route) => {
|
|
3603
3733
|
let commonRoute = {
|
|
3604
|
-
// Always include root due to default boundaries
|
|
3605
|
-
hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
|
|
3606
3734
|
id: route.id,
|
|
3607
|
-
path: route.path
|
|
3608
|
-
loader: route.module.loader ? () => null : void 0,
|
|
3609
|
-
action: void 0,
|
|
3610
|
-
handle: route.module.handle
|
|
3735
|
+
path: route.path
|
|
3611
3736
|
};
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3737
|
+
if (route.index) {
|
|
3738
|
+
return {
|
|
3739
|
+
index: true,
|
|
3740
|
+
...commonRoute
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
return {
|
|
3617
3744
|
children: createPrerenderRoutes(manifest, route.id, routesByParentId),
|
|
3618
3745
|
...commonRoute
|
|
3619
3746
|
};
|
|
3620
3747
|
});
|
|
3621
3748
|
}
|
|
3749
|
+
async function validateSsrFalsePrerenderExports(viteConfig, ctx, manifest, viteChildCompiler) {
|
|
3750
|
+
let prerenderPaths = await getPrerenderPaths(
|
|
3751
|
+
ctx.reactRouterConfig.prerender,
|
|
3752
|
+
ctx.reactRouterConfig.ssr,
|
|
3753
|
+
manifest.routes,
|
|
3754
|
+
true
|
|
3755
|
+
);
|
|
3756
|
+
if (prerenderPaths.length === 0) {
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
let prerenderRoutes = createPrerenderRoutes(manifest.routes);
|
|
3760
|
+
let prerenderedRoutes = /* @__PURE__ */ new Set();
|
|
3761
|
+
for (let path7 of prerenderPaths) {
|
|
3762
|
+
let matches = (0, import_react_router2.matchRoutes)(
|
|
3763
|
+
prerenderRoutes,
|
|
3764
|
+
`/${path7}/`.replace(/^\/\/+/, "/")
|
|
3765
|
+
);
|
|
3766
|
+
invariant(
|
|
3767
|
+
matches,
|
|
3768
|
+
`Unable to prerender path because it does not match any routes: ${path7}`
|
|
3769
|
+
);
|
|
3770
|
+
matches.forEach((m) => prerenderedRoutes.add(m.route.id));
|
|
3771
|
+
}
|
|
3772
|
+
let errors = [];
|
|
3773
|
+
let routeExports = await getRouteManifestModuleExports(
|
|
3774
|
+
viteChildCompiler,
|
|
3775
|
+
ctx
|
|
3776
|
+
);
|
|
3777
|
+
for (let [routeId, route] of Object.entries(manifest.routes)) {
|
|
3778
|
+
let invalidApis = [];
|
|
3779
|
+
invariant(route, "Expected a route object in validateSsrFalseExports");
|
|
3780
|
+
let exports2 = routeExports[route.id];
|
|
3781
|
+
if (exports2.includes("headers")) invalidApis.push("headers");
|
|
3782
|
+
if (exports2.includes("action")) invalidApis.push("action");
|
|
3783
|
+
if (invalidApis.length > 0) {
|
|
3784
|
+
errors.push(
|
|
3785
|
+
`Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when pre-rendering with \`ssr:false\`: ${invalidApis.map((a) => `\`${a}\``).join(", ")}. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
3786
|
+
);
|
|
3787
|
+
}
|
|
3788
|
+
if (!prerenderedRoutes.has(routeId)) {
|
|
3789
|
+
if (exports2.includes("loader")) {
|
|
3790
|
+
errors.push(
|
|
3791
|
+
`Prerender: 1 invalid route export in \`${route.id}\` when pre-rendering with \`ssr:false\`: \`loader\`. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
3792
|
+
);
|
|
3793
|
+
}
|
|
3794
|
+
let parentRoute = route.parentId ? manifest.routes[route.parentId] : null;
|
|
3795
|
+
while (parentRoute && parentRoute.id !== "root") {
|
|
3796
|
+
if (parentRoute.hasLoader && !parentRoute.hasClientLoader) {
|
|
3797
|
+
errors.push(
|
|
3798
|
+
`Prerender: 1 invalid route export in \`${parentRoute.id}\` when pre-rendering with \`ssr:false\`: \`loader\`. See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
parentRoute = parentRoute.parentId && parentRoute.parentId !== "root" ? manifest.routes[parentRoute.parentId] : null;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
if (errors.length > 0) {
|
|
3806
|
+
viteConfig.logger.error(import_picocolors3.default.red(errors.join("\n")));
|
|
3807
|
+
throw new Error(
|
|
3808
|
+
"Invalid route exports found when prerendering with `ssr:false`"
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3622
3812
|
function getAddressableRoutes(routes) {
|
|
3623
3813
|
let nonAddressableIds = /* @__PURE__ */ new Set();
|
|
3624
3814
|
for (let id in routes) {
|
|
@@ -3961,11 +4151,28 @@ async function getEnvironmentOptionsResolvers(ctx, buildManifest, viteCommand) {
|
|
|
3961
4151
|
build: {
|
|
3962
4152
|
outDir: getServerBuildDirectory(ctx),
|
|
3963
4153
|
rollupOptions: {
|
|
3964
|
-
input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
|
|
4154
|
+
input: (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi ? viteUserConfig.environments?.ssr?.build?.rollupOptions?.input : viteUserConfig.build?.rollupOptions?.input) ?? virtual.serverBuild.id
|
|
3965
4155
|
}
|
|
3966
|
-
}
|
|
4156
|
+
},
|
|
4157
|
+
optimizeDeps: ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteUserConfig.environments?.ssr?.optimizeDeps?.noDiscovery === false ? {
|
|
4158
|
+
entries: [
|
|
4159
|
+
vite2.normalizePath(ctx.entryServerFilePath),
|
|
4160
|
+
...Object.values(ctx.reactRouterConfig.routes).map(
|
|
4161
|
+
(route) => resolveRelativeRouteFilePath(route, ctx.reactRouterConfig)
|
|
4162
|
+
)
|
|
4163
|
+
],
|
|
4164
|
+
include: [
|
|
4165
|
+
"react",
|
|
4166
|
+
"react/jsx-dev-runtime",
|
|
4167
|
+
"react-dom/server",
|
|
4168
|
+
"react-router"
|
|
4169
|
+
]
|
|
4170
|
+
} : void 0
|
|
3967
4171
|
});
|
|
3968
4172
|
}
|
|
4173
|
+
if (ctx.reactRouterConfig.future.unstable_viteEnvironmentApi && viteCommand === "serve") {
|
|
4174
|
+
environmentOptionsResolvers[CSS_DEV_HELPER_ENVIRONMENT_NAME] = () => ({});
|
|
4175
|
+
}
|
|
3969
4176
|
return environmentOptionsResolvers;
|
|
3970
4177
|
}
|
|
3971
4178
|
function resolveEnvironmentsOptions(environmentResolvers, resolverOptions) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-router/dev",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-8677247c0",
|
|
4
4
|
"description": "Dev tools and CLI for React Router",
|
|
5
5
|
"homepage": "https://reactrouter.com",
|
|
6
6
|
"bugs": {
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"set-cookie-parser": "^2.6.0",
|
|
89
89
|
"valibot": "^0.41.0",
|
|
90
90
|
"vite-node": "3.0.0-beta.2",
|
|
91
|
-
"@react-router/node": "0.0.0-experimental-
|
|
91
|
+
"@react-router/node": "0.0.0-experimental-8677247c0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@types/babel__core": "^7.20.5",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"@types/prettier": "^2.7.3",
|
|
107
107
|
"@types/set-cookie-parser": "^2.4.1",
|
|
108
108
|
"dotenv": "^16.0.0",
|
|
109
|
-
"esbuild-register": "^3.
|
|
109
|
+
"esbuild-register": "^3.6.0",
|
|
110
110
|
"execa": "5.1.1",
|
|
111
111
|
"express": "^4.19.2",
|
|
112
112
|
"fast-glob": "3.2.11",
|
|
@@ -117,15 +117,15 @@
|
|
|
117
117
|
"vite": "^6.0.0",
|
|
118
118
|
"wireit": "0.14.9",
|
|
119
119
|
"wrangler": "^3.28.2",
|
|
120
|
-
"react-router": "
|
|
121
|
-
"
|
|
120
|
+
"@react-router/serve": "0.0.0-experimental-8677247c0",
|
|
121
|
+
"react-router": "^0.0.0-experimental-8677247c0"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
124
|
"typescript": "^5.1.0",
|
|
125
125
|
"vite": "^5.1.0 || ^6.0.0",
|
|
126
126
|
"wrangler": "^3.28.2",
|
|
127
|
-
"@react-router/serve": "^0.0.0-experimental-
|
|
128
|
-
"react-router": "^0.0.0-experimental-
|
|
127
|
+
"@react-router/serve": "^0.0.0-experimental-8677247c0",
|
|
128
|
+
"react-router": "^0.0.0-experimental-8677247c0"
|
|
129
129
|
},
|
|
130
130
|
"peerDependenciesMeta": {
|
|
131
131
|
"@react-router/serve": {
|