Skip to content

Commit cd9a5a9

Browse files
committed
fix: use a Proxy to avoid breaking change
1 parent 85aae6a commit cd9a5a9

File tree

5 files changed

+44
-20
lines changed

5 files changed

+44
-20
lines changed

packages/vite-plugin-react-router/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ type-safe `RouterContextProvider`. Note that this requires requires v2.0.0+ of `
7171
For example:
7272

7373
```tsx
74-
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
74+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
7575
import { useLoaderData } from 'react-router'
7676
import type { Route } from './+types/example'
7777

7878
export async function loader({ context }: Route.LoaderArgs) {
7979
return {
80-
country: context.get(getNetlifyRouterContext()).geo?.country?.name ?? 'an unknown country',
80+
country: context.get(netlifyRouterContext).geo?.country?.name ?? 'an unknown country',
8181
}
8282
}
8383
export default function Example() {
@@ -88,7 +88,7 @@ export default function Example() {
8888

8989
> [!IMPORTANT]
9090
>
91-
> Note that in local development, `getNetlifyRouterContext` requires Netlify platform emulation, which is provided
91+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
9292
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
9393
> you).
9494
@@ -104,12 +104,12 @@ To access the [Netlify context](https://docs.netlify.com/build/functions/api/#ne
104104
specifically, you must import our `RouterContextProvider` instance:
105105

106106
```tsx
107-
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
107+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
108108

109109
import type { Route } from './+types/home'
110110

111111
const logMiddleware: Route.MiddlewareFunction = async ({ request, context }) => {
112-
const country = context.get(getNetlifyRouterContext()).geo?.country?.name ?? 'unknown'
112+
const country = context.get(netlifyRouterContext).geo?.country?.name ?? 'unknown'
113113
console.log(`Handling ${request.method} request to ${request.url} from ${country}`)
114114
}
115115

@@ -122,6 +122,6 @@ export default function Home() {
122122

123123
> [!IMPORTANT]
124124
>
125-
> Note that in local development, `getNetlifyRouterContext` requires Netlify platform emulation, which is provided
125+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
126126
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
127127
> you).
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export type { GetLoadContextFunction, RequestHandler } from './server'
2-
export { createRequestHandler, getNetlifyRouterContext } from './server'
2+
export { createRequestHandler, netlifyRouterContext } from './server'
33

44
export { netlifyPlugin as default } from './plugin'

packages/vite-plugin-react-router/src/server.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,44 @@ export type GetLoadContextFunction_V8 = (
3535

3636
export type RequestHandler = (request: Request, context: NetlifyContext) => Promise<Response>
3737

38-
let netlifyRouterContext: RouterContext<Partial<NetlifyContext>>
3938
/**
4039
* An instance of `ReactContextProvider` providing access to
4140
* [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
4241
*
43-
* @example context.get(getNetlifyRouterContext()).geo?.country?.name
42+
* @example context.get(netlifyRouterContext).geo?.country?.name
4443
*/
45-
export const getNetlifyRouterContext = () => {
46-
// We must use a singleton because Remix contexts rely on referential equality
44+
export const netlifyRouterContext =
45+
// We must use a singleton because Remix contexts rely on referential equality.
4746
// We defer initialization until first use so that we can initialize it with a default value
48-
// within a request lifecycle, where the `Netlify` global will be set. This is just for dev.
49-
netlifyRouterContext ??= createContext<Partial<NetlifyContext>>(Netlify.context ?? {})
50-
return netlifyRouterContext
51-
}
47+
// within a request lifecycle, where the `Netlify` global will be set.
48+
// Finally, because we can't hook into the request lifecycle in dev mode, we use a Proxy to always read from the current
49+
// `Netlify.context` value, which is always contextual to the in-flight request.
50+
createContext<Partial<NetlifyContext>>(
51+
new Proxy(
52+
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
53+
{},
54+
{
55+
get(_target, prop, receiver) {
56+
return Reflect.get(Netlify.context ?? {}, prop, receiver)
57+
},
58+
set(_target, prop, value, receiver) {
59+
return Reflect.set(Netlify.context ?? {}, prop, value, receiver)
60+
},
61+
has(_target, prop) {
62+
return Reflect.has(Netlify.context ?? {}, prop)
63+
},
64+
deleteProperty(_target, prop) {
65+
return Reflect.deleteProperty(Netlify.context ?? {}, prop)
66+
},
67+
ownKeys(_target) {
68+
return Reflect.ownKeys(Netlify.context ?? {})
69+
},
70+
getOwnPropertyDescriptor(_target, prop) {
71+
return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop)
72+
},
73+
},
74+
),
75+
)
5276

5377
/**
5478
* Given a build and a callback to get the base loader context, this returns
@@ -73,7 +97,7 @@ export function createRequestHandler({
7397
try {
7498
const getDefaultReactRouterContext = () => {
7599
const ctx = new RouterContextProvider()
76-
ctx.set(getNetlifyRouterContext(), netlifyContext)
100+
ctx.set(netlifyRouterContext, netlifyContext)
77101

78102
// Provide backwards compatibility with previous plain object context
79103
// See https://reactrouter.com/how-to/middleware#migration-from-apploadcontext.

tests/e2e/fixtures/react-router-v8-middleware/app/routes/context.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
1+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
22
import { useLoaderData } from 'react-router'
33

44
import type { Route } from './+types/context'
55

66
export async function loader({ context }: Route.LoaderArgs) {
77
return {
8-
siteName: context.get(getNetlifyRouterContext()).site.name,
8+
siteName: context.get(netlifyRouterContext).site.name,
99
}
1010
}
1111
export default function About() {

tests/e2e/fixtures/react-router-v8-middleware/app/routes/middleware.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
1+
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
22

33
import type { Route } from './+types/middleware'
44

55
const logMiddleware: Route.MiddlewareFunction = async ({ request, context }, next) => {
6-
const siteName = context.get(getNetlifyRouterContext()).site.name
6+
const siteName = context.get(netlifyRouterContext).site.name
77
console.log(`Handling ${request.method} request to ${request.url} on ${siteName}`)
88
const response = await next()
99
response.headers.set('x-test-site-name', siteName)

0 commit comments

Comments
 (0)