@netlify/vite-plugin-react-router 2.0.1 → 2.1.1
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 +80 -6
- 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/entry.server.edge.d.mts +20 -0
- package/dist/entry.server.edge.mjs +29 -0
- package/dist/handler-4e_lz5jw.d.mts +18 -0
- package/dist/index.d.mts +22 -42
- package/dist/index.d.ts +30 -11
- package/dist/index.js +134 -39
- package/dist/index.mjs +93 -75
- package/dist/serverless.d.mts +31 -0
- package/dist/serverless.mjs +9 -0
- package/package.json +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [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)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **@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))
|
|
9
|
+
|
|
10
|
+
## [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)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **@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))
|
|
16
|
+
|
|
3
17
|
## [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)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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
|
-
[Netlify Functions](https://docs.netlify.com/functions/overview/).
|
|
3
|
+
The React Router Adapter for Netlify allows you to deploy your [React Router](https://reactrouter.com) app to Netlify.
|
|
5
4
|
|
|
6
5
|
## How to use
|
|
7
6
|
|
|
8
7
|
To deploy a React Router 7+ site to Netlify, install this package:
|
|
9
8
|
|
|
10
9
|
```sh
|
|
11
|
-
npm install
|
|
10
|
+
npm install @netlify/vite-plugin-react-router
|
|
12
11
|
```
|
|
13
12
|
|
|
14
13
|
It's also recommended (but not required) to use the
|
|
@@ -38,6 +37,79 @@ export default defineConfig({
|
|
|
38
37
|
})
|
|
39
38
|
```
|
|
40
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. Create a file with the following content:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
export { default } from 'virtual:netlify-server-entry'
|
|
65
|
+
```
|
|
66
|
+
|
|
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
|
+
> ```
|
|
78
|
+
|
|
79
|
+
Finally, if you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`,
|
|
80
|
+
you must exclude those paths to avoid conflicts with the generated React Router SSR handler:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
export default defineConfig({
|
|
84
|
+
plugins: [
|
|
85
|
+
reactRouter(),
|
|
86
|
+
tsconfigPaths(),
|
|
87
|
+
netlifyReactRouter({
|
|
88
|
+
edge: true,
|
|
89
|
+
excludedPaths: ['/ping', '/api/*', '/webhooks/*'],
|
|
90
|
+
}),
|
|
91
|
+
netlify(),
|
|
92
|
+
],
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Moving back from Edge Functions to Functions
|
|
97
|
+
|
|
98
|
+
To switch from Edge Functions back to Functions, you must:
|
|
99
|
+
|
|
100
|
+
1. Remove the `edge: true` option from your `vite.config.ts`
|
|
101
|
+
2. **Delete the `app/entry.server.tsx` file** (React Router will use its default Node.js-compatible entry)
|
|
102
|
+
|
|
103
|
+
#### Edge runtime
|
|
104
|
+
|
|
105
|
+
Before deploying to Edge Functions, review the Netlify Edge Functions documentation for important details:
|
|
106
|
+
|
|
107
|
+
- [Runtime environment](https://docs.netlify.com/build/edge-functions/api/#runtime-environment) - Understand the Deno
|
|
108
|
+
runtime
|
|
109
|
+
- [Supported Web APIs](https://docs.netlify.com/build/edge-functions/api/#supported-web-apis) - Check which APIs are
|
|
110
|
+
available
|
|
111
|
+
- [Limitations](https://docs.netlify.com/build/edge-functions/limits/) - Be aware of resource limits and constraints
|
|
112
|
+
|
|
41
113
|
### Load context
|
|
42
114
|
|
|
43
115
|
This plugin automatically includes all
|
|
@@ -71,7 +143,8 @@ type-safe `RouterContextProvider`. Note that this requires requires v2.0.0+ of `
|
|
|
71
143
|
For example:
|
|
72
144
|
|
|
73
145
|
```tsx
|
|
74
|
-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
|
|
146
|
+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router/serverless'
|
|
147
|
+
// NOTE: if setting `edge: true`, import from /edge ^ instead here
|
|
75
148
|
import { useLoaderData } from 'react-router'
|
|
76
149
|
import type { Route } from './+types/example'
|
|
77
150
|
|
|
@@ -101,10 +174,11 @@ To use middleware,
|
|
|
101
174
|
that this requires requires v2.0.0+ of `@netlify/vite-plugin-react-router`.
|
|
102
175
|
|
|
103
176
|
To access the [Netlify context](https://docs.netlify.com/build/functions/api/#netlify-specific-context-object)
|
|
104
|
-
specifically, you must import our `
|
|
177
|
+
specifically, you must import our `RouterContext` instance:
|
|
105
178
|
|
|
106
179
|
```tsx
|
|
107
|
-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
|
|
180
|
+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router/serverless'
|
|
181
|
+
// NOTE: if setting `edge: true`, import from /edge ^ instead here
|
|
108
182
|
|
|
109
183
|
import type { Route } from './+types/home'
|
|
110
184
|
|
|
@@ -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,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
|
+
};
|
|
@@ -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<Partial<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,12 +14,18 @@ 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
|
|
@@ -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,42 @@ __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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
function createNetlifyRequestHandler({
|
|
32
|
+
build,
|
|
33
|
+
mode,
|
|
34
|
+
getLoadContext,
|
|
35
|
+
netlifyRouterContext: netlifyRouterContext2,
|
|
36
|
+
runtimeName
|
|
37
|
+
}) {
|
|
38
|
+
const reactRouterHandler = (0, import_react_router.createRequestHandler)(build, mode);
|
|
39
|
+
return async (request, netlifyContext) => {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
console.log(`[${request.method}] ${request.url}`);
|
|
42
|
+
try {
|
|
43
|
+
const getDefaultReactRouterContext = () => {
|
|
44
|
+
const ctx = new import_react_router.RouterContextProvider();
|
|
45
|
+
ctx.set(netlifyRouterContext2, netlifyContext);
|
|
46
|
+
Object.assign(ctx, netlifyContext);
|
|
47
|
+
return ctx;
|
|
48
|
+
};
|
|
49
|
+
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
50
|
+
const response = await reactRouterHandler(request, reactRouterContext);
|
|
51
|
+
response.headers.set("x-nf-runtime", runtimeName);
|
|
52
|
+
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
53
|
+
return response;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(error);
|
|
56
|
+
return new Response("Internal Error", { status: 500 });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
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)(
|
|
36
65
|
new Proxy(
|
|
37
66
|
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
|
|
38
67
|
{},
|
|
@@ -57,34 +86,23 @@ var netlifyRouterContext = (
|
|
|
57
86
|
}
|
|
58
87
|
}
|
|
59
88
|
)
|
|
60
|
-
)
|
|
61
|
-
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/runtimes/netlify-functions.ts
|
|
93
|
+
var netlifyRouterContext = createNetlifyRouterContext();
|
|
62
94
|
function createRequestHandler({
|
|
63
95
|
build,
|
|
64
96
|
mode,
|
|
65
97
|
getLoadContext
|
|
66
98
|
}) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ctx.set(netlifyRouterContext, netlifyContext);
|
|
75
|
-
Object.assign(ctx, netlifyContext);
|
|
76
|
-
return ctx;
|
|
77
|
-
};
|
|
78
|
-
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
79
|
-
const response = await reactRouterHandler(request, reactRouterContext);
|
|
80
|
-
response.headers.set("x-nf-runtime", "Node");
|
|
81
|
-
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
82
|
-
return response;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error(error);
|
|
85
|
-
return new Response("Internal Error", { status: 500 });
|
|
86
|
-
}
|
|
87
|
-
};
|
|
99
|
+
return createNetlifyRequestHandler({
|
|
100
|
+
build,
|
|
101
|
+
mode,
|
|
102
|
+
getLoadContext,
|
|
103
|
+
netlifyRouterContext,
|
|
104
|
+
runtimeName: "Node"
|
|
105
|
+
});
|
|
88
106
|
}
|
|
89
107
|
|
|
90
108
|
// src/plugin.ts
|
|
@@ -94,26 +112,38 @@ var import_posix = require("path/posix");
|
|
|
94
112
|
|
|
95
113
|
// package.json
|
|
96
114
|
var name = "@netlify/vite-plugin-react-router";
|
|
97
|
-
var version = "2.
|
|
115
|
+
var version = "2.1.1";
|
|
98
116
|
|
|
99
117
|
// src/plugin.ts
|
|
100
118
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
119
|
+
var NETLIFY_EDGE_FUNCTIONS_DIR = ".netlify/v1/edge-functions";
|
|
101
120
|
var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
102
121
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
103
122
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
104
123
|
var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
|
|
124
|
+
var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
|
|
105
125
|
var toPosixPath = (path) => path.split(import_node_path.sep).join(import_posix.sep);
|
|
106
126
|
var FUNCTION_HANDLER = (
|
|
107
127
|
/* js */
|
|
108
128
|
`
|
|
109
|
-
import { createRequestHandler } from "@netlify/vite-plugin-react-router";
|
|
129
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/serverless";
|
|
110
130
|
import * as build from "virtual:react-router/server-build";
|
|
111
131
|
export default createRequestHandler({
|
|
112
132
|
build,
|
|
113
133
|
});
|
|
114
134
|
`
|
|
115
135
|
);
|
|
116
|
-
|
|
136
|
+
var EDGE_FUNCTION_HANDLER = (
|
|
137
|
+
/* js */
|
|
138
|
+
`
|
|
139
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/edge";
|
|
140
|
+
import * as build from "virtual:react-router/server-build";
|
|
141
|
+
export default createRequestHandler({
|
|
142
|
+
build,
|
|
143
|
+
});
|
|
144
|
+
`
|
|
145
|
+
);
|
|
146
|
+
function generateNetlifyFunction(handlerPath, excludedPath) {
|
|
117
147
|
return (
|
|
118
148
|
/* js */
|
|
119
149
|
`
|
|
@@ -123,17 +153,38 @@ function generateNetlifyFunction(handlerPath) {
|
|
|
123
153
|
name: "React Router server handler",
|
|
124
154
|
generator: "${name}@${version}",
|
|
125
155
|
path: "/*",
|
|
156
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
126
157
|
preferStatic: true,
|
|
127
158
|
};
|
|
128
159
|
`
|
|
129
160
|
);
|
|
130
161
|
}
|
|
131
|
-
function
|
|
162
|
+
function generateEdgeFunction(handlerPath, excludedPath) {
|
|
163
|
+
return (
|
|
164
|
+
/* js */
|
|
165
|
+
`
|
|
166
|
+
export { default } from "${handlerPath}";
|
|
167
|
+
|
|
168
|
+
export const config = {
|
|
169
|
+
name: "React Router server handler",
|
|
170
|
+
generator: "${name}@${version}",
|
|
171
|
+
cache: "manual",
|
|
172
|
+
path: "/*",
|
|
173
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
174
|
+
};
|
|
175
|
+
`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
function netlifyPlugin(options = {}) {
|
|
179
|
+
const edge = options.edge ?? false;
|
|
180
|
+
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
132
181
|
let resolvedConfig;
|
|
133
182
|
let isProductionSsrBuild = false;
|
|
183
|
+
let currentCommand;
|
|
134
184
|
return {
|
|
135
185
|
name: "vite-plugin-netlify-react-router",
|
|
136
186
|
config(config, { command, isSsrBuild }) {
|
|
187
|
+
currentCommand = command;
|
|
137
188
|
isProductionSsrBuild = isSsrBuild === true && command === "build";
|
|
138
189
|
if (isProductionSsrBuild) {
|
|
139
190
|
config.build ??= {};
|
|
@@ -149,17 +200,40 @@ function netlifyPlugin() {
|
|
|
149
200
|
config.build.rollupOptions.output = {};
|
|
150
201
|
}
|
|
151
202
|
config.build.rollupOptions.output.entryFileNames = "[name].js";
|
|
203
|
+
if (edge) {
|
|
204
|
+
config.ssr = {
|
|
205
|
+
...config.ssr,
|
|
206
|
+
target: "webworker",
|
|
207
|
+
// Bundle everything except Node.js built-ins (which are supported but must use the `node:` prefix):
|
|
208
|
+
// https://docs.netlify.com/build/edge-functions/api/#runtime-environment
|
|
209
|
+
noExternal: /^(?!node:).*$/,
|
|
210
|
+
resolve: {
|
|
211
|
+
...config.resolve,
|
|
212
|
+
conditions: ["worker", "deno", "browser"]
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
152
216
|
}
|
|
153
217
|
},
|
|
154
|
-
async resolveId(source) {
|
|
218
|
+
async resolveId(source, importer, options2) {
|
|
155
219
|
if (source === FUNCTION_HANDLER_MODULE_ID) {
|
|
156
220
|
return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
|
|
157
221
|
}
|
|
222
|
+
if (source === SERVER_ENTRY_MODULE_ID && edge) {
|
|
223
|
+
if (currentCommand === "serve") {
|
|
224
|
+
const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
|
|
225
|
+
if (!reactRouterDev) {
|
|
226
|
+
throw new Error("The @react-router/dev package is required for local development. Please install it.");
|
|
227
|
+
}
|
|
228
|
+
return (0, import_node_path.resolve)((0, import_node_path.dirname)(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
|
|
229
|
+
}
|
|
230
|
+
return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
|
|
231
|
+
}
|
|
158
232
|
},
|
|
159
233
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
160
234
|
load(id) {
|
|
161
235
|
if (id === RESOLVED_FUNCTION_HANDLER_MODULE_ID) {
|
|
162
|
-
return FUNCTION_HANDLER;
|
|
236
|
+
return edge ? EDGE_FUNCTION_HANDLER : FUNCTION_HANDLER;
|
|
163
237
|
}
|
|
164
238
|
},
|
|
165
239
|
async configResolved(config) {
|
|
@@ -168,11 +242,32 @@ function netlifyPlugin() {
|
|
|
168
242
|
// See https://rollupjs.org/plugin-development/#writebundle.
|
|
169
243
|
async writeBundle() {
|
|
170
244
|
if (isProductionSsrBuild) {
|
|
171
|
-
const functionsDirectory = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
172
|
-
await (0, import_promises.mkdir)(functionsDirectory, { recursive: true });
|
|
173
245
|
const handlerPath = (0, import_node_path.join)(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
174
|
-
|
|
175
|
-
|
|
246
|
+
if (edge) {
|
|
247
|
+
const clientDir = (0, import_node_path.join)(resolvedConfig.build.outDir, "..", "client");
|
|
248
|
+
const entries = await (0, import_promises.readdir)(clientDir, { withFileTypes: true });
|
|
249
|
+
const excludedPath = [
|
|
250
|
+
"/.netlify/*",
|
|
251
|
+
...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
|
|
252
|
+
...additionalExcludedPaths
|
|
253
|
+
];
|
|
254
|
+
const edgeFunctionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
255
|
+
await (0, import_promises.mkdir)(edgeFunctionsDir, { recursive: true });
|
|
256
|
+
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(edgeFunctionsDir, handlerPath));
|
|
257
|
+
await (0, import_promises.writeFile)(
|
|
258
|
+
(0, import_node_path.join)(edgeFunctionsDir, FUNCTION_FILENAME),
|
|
259
|
+
generateEdgeFunction(relativeHandlerPath, excludedPath)
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
const functionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
263
|
+
await (0, import_promises.mkdir)(functionsDir, { recursive: true });
|
|
264
|
+
const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(functionsDir, handlerPath));
|
|
265
|
+
const excludedPath = ["/.netlify/*", ...additionalExcludedPaths];
|
|
266
|
+
await (0, import_promises.writeFile)(
|
|
267
|
+
(0, import_node_path.join)(functionsDir, FUNCTION_FILENAME),
|
|
268
|
+
generateNetlifyFunction(relativeHandlerPath, excludedPath)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
176
271
|
}
|
|
177
272
|
}
|
|
178
273
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,95 +1,48 @@
|
|
|
1
|
-
// src/server.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var netlifyRouterContext = (
|
|
8
|
-
// We must use a singleton because Remix contexts rely on referential equality.
|
|
9
|
-
// We can't hook into the request lifecycle in dev mode, so we use a Proxy to always read from the
|
|
10
|
-
// current `Netlify.context` value, which is always contextual to the in-flight request.
|
|
11
|
-
createContext(
|
|
12
|
-
new Proxy(
|
|
13
|
-
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
|
|
14
|
-
{},
|
|
15
|
-
{
|
|
16
|
-
get(_target, prop, receiver) {
|
|
17
|
-
return Reflect.get(Netlify.context ?? {}, prop, receiver);
|
|
18
|
-
},
|
|
19
|
-
set(_target, prop, value, receiver) {
|
|
20
|
-
return Reflect.set(Netlify.context ?? {}, prop, value, receiver);
|
|
21
|
-
},
|
|
22
|
-
has(_target, prop) {
|
|
23
|
-
return Reflect.has(Netlify.context ?? {}, prop);
|
|
24
|
-
},
|
|
25
|
-
deleteProperty(_target, prop) {
|
|
26
|
-
return Reflect.deleteProperty(Netlify.context ?? {}, prop);
|
|
27
|
-
},
|
|
28
|
-
ownKeys(_target) {
|
|
29
|
-
return Reflect.ownKeys(Netlify.context ?? {});
|
|
30
|
-
},
|
|
31
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
32
|
-
return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
)
|
|
37
|
-
);
|
|
38
|
-
function createRequestHandler({
|
|
39
|
-
build,
|
|
40
|
-
mode,
|
|
41
|
-
getLoadContext
|
|
42
|
-
}) {
|
|
43
|
-
const reactRouterHandler = createReactRouterRequestHandler(build, mode);
|
|
44
|
-
return async (request, netlifyContext) => {
|
|
45
|
-
const start = Date.now();
|
|
46
|
-
console.log(`[${request.method}] ${request.url}`);
|
|
47
|
-
try {
|
|
48
|
-
const getDefaultReactRouterContext = () => {
|
|
49
|
-
const ctx = new RouterContextProvider();
|
|
50
|
-
ctx.set(netlifyRouterContext, netlifyContext);
|
|
51
|
-
Object.assign(ctx, netlifyContext);
|
|
52
|
-
return ctx;
|
|
53
|
-
};
|
|
54
|
-
const reactRouterContext = await getLoadContext?.(request, netlifyContext) ?? getDefaultReactRouterContext();
|
|
55
|
-
const response = await reactRouterHandler(request, reactRouterContext);
|
|
56
|
-
response.headers.set("x-nf-runtime", "Node");
|
|
57
|
-
console.log(`[${response.status}] ${request.url} (${Date.now() - start}ms)`);
|
|
58
|
-
return response;
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error(error);
|
|
61
|
-
return new Response("Internal Error", { status: 500 });
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
2
|
+
createRequestHandler,
|
|
3
|
+
netlifyRouterContext
|
|
4
|
+
} from "./chunk-3EFABROO.mjs";
|
|
5
|
+
import "./chunk-J5PMA2AP.mjs";
|
|
65
6
|
|
|
66
7
|
// src/plugin.ts
|
|
67
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
68
|
-
import { join, relative, sep } from "node:path";
|
|
8
|
+
import { mkdir, writeFile, readdir } from "node:fs/promises";
|
|
9
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
69
10
|
import { sep as posixSep } from "node:path/posix";
|
|
70
11
|
|
|
71
12
|
// package.json
|
|
72
13
|
var name = "@netlify/vite-plugin-react-router";
|
|
73
|
-
var version = "2.
|
|
14
|
+
var version = "2.1.1";
|
|
74
15
|
|
|
75
16
|
// src/plugin.ts
|
|
76
17
|
var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
|
|
18
|
+
var NETLIFY_EDGE_FUNCTIONS_DIR = ".netlify/v1/edge-functions";
|
|
77
19
|
var FUNCTION_FILENAME = "react-router-server.mjs";
|
|
78
20
|
var FUNCTION_HANDLER_CHUNK = "server";
|
|
79
21
|
var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
|
|
80
22
|
var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
|
|
23
|
+
var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
|
|
81
24
|
var toPosixPath = (path) => path.split(sep).join(posixSep);
|
|
82
25
|
var FUNCTION_HANDLER = (
|
|
83
26
|
/* js */
|
|
84
27
|
`
|
|
85
|
-
import { createRequestHandler } from "@netlify/vite-plugin-react-router";
|
|
28
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/serverless";
|
|
86
29
|
import * as build from "virtual:react-router/server-build";
|
|
87
30
|
export default createRequestHandler({
|
|
88
31
|
build,
|
|
89
32
|
});
|
|
90
33
|
`
|
|
91
34
|
);
|
|
92
|
-
|
|
35
|
+
var EDGE_FUNCTION_HANDLER = (
|
|
36
|
+
/* js */
|
|
37
|
+
`
|
|
38
|
+
import { createRequestHandler } from "@netlify/vite-plugin-react-router/edge";
|
|
39
|
+
import * as build from "virtual:react-router/server-build";
|
|
40
|
+
export default createRequestHandler({
|
|
41
|
+
build,
|
|
42
|
+
});
|
|
43
|
+
`
|
|
44
|
+
);
|
|
45
|
+
function generateNetlifyFunction(handlerPath, excludedPath) {
|
|
93
46
|
return (
|
|
94
47
|
/* js */
|
|
95
48
|
`
|
|
@@ -99,17 +52,38 @@ function generateNetlifyFunction(handlerPath) {
|
|
|
99
52
|
name: "React Router server handler",
|
|
100
53
|
generator: "${name}@${version}",
|
|
101
54
|
path: "/*",
|
|
55
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
102
56
|
preferStatic: true,
|
|
103
57
|
};
|
|
104
58
|
`
|
|
105
59
|
);
|
|
106
60
|
}
|
|
107
|
-
function
|
|
61
|
+
function generateEdgeFunction(handlerPath, excludedPath) {
|
|
62
|
+
return (
|
|
63
|
+
/* js */
|
|
64
|
+
`
|
|
65
|
+
export { default } from "${handlerPath}";
|
|
66
|
+
|
|
67
|
+
export const config = {
|
|
68
|
+
name: "React Router server handler",
|
|
69
|
+
generator: "${name}@${version}",
|
|
70
|
+
cache: "manual",
|
|
71
|
+
path: "/*",
|
|
72
|
+
excludedPath: ${JSON.stringify(excludedPath)},
|
|
73
|
+
};
|
|
74
|
+
`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function netlifyPlugin(options = {}) {
|
|
78
|
+
const edge = options.edge ?? false;
|
|
79
|
+
const additionalExcludedPaths = options.excludedPaths ?? [];
|
|
108
80
|
let resolvedConfig;
|
|
109
81
|
let isProductionSsrBuild = false;
|
|
82
|
+
let currentCommand;
|
|
110
83
|
return {
|
|
111
84
|
name: "vite-plugin-netlify-react-router",
|
|
112
85
|
config(config, { command, isSsrBuild }) {
|
|
86
|
+
currentCommand = command;
|
|
113
87
|
isProductionSsrBuild = isSsrBuild === true && command === "build";
|
|
114
88
|
if (isProductionSsrBuild) {
|
|
115
89
|
config.build ??= {};
|
|
@@ -125,17 +99,40 @@ function netlifyPlugin() {
|
|
|
125
99
|
config.build.rollupOptions.output = {};
|
|
126
100
|
}
|
|
127
101
|
config.build.rollupOptions.output.entryFileNames = "[name].js";
|
|
102
|
+
if (edge) {
|
|
103
|
+
config.ssr = {
|
|
104
|
+
...config.ssr,
|
|
105
|
+
target: "webworker",
|
|
106
|
+
// Bundle everything except Node.js built-ins (which are supported but must use the `node:` prefix):
|
|
107
|
+
// https://docs.netlify.com/build/edge-functions/api/#runtime-environment
|
|
108
|
+
noExternal: /^(?!node:).*$/,
|
|
109
|
+
resolve: {
|
|
110
|
+
...config.resolve,
|
|
111
|
+
conditions: ["worker", "deno", "browser"]
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
128
115
|
}
|
|
129
116
|
},
|
|
130
|
-
async resolveId(source) {
|
|
117
|
+
async resolveId(source, importer, options2) {
|
|
131
118
|
if (source === FUNCTION_HANDLER_MODULE_ID) {
|
|
132
119
|
return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
|
|
133
120
|
}
|
|
121
|
+
if (source === SERVER_ENTRY_MODULE_ID && edge) {
|
|
122
|
+
if (currentCommand === "serve") {
|
|
123
|
+
const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
|
|
124
|
+
if (!reactRouterDev) {
|
|
125
|
+
throw new Error("The @react-router/dev package is required for local development. Please install it.");
|
|
126
|
+
}
|
|
127
|
+
return resolve(dirname(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
|
|
128
|
+
}
|
|
129
|
+
return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
|
|
130
|
+
}
|
|
134
131
|
},
|
|
135
132
|
// See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
|
|
136
133
|
load(id) {
|
|
137
134
|
if (id === RESOLVED_FUNCTION_HANDLER_MODULE_ID) {
|
|
138
|
-
return FUNCTION_HANDLER;
|
|
135
|
+
return edge ? EDGE_FUNCTION_HANDLER : FUNCTION_HANDLER;
|
|
139
136
|
}
|
|
140
137
|
},
|
|
141
138
|
async configResolved(config) {
|
|
@@ -144,11 +141,32 @@ function netlifyPlugin() {
|
|
|
144
141
|
// See https://rollupjs.org/plugin-development/#writebundle.
|
|
145
142
|
async writeBundle() {
|
|
146
143
|
if (isProductionSsrBuild) {
|
|
147
|
-
const functionsDirectory = join(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
148
|
-
await mkdir(functionsDirectory, { recursive: true });
|
|
149
144
|
const handlerPath = join(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
if (edge) {
|
|
146
|
+
const clientDir = join(resolvedConfig.build.outDir, "..", "client");
|
|
147
|
+
const entries = await readdir(clientDir, { withFileTypes: true });
|
|
148
|
+
const excludedPath = [
|
|
149
|
+
"/.netlify/*",
|
|
150
|
+
...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
|
|
151
|
+
...additionalExcludedPaths
|
|
152
|
+
];
|
|
153
|
+
const edgeFunctionsDir = join(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
|
|
154
|
+
await mkdir(edgeFunctionsDir, { recursive: true });
|
|
155
|
+
const relativeHandlerPath = toPosixPath(relative(edgeFunctionsDir, handlerPath));
|
|
156
|
+
await writeFile(
|
|
157
|
+
join(edgeFunctionsDir, FUNCTION_FILENAME),
|
|
158
|
+
generateEdgeFunction(relativeHandlerPath, excludedPath)
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
const functionsDir = join(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR);
|
|
162
|
+
await mkdir(functionsDir, { recursive: true });
|
|
163
|
+
const relativeHandlerPath = toPosixPath(relative(functionsDir, handlerPath));
|
|
164
|
+
const excludedPath = ["/.netlify/*", ...additionalExcludedPaths];
|
|
165
|
+
await writeFile(
|
|
166
|
+
join(functionsDir, FUNCTION_FILENAME),
|
|
167
|
+
generateNetlifyFunction(relativeHandlerPath, excludedPath)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
152
170
|
}
|
|
153
171
|
}
|
|
154
172
|
};
|
|
@@ -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.1",
|
|
4
4
|
"description": "React Router 7+ Vite plugin for Netlify",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,6 +16,18 @@
|
|
|
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"
|
|
27
|
+
},
|
|
28
|
+
"./entry.server.edge": {
|
|
29
|
+
"types": "./dist/entry.server.edge.d.mts",
|
|
30
|
+
"default": "./dist/entry.server.edge.mjs"
|
|
19
31
|
}
|
|
20
32
|
},
|
|
21
33
|
"files": [
|
|
@@ -40,10 +52,11 @@
|
|
|
40
52
|
},
|
|
41
53
|
"homepage": "https://github.com/netlify/remix-compute#readme",
|
|
42
54
|
"dependencies": {
|
|
43
|
-
"
|
|
55
|
+
"@netlify/edge-functions": "^3.0.2",
|
|
56
|
+
"@netlify/functions": "^5.1.0",
|
|
57
|
+
"isbot": "^5.1.25"
|
|
44
58
|
},
|
|
45
59
|
"devDependencies": {
|
|
46
|
-
"@netlify/functions": "^3.1.9",
|
|
47
60
|
"@types/react": "^18.0.27",
|
|
48
61
|
"@types/react-dom": "^18.0.10",
|
|
49
62
|
"react": "^18.2.0",
|
|
@@ -63,7 +76,8 @@
|
|
|
63
76
|
"access": "public"
|
|
64
77
|
},
|
|
65
78
|
"scripts": {
|
|
66
|
-
"build": "
|
|
67
|
-
"build:watch": "pnpm run build --watch"
|
|
79
|
+
"build": "rm -rf dist && tsup-node",
|
|
80
|
+
"build:watch": "pnpm run build --watch",
|
|
81
|
+
"publint": "publint --strict"
|
|
68
82
|
}
|
|
69
83
|
}
|