@netlify/vite-plugin-react-router 2.1.0 → 2.1.2
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 +14 -0
- package/README.md +13 -49
- package/dist/entry.server.edge.d.mts +20 -0
- package/dist/entry.server.edge.mjs +29 -0
- package/dist/index.js +24 -8
- package/dist/index.mjs +26 -10
- package/package.json +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.2](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.1.1...vite-plugin-react-router-v2.1.2) (2025-11-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **vite-plugin-react-router:** fix custom `build.assetsDir` edge case ([#578](https://github.com/netlify/remix-compute/issues/578)) ([7316d95](https://github.com/netlify/remix-compute/commit/7316d95ea087e19dffe9414749c891642096c4cd))
|
|
9
|
+
|
|
10
|
+
## [2.1.1](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.1.0...vite-plugin-react-router-v2.1.1) (2025-11-06)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **@netlify/vite-plugin-react-router:** fix local dev with `edge: true` ([#572](https://github.com/netlify/remix-compute/issues/572)) ([dfb27c1](https://github.com/netlify/remix-compute/commit/dfb27c1cb52d253063b2c19dd52b05fb5ec8f4ce))
|
|
16
|
+
|
|
3
17
|
## [2.1.0](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.0.1...vite-plugin-react-router-v2.1.0) (2025-11-05)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -58,59 +58,23 @@ export default defineConfig({
|
|
|
58
58
|
})
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file
|
|
62
|
-
Deno runtime. Create a file with the following content:
|
|
63
|
-
|
|
64
|
-
> [!IMPORTANT]
|
|
65
|
-
>
|
|
66
|
-
> This file uses `renderToReadableStream` (Web Streams API) instead of `renderToPipeableStream` (Node.js API), which is
|
|
67
|
-
> required for the Deno runtime. You may customize your server entry file, but see below for important edge runtime
|
|
68
|
-
> constraints.
|
|
61
|
+
Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file. Create a file with the following content:
|
|
69
62
|
|
|
70
63
|
```tsx
|
|
71
|
-
|
|
72
|
-
import { ServerRouter } from 'react-router'
|
|
73
|
-
import { isbot } from 'isbot'
|
|
74
|
-
import { renderToReadableStream } from 'react-dom/server'
|
|
75
|
-
|
|
76
|
-
export default async function handleRequest(
|
|
77
|
-
request: Request,
|
|
78
|
-
responseStatusCode: number,
|
|
79
|
-
responseHeaders: Headers,
|
|
80
|
-
routerContext: EntryContext,
|
|
81
|
-
_loadContext: AppLoadContext,
|
|
82
|
-
) {
|
|
83
|
-
let shellRendered = false
|
|
84
|
-
const userAgent = request.headers.get('user-agent')
|
|
85
|
-
|
|
86
|
-
const body = await renderToReadableStream(<ServerRouter context={routerContext} url={request.url} />, {
|
|
87
|
-
onError(error: unknown) {
|
|
88
|
-
responseStatusCode = 500
|
|
89
|
-
// Log streaming rendering errors from inside the shell. Don't log
|
|
90
|
-
// errors encountered during initial shell rendering since they'll
|
|
91
|
-
// reject and get logged in handleDocumentRequest.
|
|
92
|
-
if (shellRendered) {
|
|
93
|
-
console.error(error)
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
shellRendered = true
|
|
98
|
-
|
|
99
|
-
// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
|
|
100
|
-
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
|
|
101
|
-
if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
|
|
102
|
-
await body.allReady
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
responseHeaders.set('Content-Type', 'text/html')
|
|
106
|
-
return new Response(body, {
|
|
107
|
-
headers: responseHeaders,
|
|
108
|
-
status: responseStatusCode,
|
|
109
|
-
})
|
|
110
|
-
}
|
|
64
|
+
export { default } from 'virtual:netlify-server-entry'
|
|
111
65
|
```
|
|
112
66
|
|
|
113
|
-
|
|
67
|
+
> [!TIP]
|
|
68
|
+
>
|
|
69
|
+
> If you prefer to avoid a `@ts-ignore` here, add this to `vite-env.d.ts` in your project root (or anywhere you prefer):
|
|
70
|
+
>
|
|
71
|
+
> ```typescript
|
|
72
|
+
> declare module 'virtual:netlify-server-entry' {
|
|
73
|
+
> import type { ServerEntryModule } from 'react-router'
|
|
74
|
+
> const entry: ServerEntryModule
|
|
75
|
+
> export default entry
|
|
76
|
+
> }
|
|
77
|
+
> ```
|
|
114
78
|
|
|
115
79
|
Finally, if you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`,
|
|
116
80
|
you must exclude those paths to avoid conflicts with the generated React Router SSR handler:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EntryContext, AppLoadContext } from 'react-router';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge-compatible server entry using Web Streams instead of Node.js Streams.
|
|
5
|
+
* @see {@link https://reactrouter.com/api/framework-conventions/entry.server.tsx}
|
|
6
|
+
*
|
|
7
|
+
* This file was copied as-is from the React Router repository.
|
|
8
|
+
* @see {@link
|
|
9
|
+
* https://github.com/remix-run/react-router/blob/cb9a090316003988ff367bb2f2d1ef5bd03bd3af/integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx}
|
|
10
|
+
*
|
|
11
|
+
*
|
|
12
|
+
* @example Export this from your `app/entry.server.tsx` when using `edge: true`:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* export { default } from 'virtual:netlify-server-entry'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function handleRequest(request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, _loadContext: AppLoadContext): Promise<Response>;
|
|
19
|
+
|
|
20
|
+
export { handleRequest as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/entry.server.edge.tsx
|
|
2
|
+
import { ServerRouter } from "react-router";
|
|
3
|
+
import { isbot } from "isbot";
|
|
4
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
async function handleRequest(request, responseStatusCode, responseHeaders, routerContext, _loadContext) {
|
|
7
|
+
let shellRendered = false;
|
|
8
|
+
const userAgent = request.headers.get("user-agent");
|
|
9
|
+
const body = await renderToReadableStream(/* @__PURE__ */ jsx(ServerRouter, { context: routerContext, url: request.url }), {
|
|
10
|
+
onError(error) {
|
|
11
|
+
responseStatusCode = 500;
|
|
12
|
+
if (shellRendered) {
|
|
13
|
+
console.error(error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
shellRendered = true;
|
|
18
|
+
if (userAgent && isbot(userAgent) || routerContext.isSpaMode) {
|
|
19
|
+
await body.allReady;
|
|
20
|
+
}
|
|
21
|
+
responseHeaders.set("Content-Type", "text/html");
|
|
22
|
+
return new Response(body, {
|
|
23
|
+
headers: responseHeaders,
|
|
24
|
+
status: responseStatusCode
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
handleRequest as default
|
|
29
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -109,10 +109,11 @@ function createRequestHandler({
|
|
|
109
109
|
var import_promises = require("fs/promises");
|
|
110
110
|
var import_node_path = require("path");
|
|
111
111
|
var import_posix = require("path/posix");
|
|
112
|
+
var import_tinyglobby = require("tinyglobby");
|
|
112
113
|
|
|
113
114
|
// package.json
|
|
114
115
|
var name = "@netlify/vite-plugin-react-router";
|
|
115
|
-
var version = "2.1.
|
|
116
|
+
var version = "2.1.2";
|
|
116
117
|
|
|
117
118
|
// src/plugin.ts
|
|
118
119
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
@@ -121,6 +122,7 @@ var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
|
121
122
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
122
123
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
123
124
|
var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
|
|
125
|
+
var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
|
|
124
126
|
var toPosixPath = (path) => path.split(import_node_path.sep).join(import_posix.sep);
|
|
125
127
|
var FUNCTION_HANDLER = (
|
|
126
128
|
/* js */
|
|
@@ -179,9 +181,11 @@ function netlifyPlugin(options = {}) {
|
|
|
179
181
|
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
180
182
|
let resolvedConfig;
|
|
181
183
|
let isProductionSsrBuild = false;
|
|
184
|
+
let currentCommand;
|
|
182
185
|
return {
|
|
183
186
|
name: "vite-plugin-netlify-react-router",
|
|
184
187
|
config(config, { command, isSsrBuild }) {
|
|
188
|
+
currentCommand = command;
|
|
185
189
|
isProductionSsrBuild = isSsrBuild === true && command === "build";
|
|
186
190
|
if (isProductionSsrBuild) {
|
|
187
191
|
config.build ??= {};
|
|
@@ -212,10 +216,20 @@ function netlifyPlugin(options = {}) {
|
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
},
|
|
215
|
-
async resolveId(source) {
|
|
219
|
+
async resolveId(source, importer, options2) {
|
|
216
220
|
if (source === FUNCTION_HANDLER_MODULE_ID) {
|
|
217
221
|
return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
|
|
218
222
|
}
|
|
223
|
+
if (source === SERVER_ENTRY_MODULE_ID && edge) {
|
|
224
|
+
if (currentCommand === "serve") {
|
|
225
|
+
const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
|
|
226
|
+
if (!reactRouterDev) {
|
|
227
|
+
throw new Error("The @react-router/dev package is required for local development. Please install it.");
|
|
228
|
+
}
|
|
229
|
+
return (0, import_node_path.resolve)((0, import_node_path.dirname)(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
|
|
230
|
+
}
|
|
231
|
+
return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
|
|
232
|
+
}
|
|
219
233
|
},
|
|
220
234
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
221
235
|
load(id) {
|
|
@@ -232,12 +246,14 @@ function netlifyPlugin(options = {}) {
|
|
|
232
246
|
const handlerPath = (0, import_node_path.join)(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
233
247
|
if (edge) {
|
|
234
248
|
const clientDir = (0, import_node_path.join)(resolvedConfig.build.outDir, "..", "client");
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
249
|
+
const clientFiles = await (0, import_tinyglobby.glob)("**/*", {
|
|
250
|
+
cwd: clientDir,
|
|
251
|
+
// We can't exclude entire directories because there could be `foo/bar.baz` in the
|
|
252
|
+
// client dir and a `/foo` route handled by the server function.
|
|
253
|
+
onlyFiles: true,
|
|
254
|
+
dot: true
|
|
255
|
+
});
|
|
256
|
+
const excludedPath = ["/.netlify/*", ...clientFiles.map((file) => `/${file}`), ...additionalExcludedPaths];
|
|
241
257
|
const edgeFunctionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
242
258
|
await (0, import_promises.mkdir)(edgeFunctionsDir, { recursive: true });
|
|
243
259
|
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(edgeFunctionsDir, handlerPath));
|
package/dist/index.mjs
CHANGED
|
@@ -5,13 +5,14 @@ import {
|
|
|
5
5
|
import "./chunk-J5PMA2AP.mjs";
|
|
6
6
|
|
|
7
7
|
// src/plugin.ts
|
|
8
|
-
import { mkdir, writeFile
|
|
9
|
-
import { join, relative, sep } from "node:path";
|
|
8
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { sep as posixSep } from "node:path/posix";
|
|
11
|
+
import { glob } from "tinyglobby";
|
|
11
12
|
|
|
12
13
|
// package.json
|
|
13
14
|
var name = "@netlify/vite-plugin-react-router";
|
|
14
|
-
var version = "2.1.
|
|
15
|
+
var version = "2.1.2";
|
|
15
16
|
|
|
16
17
|
// src/plugin.ts
|
|
17
18
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
@@ -20,6 +21,7 @@ var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
|
20
21
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
21
22
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
22
23
|
var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
|
|
24
|
+
var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
|
|
23
25
|
var toPosixPath = (path) => path.split(sep).join(posixSep);
|
|
24
26
|
var FUNCTION_HANDLER = (
|
|
25
27
|
/* js */
|
|
@@ -78,9 +80,11 @@ function netlifyPlugin(options = {}) {
|
|
|
78
80
|
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
79
81
|
let resolvedConfig;
|
|
80
82
|
let isProductionSsrBuild = false;
|
|
83
|
+
let currentCommand;
|
|
81
84
|
return {
|
|
82
85
|
name: "vite-plugin-netlify-react-router",
|
|
83
86
|
config(config, { command, isSsrBuild }) {
|
|
87
|
+
currentCommand = command;
|
|
84
88
|
isProductionSsrBuild = isSsrBuild === true && command === "build";
|
|
85
89
|
if (isProductionSsrBuild) {
|
|
86
90
|
config.build ??= {};
|
|
@@ -111,10 +115,20 @@ function netlifyPlugin(options = {}) {
|
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
},
|
|
114
|
-
async resolveId(source) {
|
|
118
|
+
async resolveId(source, importer, options2) {
|
|
115
119
|
if (source === FUNCTION_HANDLER_MODULE_ID) {
|
|
116
120
|
return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
|
|
117
121
|
}
|
|
122
|
+
if (source === SERVER_ENTRY_MODULE_ID && edge) {
|
|
123
|
+
if (currentCommand === "serve") {
|
|
124
|
+
const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
|
|
125
|
+
if (!reactRouterDev) {
|
|
126
|
+
throw new Error("The @react-router/dev package is required for local development. Please install it.");
|
|
127
|
+
}
|
|
128
|
+
return resolve(dirname(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
|
|
129
|
+
}
|
|
130
|
+
return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
|
|
131
|
+
}
|
|
118
132
|
},
|
|
119
133
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
120
134
|
load(id) {
|
|
@@ -131,12 +145,14 @@ function netlifyPlugin(options = {}) {
|
|
|
131
145
|
const handlerPath = join(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
132
146
|
if (edge) {
|
|
133
147
|
const clientDir = join(resolvedConfig.build.outDir, "..", "client");
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
const clientFiles = await glob("**/*", {
|
|
149
|
+
cwd: clientDir,
|
|
150
|
+
// We can't exclude entire directories because there could be `foo/bar.baz` in the
|
|
151
|
+
// client dir and a `/foo` route handled by the server function.
|
|
152
|
+
onlyFiles: true,
|
|
153
|
+
dot: true
|
|
154
|
+
});
|
|
155
|
+
const excludedPath = ["/.netlify/*", ...clientFiles.map((file) => `/${file}`), ...additionalExcludedPaths];
|
|
140
156
|
const edgeFunctionsDir = join(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
141
157
|
await mkdir(edgeFunctionsDir, { recursive: true });
|
|
142
158
|
const relativeHandlerPath = toPosixPath(relative(edgeFunctionsDir, handlerPath));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/vite-plugin-react-router",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "React Router 7+ Vite plugin for Netlify",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"./edge": {
|
|
25
25
|
"types": "./dist/edge.d.mts",
|
|
26
26
|
"default": "./dist/edge.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./entry.server.edge": {
|
|
29
|
+
"types": "./dist/entry.server.edge.d.mts",
|
|
30
|
+
"default": "./dist/entry.server.edge.mjs"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"files": [
|
|
@@ -48,11 +52,12 @@
|
|
|
48
52
|
},
|
|
49
53
|
"homepage": "https://github.com/netlify/remix-compute#readme",
|
|
50
54
|
"dependencies": {
|
|
51
|
-
"
|
|
55
|
+
"@netlify/edge-functions": "^3.0.2",
|
|
56
|
+
"@netlify/functions": "^5.1.0",
|
|
57
|
+
"isbot": "^5.1.25",
|
|
58
|
+
"tinyglobby": "^0.2.10"
|
|
52
59
|
},
|
|
53
60
|
"devDependencies": {
|
|
54
|
-
"@netlify/edge-functions": "^2.11.0",
|
|
55
|
-
"@netlify/functions": "^3.1.10",
|
|
56
61
|
"@types/react": "^18.0.27",
|
|
57
62
|
"@types/react-dom": "^18.0.10",
|
|
58
63
|
"react": "^18.2.0",
|