@netlify/vite-plugin-react-router 2.0.0 → 2.1.0
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 +141 -9
- package/dist/chunk-3EFABROO.mjs +25 -0
- package/dist/chunk-J5PMA2AP.mjs +70 -0
- package/dist/edge.d.mts +29 -0
- package/dist/edge.mjs +24 -0
- package/dist/handler-4e_lz5jw.d.mts +18 -0
- package/dist/index.d.mts +22 -42
- package/dist/index.d.ts +31 -12
- package/dist/index.js +127 -15
- package/dist/index.mjs +78 -43
- package/dist/serverless.d.mts +31 -0
- package/dist/serverless.mjs +9 -0
- package/package.json +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [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
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **@netlify/vite-plugin-react-router:** add edge support ([#562](https://github.com/netlify/remix-compute/issues/562)) ([bbcb41e](https://github.com/netlify/remix-compute/commit/bbcb41e9d6ffdb5483759fbb94fb5258092adf87))
|
|
9
|
+
|
|
10
|
+
## [2.0.1](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.0.0...vite-plugin-react-router-v2.0.1) (2025-10-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* populate value for Netlify React Router context in dev ([#550](https://github.com/netlify/remix-compute/issues/550)) ([8d2c9fd](https://github.com/netlify/remix-compute/commit/8d2c9fd21090ca063d99a9e7094393932d870801))
|
|
16
|
+
|
|
3
17
|
## [2.0.0](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v1.0.1...vite-plugin-react-router-v2.0.0) (2025-10-16)
|
|
4
18
|
|
|
5
19
|
### ⚠ BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
# React Router Adapter for Netlify
|
|
2
2
|
|
|
3
|
-
The React Router Adapter for Netlify allows you to deploy your [React Router](https://reactrouter.com) app to
|
|
4
|
-
|
|
3
|
+
The React Router Adapter for Netlify allows you to deploy your [React Router](https://reactrouter.com) app to Netlify.
|
|
4
|
+
|
|
5
|
+
## How to use
|
|
5
6
|
|
|
6
7
|
To deploy a React Router 7+ site to Netlify, install this package:
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
```sh
|
|
10
|
+
npm install @netlify/vite-plugin-react-router
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It's also recommended (but not required) to use the
|
|
14
|
+
[Netlify Vite plugin](https://www.npmjs.com/package/@netlify/vite-plugin), which provides full Netlify platform
|
|
15
|
+
emulation directly in your local dev server:
|
|
9
16
|
|
|
10
17
|
```sh
|
|
11
|
-
npm install --save-dev @netlify/vite-plugin
|
|
18
|
+
npm install --save-dev @netlify/vite-plugin
|
|
12
19
|
```
|
|
13
20
|
|
|
14
21
|
and include the Netlify plugin in your `vite.config.ts`:
|
|
@@ -17,17 +24,128 @@ and include the Netlify plugin in your `vite.config.ts`:
|
|
|
17
24
|
import { reactRouter } from '@react-router/dev/vite'
|
|
18
25
|
import { defineConfig } from 'vite'
|
|
19
26
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
20
|
-
import
|
|
27
|
+
import netlifyReactRouter from '@netlify/vite-plugin-react-router' // <- add this
|
|
28
|
+
import netlify from '@netlify/vite-plugin' // <- add this (optional)
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
plugins: [
|
|
32
|
+
reactRouter(),
|
|
33
|
+
tsconfigPaths(),
|
|
34
|
+
netlifyReactRouter(), // <- add this
|
|
35
|
+
netlify(), // <- add this (optional)
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Your app is ready to [deploy to Netlify](https://docs.netlify.com/deploy/create-deploys/).
|
|
41
|
+
|
|
42
|
+
### Deploying to Edge Functions
|
|
43
|
+
|
|
44
|
+
By default, this plugin deploys your React Router app to
|
|
45
|
+
[Netlify Functions](https://docs.netlify.com/functions/overview/) (Node.js runtime). You can optionally deploy to
|
|
46
|
+
[Netlify Edge Functions](https://docs.netlify.com/edge-functions/overview/) (Deno runtime) instead.
|
|
47
|
+
|
|
48
|
+
First, toggle the `edge` option:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
export default defineConfig({
|
|
52
|
+
plugins: [
|
|
53
|
+
reactRouter(),
|
|
54
|
+
tsconfigPaths(),
|
|
55
|
+
netlifyReactRouter({ edge: true }), // <- deploy to Edge Functions
|
|
56
|
+
netlify(),
|
|
57
|
+
],
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file that uses web-standard APIs compatible with the
|
|
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.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import type { AppLoadContext, EntryContext } from 'react-router'
|
|
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
|
+
}
|
|
111
|
+
```
|
|
21
112
|
|
|
113
|
+
You may need to `npm install isbot` if you do not have this dependency.
|
|
114
|
+
|
|
115
|
+
Finally, if you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`,
|
|
116
|
+
you must exclude those paths to avoid conflicts with the generated React Router SSR handler:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
22
119
|
export default defineConfig({
|
|
23
120
|
plugins: [
|
|
24
121
|
reactRouter(),
|
|
25
122
|
tsconfigPaths(),
|
|
26
|
-
|
|
123
|
+
netlifyReactRouter({
|
|
124
|
+
edge: true,
|
|
125
|
+
excludedPaths: ['/ping', '/api/*', '/webhooks/*'],
|
|
126
|
+
}),
|
|
127
|
+
netlify(),
|
|
27
128
|
],
|
|
28
129
|
})
|
|
29
130
|
```
|
|
30
131
|
|
|
132
|
+
#### Moving back from Edge Functions to Functions
|
|
133
|
+
|
|
134
|
+
To switch from Edge Functions back to Functions, you must:
|
|
135
|
+
|
|
136
|
+
1. Remove the `edge: true` option from your `vite.config.ts`
|
|
137
|
+
2. **Delete the `app/entry.server.tsx` file** (React Router will use its default Node.js-compatible entry)
|
|
138
|
+
|
|
139
|
+
#### Edge runtime
|
|
140
|
+
|
|
141
|
+
Before deploying to Edge Functions, review the Netlify Edge Functions documentation for important details:
|
|
142
|
+
|
|
143
|
+
- [Runtime environment](https://docs.netlify.com/build/edge-functions/api/#runtime-environment) - Understand the Deno
|
|
144
|
+
runtime
|
|
145
|
+
- [Supported Web APIs](https://docs.netlify.com/build/edge-functions/api/#supported-web-apis) - Check which APIs are
|
|
146
|
+
available
|
|
147
|
+
- [Limitations](https://docs.netlify.com/build/edge-functions/limits/) - Be aware of resource limits and constraints
|
|
148
|
+
|
|
31
149
|
### Load context
|
|
32
150
|
|
|
33
151
|
This plugin automatically includes all
|
|
@@ -61,7 +179,8 @@ type-safe `RouterContextProvider`. Note that this requires requires v2.0.0+ of `
|
|
|
61
179
|
For example:
|
|
62
180
|
|
|
63
181
|
```tsx
|
|
64
|
-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
|
|
182
|
+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router/serverless'
|
|
183
|
+
// NOTE: if setting `edge: true`, import from /edge ^ instead here
|
|
65
184
|
import { useLoaderData } from 'react-router'
|
|
66
185
|
import type { Route } from './+types/example'
|
|
67
186
|
|
|
@@ -76,6 +195,12 @@ export default function Example() {
|
|
|
76
195
|
}
|
|
77
196
|
```
|
|
78
197
|
|
|
198
|
+
> [!IMPORTANT]
|
|
199
|
+
>
|
|
200
|
+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
|
|
201
|
+
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
|
|
202
|
+
> you).
|
|
203
|
+
|
|
79
204
|
### Middleware context
|
|
80
205
|
|
|
81
206
|
React Router introduced a stable middleware feature in 7.9.0.
|
|
@@ -85,10 +210,11 @@ To use middleware,
|
|
|
85
210
|
that this requires requires v2.0.0+ of `@netlify/vite-plugin-react-router`.
|
|
86
211
|
|
|
87
212
|
To access the [Netlify context](https://docs.netlify.com/build/functions/api/#netlify-specific-context-object)
|
|
88
|
-
specifically, you must import our `
|
|
213
|
+
specifically, you must import our `RouterContext` instance:
|
|
89
214
|
|
|
90
215
|
```tsx
|
|
91
|
-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
|
|
216
|
+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router/serverless'
|
|
217
|
+
// NOTE: if setting `edge: true`, import from /edge ^ instead here
|
|
92
218
|
|
|
93
219
|
import type { Route } from './+types/home'
|
|
94
220
|
|
|
@@ -103,3 +229,9 @@ export default function Home() {
|
|
|
103
229
|
return <h1>Hello world</h1>
|
|
104
230
|
}
|
|
105
231
|
```
|
|
232
|
+
|
|
233
|
+
> [!IMPORTANT]
|
|
234
|
+
>
|
|
235
|
+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
|
|
236
|
+
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
|
|
237
|
+
> you).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNetlifyRequestHandler,
|
|
3
|
+
createNetlifyRouterContext
|
|
4
|
+
} from "./chunk-J5PMA2AP.mjs";
|
|
5
|
+
|
|
6
|
+
// src/runtimes/netlify-functions.ts
|
|
7
|
+
var netlifyRouterContext = createNetlifyRouterContext();
|
|
8
|
+
function createRequestHandler({
|
|
9
|
+
build,
|
|
10
|
+
mode,
|
|
11
|
+
getLoadContext
|
|
12
|
+
}) {
|
|
13
|
+
return createNetlifyRequestHandler({
|
|
14
|
+
build,
|
|
15
|
+
mode,
|
|
16
|
+
getLoadContext,
|
|
17
|
+
netlifyRouterContext,
|
|
18
|
+
runtimeName: "Node"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
netlifyRouterContext,
|
|
24
|
+
createRequestHandler
|
|
25
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/lib/handler.ts
|
|
2
|
+
import {
|
|
3
|
+
RouterContextProvider,
|
|
4
|
+
createRequestHandler as createReactRouterRequestHandler
|
|
5
|
+
} from "react-router";
|
|
6
|
+
function createNetlifyRequestHandler({
|
|
7
|
+
build,
|
|
8
|
+
mode,
|
|
9
|
+
getLoadContext,
|
|
10
|
+
netlifyRouterContext,
|
|
11
|
+
runtimeName
|
|
12
|
+
}) {
|
|
13
|
+
const reactRouterHandler = createReactRouterRequestHandler(build, mode);
|
|
14
|
+
return async (request, netlifyContext) => {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
console.log(`[${request.method}] ${request.url}`);
|
|
17
|
+
try {
|
|
18
|
+
const getDefaultReactRouterContext = () => {
|
|
19
|
+
const ctx = new RouterContextProvider();
|
|
20
|
+
ctx.set(netlifyRouterContext, netlifyContext);
|
|
21
|
+
Object.assign(ctx, netlifyContext);
|
|
22
|
+
return ctx;
|
|
23
|
+
};
|
|
24
|
+
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
25
|
+
const response = await reactRouterHandler(request, reactRouterContext);
|
|
26
|
+
response.headers.set("x-nf-runtime", runtimeName);
|
|
27
|
+
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
28
|
+
return response;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(error);
|
|
31
|
+
return new Response("Internal Error", { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/lib/context.ts
|
|
37
|
+
import { createContext } from "react-router";
|
|
38
|
+
function createNetlifyRouterContext() {
|
|
39
|
+
return createContext(
|
|
40
|
+
new Proxy(
|
|
41
|
+
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
|
|
42
|
+
{},
|
|
43
|
+
{
|
|
44
|
+
get(_target, prop, receiver) {
|
|
45
|
+
return Reflect.get(Netlify.context ?? {}, prop, receiver);
|
|
46
|
+
},
|
|
47
|
+
set(_target, prop, value, receiver) {
|
|
48
|
+
return Reflect.set(Netlify.context ?? {}, prop, value, receiver);
|
|
49
|
+
},
|
|
50
|
+
has(_target, prop) {
|
|
51
|
+
return Reflect.has(Netlify.context ?? {}, prop);
|
|
52
|
+
},
|
|
53
|
+
deleteProperty(_target, prop) {
|
|
54
|
+
return Reflect.deleteProperty(Netlify.context ?? {}, prop);
|
|
55
|
+
},
|
|
56
|
+
ownKeys(_target) {
|
|
57
|
+
return Reflect.ownKeys(Netlify.context ?? {});
|
|
58
|
+
},
|
|
59
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
60
|
+
return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
createNetlifyRequestHandler,
|
|
69
|
+
createNetlifyRouterContext
|
|
70
|
+
};
|
package/dist/edge.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as react_router from 'react-router';
|
|
2
|
+
import { ServerBuild } from 'react-router';
|
|
3
|
+
import { Context } from '@netlify/edge-functions';
|
|
4
|
+
import { G as GetLoadContextFunction, R as RequestHandler } from './handler-4e_lz5jw.mjs';
|
|
5
|
+
|
|
6
|
+
declare module 'react-router' {
|
|
7
|
+
interface AppLoadContext extends Context {
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* An instance of `RouterContext` providing access to
|
|
12
|
+
* [Netlify request context]{@link https://docs.netlify.com/build/edge-functions/api/#netlify-specific-context-object}
|
|
13
|
+
*
|
|
14
|
+
* @example context.get(netlifyRouterContext).geo?.country?.name
|
|
15
|
+
*/
|
|
16
|
+
declare const netlifyRouterContext: react_router.RouterContext<Partial<Context>>;
|
|
17
|
+
/**
|
|
18
|
+
* Given a build and a callback to get the base loader context, this returns
|
|
19
|
+
* a Netlify Edge Function handler (https://docs.netlify.com/edge-functions/overview/) which renders the
|
|
20
|
+
* requested path. The loader context in this lifecycle will contain the Netlify Edge Functions context
|
|
21
|
+
* fields merged in.
|
|
22
|
+
*/
|
|
23
|
+
declare function createRequestHandler({ build, mode, getLoadContext, }: {
|
|
24
|
+
build: ServerBuild;
|
|
25
|
+
mode?: string;
|
|
26
|
+
getLoadContext?: GetLoadContextFunction<Context>;
|
|
27
|
+
}): RequestHandler<Context>;
|
|
28
|
+
|
|
29
|
+
export { createRequestHandler, netlifyRouterContext };
|
package/dist/edge.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNetlifyRequestHandler,
|
|
3
|
+
createNetlifyRouterContext
|
|
4
|
+
} from "./chunk-J5PMA2AP.mjs";
|
|
5
|
+
|
|
6
|
+
// src/runtimes/netlify-edge-functions.ts
|
|
7
|
+
var netlifyRouterContext = createNetlifyRouterContext();
|
|
8
|
+
function createRequestHandler({
|
|
9
|
+
build,
|
|
10
|
+
mode,
|
|
11
|
+
getLoadContext
|
|
12
|
+
}) {
|
|
13
|
+
return createNetlifyRequestHandler({
|
|
14
|
+
build,
|
|
15
|
+
mode,
|
|
16
|
+
getLoadContext,
|
|
17
|
+
netlifyRouterContext,
|
|
18
|
+
runtimeName: "edge"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
createRequestHandler,
|
|
23
|
+
netlifyRouterContext
|
|
24
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RouterContextProvider, AppLoadContext } from 'react-router';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A function that returns the value to use as `context` in route `loader` and `action` functions.
|
|
5
|
+
*
|
|
6
|
+
* You can think of this as an escape hatch that allows you to pass environment/platform-specific
|
|
7
|
+
* values through to your loader/action.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: v7.9.0 introduced a breaking change when the user opts in to `future.v8_middleware`. This
|
|
10
|
+
* requires returning an instance of `RouterContextProvider` instead of a plain object. We have a
|
|
11
|
+
* peer dependency on >=7.9.0 so we can safely *import* these, but we cannot assume the user has
|
|
12
|
+
* opted in to the flag.
|
|
13
|
+
*/
|
|
14
|
+
type GetLoadContextFunction<TNetlifyContext> = ((request: Request, context: TNetlifyContext) => Promise<RouterContextProvider> | RouterContextProvider) | ((request: Request, context: TNetlifyContext) => Promise<AppLoadContext> | AppLoadContext);
|
|
15
|
+
|
|
16
|
+
type RequestHandler<TNetlifyContext> = (request: Request, context: TNetlifyContext) => Promise<Response>;
|
|
17
|
+
|
|
18
|
+
export type { GetLoadContextFunction as G, RequestHandler as R };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,46 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
import { AppLoadContext, RouterContextProvider, ServerBuild } from 'react-router';
|
|
3
|
-
import { Context } from '@netlify/functions';
|
|
1
|
+
export { GetLoadContextFunction, RequestHandler, createRequestHandler, netlifyRouterContext } from './serverless.mjs';
|
|
4
2
|
import { Plugin } from 'vite';
|
|
3
|
+
import 'react-router';
|
|
4
|
+
import '@netlify/functions';
|
|
5
|
+
import './handler-4e_lz5jw.mjs';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface NetlifyPluginOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Deploy to Netlify Edge Functions instead of Netlify Functions.
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
edge?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Paths to exclude from being handled by the React Router handler.
|
|
15
|
+
*
|
|
16
|
+
* @IMPORTANT If you have opted in to edge rendering with `edge: true` and you have your own Netlify
|
|
17
|
+
* Functions running on custom `path`s, you must exclude those paths here to avoid conflicts.
|
|
18
|
+
*
|
|
19
|
+
* @type {URLPattern[]}
|
|
20
|
+
* @default []
|
|
21
|
+
*/
|
|
22
|
+
excludedPaths?: string[];
|
|
9
23
|
}
|
|
10
|
-
|
|
11
|
-
* A function that returns the value to use as `context` in route `loader` and `action` functions.
|
|
12
|
-
*
|
|
13
|
-
* You can think of this as an escape hatch that allows you to pass environment/platform-specific
|
|
14
|
-
* values through to your loader/action.
|
|
15
|
-
*
|
|
16
|
-
* NOTE: v7.9.0 introduced a breaking change when the user opts in to `future.v8_middleware`. This
|
|
17
|
-
* requires returning an instance of `RouterContextProvider` instead of a plain object. We have a
|
|
18
|
-
* peer dependency on >=7.9.0 so we can safely *import* these, but we cannot assume the user has
|
|
19
|
-
* opted in to the flag.
|
|
20
|
-
*/
|
|
21
|
-
type GetLoadContextFunction = GetLoadContextFunction_V7 | GetLoadContextFunction_V8;
|
|
22
|
-
type GetLoadContextFunction_V7 = (request: Request, context: Context) => Promise<AppLoadContext> | AppLoadContext;
|
|
23
|
-
type GetLoadContextFunction_V8 = (request: Request, context: Context) => Promise<RouterContextProvider> | RouterContextProvider;
|
|
24
|
-
type RequestHandler = (request: Request, context: Context) => Promise<Response>;
|
|
25
|
-
/**
|
|
26
|
-
* An instance of `ReactContextProvider` providing access to
|
|
27
|
-
* [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
|
|
28
|
-
*
|
|
29
|
-
* @example context.get(netlifyRouterContext).geo?.country?.name
|
|
30
|
-
*/
|
|
31
|
-
declare const netlifyRouterContext: react_router.RouterContext<Context>;
|
|
32
|
-
/**
|
|
33
|
-
* Given a build and a callback to get the base loader context, this returns
|
|
34
|
-
* a Netlify Function handler (https://docs.netlify.com/functions/overview/) which renders the
|
|
35
|
-
* requested path. The loader context in this lifecycle will contain the Netlify Functions context
|
|
36
|
-
* fields merged in.
|
|
37
|
-
*/
|
|
38
|
-
declare function createRequestHandler({ build, mode, getLoadContext, }: {
|
|
39
|
-
build: ServerBuild;
|
|
40
|
-
mode?: string;
|
|
41
|
-
getLoadContext?: GetLoadContextFunction;
|
|
42
|
-
}): RequestHandler;
|
|
24
|
+
declare function netlifyPlugin(options?: NetlifyPluginOptions): Plugin;
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export { type GetLoadContextFunction, type RequestHandler, createRequestHandler, netlifyPlugin as default, netlifyRouterContext };
|
|
26
|
+
export { netlifyPlugin as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import * as react_router from 'react-router';
|
|
2
|
-
import {
|
|
2
|
+
import { RouterContextProvider, AppLoadContext, ServerBuild } from 'react-router';
|
|
3
3
|
import { Context } from '@netlify/functions';
|
|
4
4
|
import { Plugin } from 'vite';
|
|
5
5
|
|
|
6
|
-
declare module 'react-router' {
|
|
7
|
-
interface AppLoadContext extends Context {
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
6
|
/**
|
|
11
7
|
* A function that returns the value to use as `context` in route `loader` and `action` functions.
|
|
12
8
|
*
|
|
@@ -18,17 +14,23 @@ declare module 'react-router' {
|
|
|
18
14
|
* peer dependency on >=7.9.0 so we can safely *import* these, but we cannot assume the user has
|
|
19
15
|
* opted in to the flag.
|
|
20
16
|
*/
|
|
21
|
-
type GetLoadContextFunction =
|
|
22
|
-
|
|
23
|
-
type
|
|
24
|
-
|
|
17
|
+
type GetLoadContextFunction$1<TNetlifyContext> = ((request: Request, context: TNetlifyContext) => Promise<RouterContextProvider> | RouterContextProvider) | ((request: Request, context: TNetlifyContext) => Promise<AppLoadContext> | AppLoadContext);
|
|
18
|
+
|
|
19
|
+
type RequestHandler$1<TNetlifyContext> = (request: Request, context: TNetlifyContext) => Promise<Response>;
|
|
20
|
+
|
|
21
|
+
declare module 'react-router' {
|
|
22
|
+
interface AppLoadContext extends Context {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
type GetLoadContextFunction = GetLoadContextFunction$1<Context>;
|
|
26
|
+
type RequestHandler = RequestHandler$1<Context>;
|
|
25
27
|
/**
|
|
26
|
-
* An instance of `
|
|
28
|
+
* An instance of `RouterContext` providing access to
|
|
27
29
|
* [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
|
|
28
30
|
*
|
|
29
31
|
* @example context.get(netlifyRouterContext).geo?.country?.name
|
|
30
32
|
*/
|
|
31
|
-
declare const netlifyRouterContext: react_router.RouterContext<Context
|
|
33
|
+
declare const netlifyRouterContext: react_router.RouterContext<Partial<Context>>;
|
|
32
34
|
/**
|
|
33
35
|
* Given a build and a callback to get the base loader context, this returns
|
|
34
36
|
* a Netlify Function handler (https://docs.netlify.com/functions/overview/) which renders the
|
|
@@ -41,6 +43,23 @@ declare function createRequestHandler({ build, mode, getLoadContext, }: {
|
|
|
41
43
|
getLoadContext?: GetLoadContextFunction;
|
|
42
44
|
}): RequestHandler;
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
interface NetlifyPluginOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Deploy to Netlify Edge Functions instead of Netlify Functions.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
edge?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Paths to exclude from being handled by the React Router handler.
|
|
54
|
+
*
|
|
55
|
+
* @IMPORTANT If you have opted in to edge rendering with `edge: true` and you have your own Netlify
|
|
56
|
+
* Functions running on custom `path`s, you must exclude those paths here to avoid conflicts.
|
|
57
|
+
*
|
|
58
|
+
* @type {URLPattern[]}
|
|
59
|
+
* @default []
|
|
60
|
+
*/
|
|
61
|
+
excludedPaths?: string[];
|
|
62
|
+
}
|
|
63
|
+
declare function netlifyPlugin(options?: NetlifyPluginOptions): Plugin;
|
|
45
64
|
|
|
46
65
|
export { type GetLoadContextFunction, type RequestHandler, createRequestHandler, netlifyPlugin as default, netlifyRouterContext };
|
package/dist/index.js
CHANGED
|
@@ -26,13 +26,14 @@ __export(index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
29
|
-
// src/
|
|
29
|
+
// src/lib/handler.ts
|
|
30
30
|
var import_react_router = require("react-router");
|
|
31
|
-
|
|
32
|
-
function createRequestHandler({
|
|
31
|
+
function createNetlifyRequestHandler({
|
|
33
32
|
build,
|
|
34
33
|
mode,
|
|
35
|
-
getLoadContext
|
|
34
|
+
getLoadContext,
|
|
35
|
+
netlifyRouterContext: netlifyRouterContext2,
|
|
36
|
+
runtimeName
|
|
36
37
|
}) {
|
|
37
38
|
const reactRouterHandler = (0, import_react_router.createRequestHandler)(build, mode);
|
|
38
39
|
return async (request, netlifyContext) => {
|
|
@@ -41,13 +42,13 @@ function createRequestHandler({
|
|
|
41
42
|
try {
|
|
42
43
|
const getDefaultReactRouterContext = () => {
|
|
43
44
|
const ctx = new import_react_router.RouterContextProvider();
|
|
44
|
-
ctx.set(
|
|
45
|
+
ctx.set(netlifyRouterContext2, netlifyContext);
|
|
45
46
|
Object.assign(ctx, netlifyContext);
|
|
46
47
|
return ctx;
|
|
47
48
|
};
|
|
48
49
|
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
49
50
|
const response = await reactRouterHandler(request, reactRouterContext);
|
|
50
|
-
response.headers.set("x-nf-runtime",
|
|
51
|
+
response.headers.set("x-nf-runtime", runtimeName);
|
|
51
52
|
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
52
53
|
return response;
|
|
53
54
|
} catch (error) {
|
|
@@ -57,6 +58,53 @@ function createRequestHandler({
|
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// src/lib/context.ts
|
|
62
|
+
var import_react_router2 = require("react-router");
|
|
63
|
+
function createNetlifyRouterContext() {
|
|
64
|
+
return (0, import_react_router2.createContext)(
|
|
65
|
+
new Proxy(
|
|
66
|
+
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
|
|
67
|
+
{},
|
|
68
|
+
{
|
|
69
|
+
get(_target, prop, receiver) {
|
|
70
|
+
return Reflect.get(Netlify.context ?? {}, prop, receiver);
|
|
71
|
+
},
|
|
72
|
+
set(_target, prop, value, receiver) {
|
|
73
|
+
return Reflect.set(Netlify.context ?? {}, prop, value, receiver);
|
|
74
|
+
},
|
|
75
|
+
has(_target, prop) {
|
|
76
|
+
return Reflect.has(Netlify.context ?? {}, prop);
|
|
77
|
+
},
|
|
78
|
+
deleteProperty(_target, prop) {
|
|
79
|
+
return Reflect.deleteProperty(Netlify.context ?? {}, prop);
|
|
80
|
+
},
|
|
81
|
+
ownKeys(_target) {
|
|
82
|
+
return Reflect.ownKeys(Netlify.context ?? {});
|
|
83
|
+
},
|
|
84
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
85
|
+
return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/runtimes/netlify-functions.ts
|
|
93
|
+
var netlifyRouterContext = createNetlifyRouterContext();
|
|
94
|
+
function createRequestHandler({
|
|
95
|
+
build,
|
|
96
|
+
mode,
|
|
97
|
+
getLoadContext
|
|
98
|
+
}) {
|
|
99
|
+
return createNetlifyRequestHandler({
|
|
100
|
+
build,
|
|
101
|
+
mode,
|
|
102
|
+
getLoadContext,
|
|
103
|
+
netlifyRouterContext,
|
|
104
|
+
runtimeName: "Node"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
// src/plugin.ts
|
|
61
109
|
var import_promises = require("fs/promises");
|
|
62
110
|
var import_node_path = require("path");
|
|
@@ -64,10 +112,11 @@ var import_posix = require("path/posix");
|
|
|
64
112
|
|
|
65
113
|
// package.json
|
|
66
114
|
var name = "@netlify/vite-plugin-react-router";
|
|
67
|
-
var version = "2.
|
|
115
|
+
var version = "2.1.0";
|
|
68
116
|
|
|
69
117
|
// src/plugin.ts
|
|
70
118
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
119
|
+
var NETLIFY_EDGE_FUNCTIONS_DIR = ".netlify/v1/edge-functions";
|
|
71
120
|
var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
72
121
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
73
122
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
@@ -76,14 +125,24 @@ var toPosixPath = (path) => path.split(import_node_path.sep).join(import_posix.s
|
|
|
76
125
|
var FUNCTION_HANDLER = (
|
|
77
126
|
/* js */
|
|
78
127
|
`
|
|
79
|
-
import { createRequestHandler } from "@netlify/vite-plugin-react-router";
|
|
128
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/serverless";
|
|
80
129
|
import * as build from "virtual:react-router/server-build";
|
|
81
130
|
export default createRequestHandler({
|
|
82
131
|
build,
|
|
83
132
|
});
|
|
84
133
|
`
|
|
85
134
|
);
|
|
86
|
-
|
|
135
|
+
var EDGE_FUNCTION_HANDLER = (
|
|
136
|
+
/* js */
|
|
137
|
+
`
|
|
138
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/edge";
|
|
139
|
+
import * as build from "virtual:react-router/server-build";
|
|
140
|
+
export default createRequestHandler({
|
|
141
|
+
build,
|
|
142
|
+
});
|
|
143
|
+
`
|
|
144
|
+
);
|
|
145
|
+
function generateNetlifyFunction(handlerPath, excludedPath) {
|
|
87
146
|
return (
|
|
88
147
|
/* js */
|
|
89
148
|
`
|
|
@@ -93,12 +152,31 @@ function generateNetlifyFunction(handlerPath) {
|
|
|
93
152
|
name: "React Router server handler",
|
|
94
153
|
generator: "${name}@${version}",
|
|
95
154
|
path: "/*",
|
|
155
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
96
156
|
preferStatic: true,
|
|
97
157
|
};
|
|
98
158
|
`
|
|
99
159
|
);
|
|
100
160
|
}
|
|
101
|
-
function
|
|
161
|
+
function generateEdgeFunction(handlerPath, excludedPath) {
|
|
162
|
+
return (
|
|
163
|
+
/* js */
|
|
164
|
+
`
|
|
165
|
+
export { default } from "${handlerPath}";
|
|
166
|
+
|
|
167
|
+
export const config = {
|
|
168
|
+
name: "React Router server handler",
|
|
169
|
+
generator: "${name}@${version}",
|
|
170
|
+
cache: "manual",
|
|
171
|
+
path: "/*",
|
|
172
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
173
|
+
};
|
|
174
|
+
`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function netlifyPlugin(options = {}) {
|
|
178
|
+
const edge = options.edge ?? false;
|
|
179
|
+
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
102
180
|
let resolvedConfig;
|
|
103
181
|
let isProductionSsrBuild = false;
|
|
104
182
|
return {
|
|
@@ -119,6 +197,19 @@ function netlifyPlugin() {
|
|
|
119
197
|
config.build.rollupOptions.output = {};
|
|
120
198
|
}
|
|
121
199
|
config.build.rollupOptions.output.entryFileNames = "[name].js";
|
|
200
|
+
if (edge) {
|
|
201
|
+
config.ssr = {
|
|
202
|
+
...config.ssr,
|
|
203
|
+
target: "webworker",
|
|
204
|
+
// Bundle everything except Node.js built-ins (which are supported but must use the `node:` prefix):
|
|
205
|
+
// https://docs.netlify.com/build/edge-functions/api/#runtime-environment
|
|
206
|
+
noExternal: /^(?!node:).*$/,
|
|
207
|
+
resolve: {
|
|
208
|
+
...config.resolve,
|
|
209
|
+
conditions: ["worker", "deno", "browser"]
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
122
213
|
}
|
|
123
214
|
},
|
|
124
215
|
async resolveId(source) {
|
|
@@ -129,7 +220,7 @@ function netlifyPlugin() {
|
|
|
129
220
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
130
221
|
load(id) {
|
|
131
222
|
if (id === RESOLVED_FUNCTION_HANDLER_MODULE_ID) {
|
|
132
|
-
return FUNCTION_HANDLER;
|
|
223
|
+
return edge ? EDGE_FUNCTION_HANDLER : FUNCTION_HANDLER;
|
|
133
224
|
}
|
|
134
225
|
},
|
|
135
226
|
async configResolved(config) {
|
|
@@ -138,11 +229,32 @@ function netlifyPlugin() {
|
|
|
138
229
|
// See https://rollupjs.org/plugin-development/#writebundle.
|
|
139
230
|
async writeBundle() {
|
|
140
231
|
if (isProductionSsrBuild) {
|
|
141
|
-
const functionsDirectory = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
142
|
-
await (0, import_promises.mkdir)(functionsDirectory, { recursive: true });
|
|
143
232
|
const handlerPath = (0, import_node_path.join)(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
144
|
-
|
|
145
|
-
|
|
233
|
+
if (edge) {
|
|
234
|
+
const clientDir = (0, import_node_path.join)(resolvedConfig.build.outDir, "..", "client");
|
|
235
|
+
const entries = await (0, import_promises.readdir)(clientDir, { withFileTypes: true });
|
|
236
|
+
const excludedPath = [
|
|
237
|
+
"/.netlify/*",
|
|
238
|
+
...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
|
|
239
|
+
...additionalExcludedPaths
|
|
240
|
+
];
|
|
241
|
+
const edgeFunctionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
242
|
+
await (0, import_promises.mkdir)(edgeFunctionsDir, { recursive: true });
|
|
243
|
+
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(edgeFunctionsDir, handlerPath));
|
|
244
|
+
await (0, import_promises.writeFile)(
|
|
245
|
+
(0, import_node_path.join)(edgeFunctionsDir, FUNCTION_FILENAME),
|
|
246
|
+
generateEdgeFunction(relativeHandlerPath, excludedPath)
|
|
247
|
+
);
|
|
248
|
+
} else {
|
|
249
|
+
const functionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
250
|
+
await (0, import_promises.mkdir)(functionsDir, { recursive: true });
|
|
251
|
+
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(functionsDir, handlerPath));
|
|
252
|
+
const excludedPath = ["/.netlify/*", ...additionalExcludedPaths];
|
|
253
|
+
await (0, import_promises.writeFile)(
|
|
254
|
+
(0, import_node_path.join)(functionsDir, FUNCTION_FILENAME),
|
|
255
|
+
generateNetlifyFunction(relativeHandlerPath, excludedPath)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
146
258
|
}
|
|
147
259
|
}
|
|
148
260
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,49 +1,21 @@
|
|
|
1
|
-
// src/server.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var netlifyRouterContext = createContext();
|
|
8
|
-
function createRequestHandler({
|
|
9
|
-
build,
|
|
10
|
-
mode,
|
|
11
|
-
getLoadContext
|
|
12
|
-
}) {
|
|
13
|
-
const reactRouterHandler = createReactRouterRequestHandler(build, mode);
|
|
14
|
-
return async (request, netlifyContext) => {
|
|
15
|
-
const start = Date.now();
|
|
16
|
-
console.log(`[${request.method}] ${request.url}`);
|
|
17
|
-
try {
|
|
18
|
-
const getDefaultReactRouterContext = () => {
|
|
19
|
-
const ctx = new RouterContextProvider();
|
|
20
|
-
ctx.set(netlifyRouterContext, netlifyContext);
|
|
21
|
-
Object.assign(ctx, netlifyContext);
|
|
22
|
-
return ctx;
|
|
23
|
-
};
|
|
24
|
-
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
25
|
-
const response = await reactRouterHandler(request, reactRouterContext);
|
|
26
|
-
response.headers.set("x-nf-runtime", "Node");
|
|
27
|
-
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
28
|
-
return response;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error(error);
|
|
31
|
-
return new Response("Internal Error", { status: 500 });
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
2
|
+
createRequestHandler,
|
|
3
|
+
netlifyRouterContext
|
|
4
|
+
} from "./chunk-3EFABROO.mjs";
|
|
5
|
+
import "./chunk-J5PMA2AP.mjs";
|
|
35
6
|
|
|
36
7
|
// src/plugin.ts
|
|
37
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { mkdir, writeFile, readdir } from "node:fs/promises";
|
|
38
9
|
import { join, relative, sep } from "node:path";
|
|
39
10
|
import { sep as posixSep } from "node:path/posix";
|
|
40
11
|
|
|
41
12
|
// package.json
|
|
42
13
|
var name = "@netlify/vite-plugin-react-router";
|
|
43
|
-
var version = "2.
|
|
14
|
+
var version = "2.1.0";
|
|
44
15
|
|
|
45
16
|
// src/plugin.ts
|
|
46
17
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
18
|
+
var NETLIFY_EDGE_FUNCTIONS_DIR = ".netlify/v1/edge-functions";
|
|
47
19
|
var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
48
20
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
49
21
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
@@ -52,14 +24,24 @@ var toPosixPath = (path) => path.split(sep).join(posixSep);
|
|
|
52
24
|
var FUNCTION_HANDLER = (
|
|
53
25
|
/* js */
|
|
54
26
|
`
|
|
55
|
-
import { createRequestHandler } from "@netlify/vite-plugin-react-router";
|
|
27
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/serverless";
|
|
28
|
+
import * as build from "virtual:react-router/server-build";
|
|
29
|
+
export default createRequestHandler({
|
|
30
|
+
build,
|
|
31
|
+
});
|
|
32
|
+
`
|
|
33
|
+
);
|
|
34
|
+
var EDGE_FUNCTION_HANDLER = (
|
|
35
|
+
/* js */
|
|
36
|
+
`
|
|
37
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/edge";
|
|
56
38
|
import * as build from "virtual:react-router/server-build";
|
|
57
39
|
export default createRequestHandler({
|
|
58
40
|
build,
|
|
59
41
|
});
|
|
60
42
|
`
|
|
61
43
|
);
|
|
62
|
-
function generateNetlifyFunction(handlerPath) {
|
|
44
|
+
function generateNetlifyFunction(handlerPath, excludedPath) {
|
|
63
45
|
return (
|
|
64
46
|
/* js */
|
|
65
47
|
`
|
|
@@ -69,12 +51,31 @@ function generateNetlifyFunction(handlerPath) {
|
|
|
69
51
|
name: "React Router server handler",
|
|
70
52
|
generator: "${name}@${version}",
|
|
71
53
|
path: "/*",
|
|
54
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
72
55
|
preferStatic: true,
|
|
73
56
|
};
|
|
74
57
|
`
|
|
75
58
|
);
|
|
76
59
|
}
|
|
77
|
-
function
|
|
60
|
+
function generateEdgeFunction(handlerPath, excludedPath) {
|
|
61
|
+
return (
|
|
62
|
+
/* js */
|
|
63
|
+
`
|
|
64
|
+
export { default } from "${handlerPath}";
|
|
65
|
+
|
|
66
|
+
export const config = {
|
|
67
|
+
name: "React Router server handler",
|
|
68
|
+
generator: "${name}@${version}",
|
|
69
|
+
cache: "manual",
|
|
70
|
+
path: "/*",
|
|
71
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
72
|
+
};
|
|
73
|
+
`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function netlifyPlugin(options = {}) {
|
|
77
|
+
const edge = options.edge ?? false;
|
|
78
|
+
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
78
79
|
let resolvedConfig;
|
|
79
80
|
let isProductionSsrBuild = false;
|
|
80
81
|
return {
|
|
@@ -95,6 +96,19 @@ function netlifyPlugin() {
|
|
|
95
96
|
config.build.rollupOptions.output = {};
|
|
96
97
|
}
|
|
97
98
|
config.build.rollupOptions.output.entryFileNames = "[name].js";
|
|
99
|
+
if (edge) {
|
|
100
|
+
config.ssr = {
|
|
101
|
+
...config.ssr,
|
|
102
|
+
target: "webworker",
|
|
103
|
+
// Bundle everything except Node.js built-ins (which are supported but must use the `node:` prefix):
|
|
104
|
+
// https://docs.netlify.com/build/edge-functions/api/#runtime-environment
|
|
105
|
+
noExternal: /^(?!node:).*$/,
|
|
106
|
+
resolve: {
|
|
107
|
+
...config.resolve,
|
|
108
|
+
conditions: ["worker", "deno", "browser"]
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
98
112
|
}
|
|
99
113
|
},
|
|
100
114
|
async resolveId(source) {
|
|
@@ -105,7 +119,7 @@ function netlifyPlugin() {
|
|
|
105
119
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
106
120
|
load(id) {
|
|
107
121
|
if (id === RESOLVED_FUNCTION_HANDLER_MODULE_ID) {
|
|
108
|
-
return FUNCTION_HANDLER;
|
|
122
|
+
return edge ? EDGE_FUNCTION_HANDLER : FUNCTION_HANDLER;
|
|
109
123
|
}
|
|
110
124
|
},
|
|
111
125
|
async configResolved(config) {
|
|
@@ -114,11 +128,32 @@ function netlifyPlugin() {
|
|
|
114
128
|
// See https://rollupjs.org/plugin-development/#writebundle.
|
|
115
129
|
async writeBundle() {
|
|
116
130
|
if (isProductionSsrBuild) {
|
|
117
|
-
const functionsDirectory = join(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
118
|
-
await mkdir(functionsDirectory, { recursive: true });
|
|
119
131
|
const handlerPath = join(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
120
|
-
|
|
121
|
-
|
|
132
|
+
if (edge) {
|
|
133
|
+
const clientDir = join(resolvedConfig.build.outDir, "..", "client");
|
|
134
|
+
const entries = await readdir(clientDir, { withFileTypes: true });
|
|
135
|
+
const excludedPath = [
|
|
136
|
+
"/.netlify/*",
|
|
137
|
+
...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
|
|
138
|
+
...additionalExcludedPaths
|
|
139
|
+
];
|
|
140
|
+
const edgeFunctionsDir = join(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
141
|
+
await mkdir(edgeFunctionsDir, { recursive: true });
|
|
142
|
+
const relativeHandlerPath = toPosixPath(relative(edgeFunctionsDir, handlerPath));
|
|
143
|
+
await writeFile(
|
|
144
|
+
join(edgeFunctionsDir, FUNCTION_FILENAME),
|
|
145
|
+
generateEdgeFunction(relativeHandlerPath, excludedPath)
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
const functionsDir = join(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
149
|
+
await mkdir(functionsDir, { recursive: true });
|
|
150
|
+
const relativeHandlerPath = toPosixPath(relative(functionsDir, handlerPath));
|
|
151
|
+
const excludedPath = ["/.netlify/*", ...additionalExcludedPaths];
|
|
152
|
+
await writeFile(
|
|
153
|
+
join(functionsDir, FUNCTION_FILENAME),
|
|
154
|
+
generateNetlifyFunction(relativeHandlerPath, excludedPath)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
122
157
|
}
|
|
123
158
|
}
|
|
124
159
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react_router from 'react-router';
|
|
2
|
+
import { ServerBuild } from 'react-router';
|
|
3
|
+
import { Context } from '@netlify/functions';
|
|
4
|
+
import { G as GetLoadContextFunction$1, R as RequestHandler$1 } from './handler-4e_lz5jw.mjs';
|
|
5
|
+
|
|
6
|
+
declare module 'react-router' {
|
|
7
|
+
interface AppLoadContext extends Context {
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
type GetLoadContextFunction = GetLoadContextFunction$1<Context>;
|
|
11
|
+
type RequestHandler = RequestHandler$1<Context>;
|
|
12
|
+
/**
|
|
13
|
+
* An instance of `RouterContext` providing access to
|
|
14
|
+
* [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
|
|
15
|
+
*
|
|
16
|
+
* @example context.get(netlifyRouterContext).geo?.country?.name
|
|
17
|
+
*/
|
|
18
|
+
declare const netlifyRouterContext: react_router.RouterContext<Partial<Context>>;
|
|
19
|
+
/**
|
|
20
|
+
* Given a build and a callback to get the base loader context, this returns
|
|
21
|
+
* a Netlify Function handler (https://docs.netlify.com/functions/overview/) which renders the
|
|
22
|
+
* requested path. The loader context in this lifecycle will contain the Netlify Functions context
|
|
23
|
+
* fields merged in.
|
|
24
|
+
*/
|
|
25
|
+
declare function createRequestHandler({ build, mode, getLoadContext, }: {
|
|
26
|
+
build: ServerBuild;
|
|
27
|
+
mode?: string;
|
|
28
|
+
getLoadContext?: GetLoadContextFunction;
|
|
29
|
+
}): RequestHandler;
|
|
30
|
+
|
|
31
|
+
export { type GetLoadContextFunction, type RequestHandler, createRequestHandler, netlifyRouterContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/vite-plugin-react-router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "React Router 7+ Vite plugin for Netlify",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
"types": "./dist/index.d.mts",
|
|
17
17
|
"default": "./dist/index.mjs"
|
|
18
18
|
}
|
|
19
|
+
},
|
|
20
|
+
"./serverless": {
|
|
21
|
+
"types": "./dist/serverless.d.mts",
|
|
22
|
+
"default": "./dist/serverless.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./edge": {
|
|
25
|
+
"types": "./dist/edge.d.mts",
|
|
26
|
+
"default": "./dist/edge.mjs"
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"files": [
|
|
@@ -43,7 +51,8 @@
|
|
|
43
51
|
"isbot": "^5.0.0"
|
|
44
52
|
},
|
|
45
53
|
"devDependencies": {
|
|
46
|
-
"@netlify/functions": "^
|
|
54
|
+
"@netlify/edge-functions": "^2.11.0",
|
|
55
|
+
"@netlify/functions": "^3.1.10",
|
|
47
56
|
"@types/react": "^18.0.27",
|
|
48
57
|
"@types/react-dom": "^18.0.10",
|
|
49
58
|
"react": "^18.2.0",
|
|
@@ -63,7 +72,8 @@
|
|
|
63
72
|
"access": "public"
|
|
64
73
|
},
|
|
65
74
|
"scripts": {
|
|
66
|
-
"build": "
|
|
67
|
-
"build:watch": "pnpm run build --watch"
|
|
75
|
+
"build": "rm -rf dist && tsup-node",
|
|
76
|
+
"build:watch": "pnpm run build --watch",
|
|
77
|
+
"publint": "publint --strict"
|
|
68
78
|
}
|
|
69
79
|
}
|