@palamedes/react-router-rsc 1.16.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/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.mjs +22 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sebastian Software GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @palamedes/react-router-rsc
|
|
2
|
+
|
|
3
|
+
Experimental request-scoped i18n for React Router RSC Framework Mode Server
|
|
4
|
+
Functions.
|
|
5
|
+
|
|
6
|
+
React Router marks RSC as experimental and allows breaking changes in patch and
|
|
7
|
+
minor releases. This package is opt-in and intentionally pins its supported
|
|
8
|
+
upstream contract to React Router `8.3.0` and `@vitejs/plugin-rsc` `0.5.34`.
|
|
9
|
+
Review React Router's RSC release notes and rerun the production fixture before
|
|
10
|
+
upgrading either package.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm add @palamedes/core @palamedes/runtime @palamedes/react-router-rsc
|
|
16
|
+
pnpm add -D @palamedes/cli @palamedes/vite-plugin @react-router/dev@8.3.0 \
|
|
17
|
+
@vitejs/plugin-rsc@0.5.34 vite react-router@8.3.0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`@palamedes/react-router-rsc` is ESM-only, matching React Router's RSC and Vite
|
|
21
|
+
runtime. Use `import`; CommonJS `require()` is deliberately unsupported.
|
|
22
|
+
|
|
23
|
+
This adapter supports only RSC Framework Mode. It does not change ordinary
|
|
24
|
+
React Router Framework Mode applications or route actions. RSC Data Mode owns
|
|
25
|
+
its RSC/SSR entry wiring, so it is not supported by this adapter.
|
|
26
|
+
|
|
27
|
+
## Configure the RSC entry once
|
|
28
|
+
|
|
29
|
+
Use the React Router RSC Vite plugin and Vite's experimental RSC plugin. React
|
|
30
|
+
Router requires its RSC plugin before `rsc()`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// vite.config.ts
|
|
34
|
+
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite"
|
|
35
|
+
import { palamedes } from "@palamedes/vite-plugin"
|
|
36
|
+
import rsc from "@vitejs/plugin-rsc"
|
|
37
|
+
import { defineConfig } from "vite"
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [palamedes(), reactRouterRSC(), rsc()],
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Create `app/entry.rsc.tsx`. The resolver receives React Router's original
|
|
45
|
+
Fetch `Request`, including `headers` and `cookies`; it owns locale negotiation,
|
|
46
|
+
catalog loading, and activation of a **fresh** i18n instance.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import defaultEntry from "@react-router/dev/config/default-rsc-entries/entry.rsc"
|
|
50
|
+
import { createReactRouterRscI18nRequestScope } from "@palamedes/react-router-rsc"
|
|
51
|
+
import type { RouterContextProvider } from "react-router"
|
|
52
|
+
|
|
53
|
+
import { createRequestI18n } from "./i18n"
|
|
54
|
+
|
|
55
|
+
const palamedesI18n = createReactRouterRscI18nRequestScope(createRequestI18n)
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
fetch(request: Request, requestContext?: RouterContextProvider) {
|
|
59
|
+
return palamedesI18n.run(request, () => defaultEntry.fetch(request, requestContext))
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (import.meta.hot) {
|
|
64
|
+
import.meta.hot.accept()
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`createRequestI18n()` should load only the active locale before returning:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { createI18n } from "@palamedes/core"
|
|
72
|
+
import { messages as de } from "./locales/de.po"
|
|
73
|
+
import { messages as en } from "./locales/en.po"
|
|
74
|
+
|
|
75
|
+
export function createRequestI18n(request: Request) {
|
|
76
|
+
const locale = request.headers.get("cookie")?.includes("locale=de") ? "de" : "en"
|
|
77
|
+
const i18n = createI18n()
|
|
78
|
+
i18n.load(locale, locale === "de" ? de : en)
|
|
79
|
+
i18n.activate(locale)
|
|
80
|
+
return i18n
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The entry wrapper starts before React Router calls
|
|
85
|
+
`unstable_matchRSCServerRequest`. It therefore covers Server Function argument
|
|
86
|
+
and default-parameter evaluation, dispatch, awaited work, RSC rendering,
|
|
87
|
+
automatic post-action revalidation, the SSR pass, and streams created during
|
|
88
|
+
that work. Initialization failures reject before React Router dispatches a
|
|
89
|
+
Server Function, with an error beginning `Palamedes React Router RSC i18n
|
|
90
|
+
initialization failed`.
|
|
91
|
+
|
|
92
|
+
## Current limits
|
|
93
|
+
|
|
94
|
+
- The adapter relies on Node's `AsyncLocalStorage` through
|
|
95
|
+
`@palamedes/runtime/server`. Do not use it in Edge or Worker runtimes unless
|
|
96
|
+
that runtime's Node-compatible async context propagation has been verified.
|
|
97
|
+
- `scope.run()` restores its caller's store when the default entry settles, but
|
|
98
|
+
timers, promise continuations, stream callbacks, and other async resources
|
|
99
|
+
created inside that scope inherit its request locale even if they run later.
|
|
100
|
+
Work initiated separately without an inherited request context has no active
|
|
101
|
+
locale; pass explicit data or create an appropriate new scope for it.
|
|
102
|
+
- React Router RSC runs separate RSC and SSR module graphs. The shared
|
|
103
|
+
Palamedes runtime state and `AsyncLocalStorage` scope preserve locale
|
|
104
|
+
isolation across both graphs, including concurrent requests.
|
|
105
|
+
- Vite graph splitting currently maps client chunks to active-locale catalog
|
|
106
|
+
fragments. It does not expose an RSC Server Function module-to-fragment
|
|
107
|
+
manifest, so this adapter cannot safely lazy-load only evaluated server
|
|
108
|
+
fragments. Load the active locale's server catalog in the resolver. This is
|
|
109
|
+
intentionally a documented limitation, not a transform fallback.
|
|
110
|
+
- Do not add the Next.js directive-aware Server Function transform. The RSC
|
|
111
|
+
entry is earlier than parameter evaluation, while body instrumentation would
|
|
112
|
+
be too late for eager macro defaults and would need the existing safety
|
|
113
|
+
diagnostic.
|
|
114
|
+
|
|
115
|
+
## Validation
|
|
116
|
+
|
|
117
|
+
`examples/react-router-rsc-cookie` is the production-built browser fixture. It
|
|
118
|
+
calls a real top-level `"use server"` function from a Client Component, proves
|
|
119
|
+
direct/synchronous/asynchronous/cross-module macros and a default parameter,
|
|
120
|
+
then verifies concurrent English and German requests plus revalidation.
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
pnpm verify:react-router-rsc
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT © 2026 Sebastian Software
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { I18nInstance } from '@palamedes/runtime';
|
|
2
|
+
import { ServerI18nScope } from '@palamedes/runtime/server';
|
|
3
|
+
|
|
4
|
+
/** Resolves a fresh, activated i18n instance from React Router's original Fetch request. */
|
|
5
|
+
type ReactRouterRscI18nResolver<T extends I18nInstance = I18nInstance> = (request: Request) => T | Promise<T>;
|
|
6
|
+
/** Runs the complete React Router RSC entry request inside a request-local i18n scope. */
|
|
7
|
+
type ReactRouterRscI18nRequestScope<T extends I18nInstance = I18nInstance> = {
|
|
8
|
+
run<Result>(request: Request, dispatch: () => Result | Promise<Result>): Promise<Result>;
|
|
9
|
+
scope: ServerI18nScope<T>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates the runtime boundary used by a custom React Router `entry.rsc.tsx`.
|
|
13
|
+
*
|
|
14
|
+
* Wrap React Router's default RSC `fetch()` call, not an individual Server
|
|
15
|
+
* Function. The default entry starts `unstable_matchRSCServerRequest` after
|
|
16
|
+
* this callback begins, so argument binding, Server Function dispatch, RSC
|
|
17
|
+
* rendering, automatic revalidation, SSR, and streams created during that work
|
|
18
|
+
* all inherit the same `AsyncLocalStorage` context.
|
|
19
|
+
*/
|
|
20
|
+
declare function createReactRouterRscI18nRequestScope<T extends I18nInstance = I18nInstance>(resolveI18n: ReactRouterRscI18nResolver<T>): ReactRouterRscI18nRequestScope<T>;
|
|
21
|
+
|
|
22
|
+
export { createReactRouterRscI18nRequestScope };
|
|
23
|
+
export type { ReactRouterRscI18nRequestScope, ReactRouterRscI18nResolver };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { I18nInstance } from '@palamedes/runtime';
|
|
2
|
+
import { ServerI18nScope } from '@palamedes/runtime/server';
|
|
3
|
+
|
|
4
|
+
/** Resolves a fresh, activated i18n instance from React Router's original Fetch request. */
|
|
5
|
+
type ReactRouterRscI18nResolver<T extends I18nInstance = I18nInstance> = (request: Request) => T | Promise<T>;
|
|
6
|
+
/** Runs the complete React Router RSC entry request inside a request-local i18n scope. */
|
|
7
|
+
type ReactRouterRscI18nRequestScope<T extends I18nInstance = I18nInstance> = {
|
|
8
|
+
run<Result>(request: Request, dispatch: () => Result | Promise<Result>): Promise<Result>;
|
|
9
|
+
scope: ServerI18nScope<T>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates the runtime boundary used by a custom React Router `entry.rsc.tsx`.
|
|
13
|
+
*
|
|
14
|
+
* Wrap React Router's default RSC `fetch()` call, not an individual Server
|
|
15
|
+
* Function. The default entry starts `unstable_matchRSCServerRequest` after
|
|
16
|
+
* this callback begins, so argument binding, Server Function dispatch, RSC
|
|
17
|
+
* rendering, automatic revalidation, SSR, and streams created during that work
|
|
18
|
+
* all inherit the same `AsyncLocalStorage` context.
|
|
19
|
+
*/
|
|
20
|
+
declare function createReactRouterRscI18nRequestScope<T extends I18nInstance = I18nInstance>(resolveI18n: ReactRouterRscI18nResolver<T>): ReactRouterRscI18nRequestScope<T>;
|
|
21
|
+
|
|
22
|
+
export { createReactRouterRscI18nRequestScope };
|
|
23
|
+
export type { ReactRouterRscI18nRequestScope, ReactRouterRscI18nResolver };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createServerI18nScope } from '@palamedes/runtime/server';
|
|
2
|
+
|
|
3
|
+
function createReactRouterRscI18nRequestScope(resolveI18n) {
|
|
4
|
+
const scope = createServerI18nScope();
|
|
5
|
+
return {
|
|
6
|
+
async run(request, dispatch) {
|
|
7
|
+
let i18n;
|
|
8
|
+
try {
|
|
9
|
+
i18n = await resolveI18n(request);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Palamedes React Router RSC i18n initialization failed before RSC dispatch ran.",
|
|
13
|
+
{ cause: error }
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return await scope.run(i18n, async () => await dispatch());
|
|
17
|
+
},
|
|
18
|
+
scope
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { createReactRouterRscI18nRequestScope };
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@palamedes/react-router-rsc",
|
|
3
|
+
"version": "1.16.1",
|
|
4
|
+
"description": "Experimental React Router RSC request-scoped i18n for Palamedes",
|
|
5
|
+
"homepage": "https://github.com/sebastian-software/palamedes",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/sebastian-software/palamedes.git",
|
|
9
|
+
"directory": "packages/react-router-rsc"
|
|
10
|
+
},
|
|
11
|
+
"bugs": "https://github.com/sebastian-software/palamedes/issues",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.mjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"files": [
|
|
23
|
+
"README.md",
|
|
24
|
+
"dist/"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.22.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@palamedes/runtime": "^1.16.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@react-router/dev": "8.3.0",
|
|
37
|
+
"@vitejs/plugin-rsc": "0.5.34",
|
|
38
|
+
"react-router": "8.3.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"@react-router/dev": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"@vitejs/plugin-rsc": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"react-router": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@react-router/dev": "8.3.0",
|
|
53
|
+
"@types/node": "^24.0.0",
|
|
54
|
+
"@vitejs/plugin-rsc": "0.5.34",
|
|
55
|
+
"react": "^19.2.8",
|
|
56
|
+
"react-dom": "^19.2.8",
|
|
57
|
+
"react-router": "8.3.0",
|
|
58
|
+
"react-server-dom-webpack": "^19.2.8",
|
|
59
|
+
"typescript": "^7.0.2",
|
|
60
|
+
"unbuild": "^3.6.1",
|
|
61
|
+
"vite": "^8.2.1",
|
|
62
|
+
"vitest": "^4.1.10"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "unbuild",
|
|
66
|
+
"test": "pnpm build && pnpm --filter @palamedes/runtime build && vitest run --globals src/index.test.ts && node ./scripts/packed-consumer.test.mjs",
|
|
67
|
+
"stub": "unbuild --stub"
|
|
68
|
+
}
|
|
69
|
+
}
|