@vitejs/plugin-rsc 0.4.30 → 0.4.31
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/README.md +78 -11
- package/dist/index.js +1 -1
- package/dist/{plugin-Bzocj-4a.js → plugin-BI8kK-GE.js} +85 -13
- package/dist/plugin.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -353,6 +353,17 @@ export default defineConfig({
|
|
|
353
353
|
// this behavior can be customized by `serverHandler` option.
|
|
354
354
|
serverHandler: false,
|
|
355
355
|
|
|
356
|
+
// the plugin provides build-time validation of 'server-only' and 'client-only' imports.
|
|
357
|
+
// this is enabled by default. See the "server-only and client-only import" section below for details.
|
|
358
|
+
validateImports: true,
|
|
359
|
+
|
|
360
|
+
// by default, the plugin uses a build-time generated encryption key for
|
|
361
|
+
// "use server" closure argument binding.
|
|
362
|
+
// This can be overwritten by configuring `defineEncryptionKey` option,
|
|
363
|
+
// for example, to obtain a key through environment variable during runtime.
|
|
364
|
+
// cf. https://nextjs.org/docs/app/guides/data-security#overwriting-encryption-keys-advanced
|
|
365
|
+
defineEncryptionKey: 'process.env.MY_ENCRYPTION_KEY',
|
|
366
|
+
|
|
356
367
|
// when `loadModuleDevProxy: true`, `import.meta.viteRsc.loadModule` is implemented
|
|
357
368
|
// through `fetch` based RPC, which allows, for example, rsc environment inside
|
|
358
369
|
// cloudflare workers to communicate with node ssr environment on main Vite process.
|
|
@@ -362,13 +373,6 @@ export default defineConfig({
|
|
|
362
373
|
// if it breaks, it can be opt-out or selectively applied based on files.
|
|
363
374
|
rscCssTransform: { filter: (id) => id.includes('/my-app/') },
|
|
364
375
|
|
|
365
|
-
// by default, the plugin uses a build-time generated encryption key for
|
|
366
|
-
// "use server" closure argument binding.
|
|
367
|
-
// This can be overwritten by configuring `defineEncryptionKey` option,
|
|
368
|
-
// for example, to obtain a key through environment variable during runtime.
|
|
369
|
-
// cf. https://nextjs.org/docs/app/guides/data-security#overwriting-encryption-keys-advanced
|
|
370
|
-
defineEncryptionKey: 'process.env.MY_ENCRYPTION_KEY',
|
|
371
|
-
|
|
372
376
|
// see `RscPluginOptions` for full options ...
|
|
373
377
|
}),
|
|
374
378
|
],
|
|
@@ -405,7 +409,9 @@ This module re-exports RSC runtime API provided by `react-server-dom/client.brow
|
|
|
405
409
|
- `createFromFetch`: a robust way of `createFromReadableStream((await fetch("...")).body)`
|
|
406
410
|
- `encodeReply/setServerCallback`: server function related...
|
|
407
411
|
|
|
408
|
-
##
|
|
412
|
+
## Tips
|
|
413
|
+
|
|
414
|
+
### CSS Support
|
|
409
415
|
|
|
410
416
|
The plugin automatically handles CSS code-splitting and injection for server components. This eliminates the need to manually call [`import.meta.viteRsc.loadCss()`](#importmetaviterscloadcss) in most cases.
|
|
411
417
|
|
|
@@ -439,11 +445,11 @@ export function Page() {
|
|
|
439
445
|
}
|
|
440
446
|
```
|
|
441
447
|
|
|
442
|
-
|
|
448
|
+
### Canary and Experimental channel releases
|
|
443
449
|
|
|
444
450
|
See https://github.com/vitejs/vite-plugin-react/pull/524 for how to install the package for React [canary](https://react.dev/community/versioning-policy#canary-channel) and [experimental](https://react.dev/community/versioning-policy#all-release-channels) usages.
|
|
445
451
|
|
|
446
|
-
|
|
452
|
+
### Using `@vitejs/plugin-rsc` as a framework package's `dependencies`
|
|
447
453
|
|
|
448
454
|
By default, `@vitejs/plugin-rsc` is expected to be used as `peerDependencies` similar to `react` and `react-dom`. When `@vitejs/plugin-rsc` is not available at the project root (e.g., in `node_modules/@vitejs/plugin-rsc`), you will see warnings like:
|
|
449
455
|
|
|
@@ -474,7 +480,7 @@ export default function myRscFrameworkPlugin() {
|
|
|
474
480
|
}
|
|
475
481
|
```
|
|
476
482
|
|
|
477
|
-
|
|
483
|
+
### Typescript
|
|
478
484
|
|
|
479
485
|
Types for global API are defined in `@vitejs/plugin-rsc/types`. For example, you can add it to `tsconfig.json` to have types for `import.meta.viteRsc` APIs:
|
|
480
486
|
|
|
@@ -494,6 +500,67 @@ import.meta.viteRsc.loadModule
|
|
|
494
500
|
|
|
495
501
|
See also [Vite documentation](https://vite.dev/guide/api-hmr.html#intellisense-for-typescript) for `vite/client` types.
|
|
496
502
|
|
|
503
|
+
### `server-only` and `client-only` import
|
|
504
|
+
|
|
505
|
+
<!-- references? -->
|
|
506
|
+
<!-- https://nextjs.org/docs/app/getting-started/server-and-client-components#preventing-environment-poisoning -->
|
|
507
|
+
<!-- https://overreacted.io/how-imports-work-in-rsc/ -->
|
|
508
|
+
|
|
509
|
+
You can use the `server-only` import to prevent accidentally importing server-only code into client bundles, which can expose sensitive server code in public static assets.
|
|
510
|
+
For example, the plugin will show an error `'server-only' cannot be imported in client build` for the following code:
|
|
511
|
+
|
|
512
|
+
- server-utils.js
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import 'server-only'
|
|
516
|
+
|
|
517
|
+
export async function getData() {
|
|
518
|
+
const res = await fetch('https://internal-service.com/data', {
|
|
519
|
+
headers: {
|
|
520
|
+
authorization: process.env.API_KEY,
|
|
521
|
+
},
|
|
522
|
+
})
|
|
523
|
+
return res.json()
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- client.js
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
'use client'
|
|
531
|
+
import { getData } from './server-utils.js' // ❌ 'server-only' cannot be imported in client build
|
|
532
|
+
...
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Similarly, the `client-only` import ensures browser-specific code isn't accidentally imported into server environments.
|
|
536
|
+
For example, the plugin will show an error `'client-only' cannot be imported in server build` for the following code:
|
|
537
|
+
|
|
538
|
+
- client-utils.js
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
import 'client-only'
|
|
542
|
+
|
|
543
|
+
export function getStorage(key) {
|
|
544
|
+
// This uses browser-only APIs
|
|
545
|
+
return window.localStorage.getItem(key)
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
- server.js
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { getStorage } from './client-utils.js' // ❌ 'client-only' cannot be imported in server build
|
|
553
|
+
|
|
554
|
+
export function ServerComponent() {
|
|
555
|
+
const data = getStorage("settings")
|
|
556
|
+
...
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Note that while there are official npm packages [`server-only`](https://www.npmjs.com/package/server-only) and [`client-only`](https://www.npmjs.com/package/client-only) created by React team, they don't need to be installed. The plugin internally overrides these imports and surfaces their runtime errors as build-time errors.
|
|
561
|
+
|
|
562
|
+
This build-time validation is enabled by default and can be disabled by setting `validateImports: false` in the plugin options.
|
|
563
|
+
|
|
497
564
|
## Credits
|
|
498
565
|
|
|
499
566
|
This project builds on fundamental techniques and insights from pioneering Vite RSC implementations.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import "./plugin-CZbI4rhS.js";
|
|
|
3
3
|
import "./transforms-D1-2JfCh.js";
|
|
4
4
|
import "./encryption-utils-BDwwcMVT.js";
|
|
5
5
|
import "./rpc-CUvSZurk.js";
|
|
6
|
-
import { getPluginApi, vitePluginRsc } from "./plugin-
|
|
6
|
+
import { getPluginApi, vitePluginRsc } from "./plugin-BI8kK-GE.js";
|
|
7
7
|
import "./cjs-WQBk0zA_.js";
|
|
8
8
|
import "./shared-AvKUASD5.js";
|
|
9
9
|
|
|
@@ -183,29 +183,82 @@ function validateImportPlugin() {
|
|
|
183
183
|
name: "rsc:validate-imports",
|
|
184
184
|
resolveId: {
|
|
185
185
|
order: "pre",
|
|
186
|
-
async handler(source,
|
|
186
|
+
async handler(source, _importer, options) {
|
|
187
187
|
if ("scan" in options && options.scan) return;
|
|
188
|
-
if (source === "client-only") {
|
|
189
|
-
if (this.environment.name === "rsc"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
moduleSideEffects: false
|
|
188
|
+
if (source === "client-only" || source === "server-only") {
|
|
189
|
+
if (source === "client-only" && this.environment.name === "rsc" || source === "server-only" && this.environment.name !== "rsc") return {
|
|
190
|
+
id: `\0virtual:vite-rsc/validate-imports/invalid/${source}`,
|
|
191
|
+
moduleSideEffects: true
|
|
193
192
|
};
|
|
194
|
-
}
|
|
195
|
-
if (source === "server-only") {
|
|
196
|
-
if (this.environment.name !== "rsc") throw new Error(`'server-only' cannot be imported in client build (importer: '${importer ?? "unknown"}', environment: ${this.environment.name})`);
|
|
197
193
|
return {
|
|
198
|
-
id: `\0virtual:vite-rsc/
|
|
194
|
+
id: `\0virtual:vite-rsc/validate-imports/valid/${source}`,
|
|
199
195
|
moduleSideEffects: false
|
|
200
196
|
};
|
|
201
197
|
}
|
|
202
198
|
}
|
|
203
199
|
},
|
|
204
200
|
load(id) {
|
|
205
|
-
if (id.startsWith("\0virtual:vite-rsc/
|
|
201
|
+
if (id.startsWith("\0virtual:vite-rsc/validate-imports/invalid/")) return `throw new Error("invalid import of '${id.slice(id.lastIndexOf("/") + 1)}'")`;
|
|
202
|
+
if (id.startsWith("\0virtual:vite-rsc/validate-imports/")) return `export {}`;
|
|
203
|
+
},
|
|
204
|
+
transform: {
|
|
205
|
+
order: "post",
|
|
206
|
+
async handler(_code, id) {
|
|
207
|
+
if (this.environment.mode === "dev") {
|
|
208
|
+
if (id.startsWith(`\0virtual:vite-rsc/validate-imports/invalid/`)) {
|
|
209
|
+
const chain = getImportChainDev(this.environment, id);
|
|
210
|
+
validateImportChain(chain, this.environment.name, this.environment.config.root);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
buildEnd() {
|
|
216
|
+
if (this.environment.mode === "build") {
|
|
217
|
+
const serverOnly = getImportChainBuild(this, "\0virtual:vite-rsc/validate-imports/invalid/server-only");
|
|
218
|
+
validateImportChain(serverOnly, this.environment.name, this.environment.config.root);
|
|
219
|
+
const clientOnly = getImportChainBuild(this, "\0virtual:vite-rsc/validate-imports/invalid/client-only");
|
|
220
|
+
validateImportChain(clientOnly, this.environment.name, this.environment.config.root);
|
|
221
|
+
}
|
|
206
222
|
}
|
|
207
223
|
};
|
|
208
224
|
}
|
|
225
|
+
function getImportChainDev(environment, id) {
|
|
226
|
+
const chain = [];
|
|
227
|
+
const recurse = (id$1) => {
|
|
228
|
+
if (chain.includes(id$1)) return;
|
|
229
|
+
const info = environment.moduleGraph.getModuleById(id$1);
|
|
230
|
+
if (!info) return;
|
|
231
|
+
chain.push(id$1);
|
|
232
|
+
const next = [...info.importers][0];
|
|
233
|
+
if (next && next.id) recurse(next.id);
|
|
234
|
+
};
|
|
235
|
+
recurse(id);
|
|
236
|
+
return chain;
|
|
237
|
+
}
|
|
238
|
+
function getImportChainBuild(ctx, id) {
|
|
239
|
+
const chain = [];
|
|
240
|
+
const recurse = (id$1) => {
|
|
241
|
+
if (chain.includes(id$1)) return;
|
|
242
|
+
const info = ctx.getModuleInfo(id$1);
|
|
243
|
+
if (!info) return;
|
|
244
|
+
chain.push(id$1);
|
|
245
|
+
const next = info.importers[0];
|
|
246
|
+
if (next) recurse(next);
|
|
247
|
+
};
|
|
248
|
+
recurse(id);
|
|
249
|
+
return chain;
|
|
250
|
+
}
|
|
251
|
+
function validateImportChain(chain, environmentName, root) {
|
|
252
|
+
if (chain.length === 0) return;
|
|
253
|
+
const id = chain[0];
|
|
254
|
+
const source = id.slice(id.lastIndexOf("/") + 1);
|
|
255
|
+
let result = `'${source}' cannot be imported in ${source === "server-only" ? "client" : "server"} build ('${environmentName}' environment):\n`;
|
|
256
|
+
result += chain.slice(1, 6).map((id$1, i) => " ".repeat(i + 1) + `imported by ${path.relative(root, id$1).replaceAll("\0", "")}\n`).join("");
|
|
257
|
+
if (chain.length > 6) result += " ".repeat(7) + "...\n";
|
|
258
|
+
const error = new Error(result);
|
|
259
|
+
if (chain[1]) Object.assign(error, { id: chain[1] });
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
209
262
|
|
|
210
263
|
//#endregion
|
|
211
264
|
//#region src/plugins/find-source-map-url.ts
|
|
@@ -813,6 +866,11 @@ import.meta.hot.on("rsc:update", () => {
|
|
|
813
866
|
document.querySelectorAll("vite-error-overlay").forEach((n) => n.close())
|
|
814
867
|
});
|
|
815
868
|
`;
|
|
869
|
+
code += `import.meta.hot.on("rsc:prune", ${(e) => {
|
|
870
|
+
document.querySelectorAll("link[rel='stylesheet']").forEach((node) => {
|
|
871
|
+
if (e.paths.includes(node.dataset.rscCssHref)) node.remove();
|
|
872
|
+
});
|
|
873
|
+
}});`;
|
|
816
874
|
return code;
|
|
817
875
|
}),
|
|
818
876
|
...vitePluginRscMinimal(rscPluginOptions, manager),
|
|
@@ -997,7 +1055,7 @@ function vitePluginUseClient(useClientPluginOptions, manager) {
|
|
|
997
1055
|
resolveId: {
|
|
998
1056
|
order: "pre",
|
|
999
1057
|
async handler(source, importer, options) {
|
|
1000
|
-
if (this.environment.name === serverEnvironmentName && bareImportRE.test(source)) {
|
|
1058
|
+
if (this.environment.name === serverEnvironmentName && bareImportRE.test(source) && !(source === "client-only" || source === "server-only")) {
|
|
1001
1059
|
const resolved = await this.resolve(source, importer, options);
|
|
1002
1060
|
if (resolved && resolved.id.includes("/node_modules/")) {
|
|
1003
1061
|
packageSources.set(resolved.id, source);
|
|
@@ -1408,6 +1466,19 @@ function vitePluginRscCss(rscCssOptions = {}, manager) {
|
|
|
1408
1466
|
},
|
|
1409
1467
|
{
|
|
1410
1468
|
name: "rsc:importer-resources",
|
|
1469
|
+
configureServer(server) {
|
|
1470
|
+
const hot = server.environments.rsc.hot;
|
|
1471
|
+
const original = hot.send;
|
|
1472
|
+
hot.send = function(...args) {
|
|
1473
|
+
const e = args[0];
|
|
1474
|
+
if (e && typeof e === "object" && e.type === "prune") server.environments.client.hot.send({
|
|
1475
|
+
type: "custom",
|
|
1476
|
+
event: "rsc:prune",
|
|
1477
|
+
data: e
|
|
1478
|
+
});
|
|
1479
|
+
return original.apply(this, args);
|
|
1480
|
+
};
|
|
1481
|
+
},
|
|
1411
1482
|
async transform(code, id) {
|
|
1412
1483
|
if (!code.includes("import.meta.viteRsc.loadCss")) return;
|
|
1413
1484
|
assert(this.environment.name === "rsc");
|
|
@@ -1507,7 +1578,8 @@ function generateResourcesCode(depsCode, manager) {
|
|
|
1507
1578
|
key: "css:" + href,
|
|
1508
1579
|
rel: "stylesheet",
|
|
1509
1580
|
precedence: "vite-rsc/importer-resources",
|
|
1510
|
-
href
|
|
1581
|
+
href,
|
|
1582
|
+
"data-rsc-css-href": href
|
|
1511
1583
|
})), RemoveDuplicateServerCss && React.createElement(RemoveDuplicateServerCss, { key: "remove-duplicate-css" })]);
|
|
1512
1584
|
};
|
|
1513
1585
|
};
|
package/dist/plugin.js
CHANGED
|
@@ -3,7 +3,7 @@ import "./plugin-CZbI4rhS.js";
|
|
|
3
3
|
import "./transforms-D1-2JfCh.js";
|
|
4
4
|
import "./encryption-utils-BDwwcMVT.js";
|
|
5
5
|
import "./rpc-CUvSZurk.js";
|
|
6
|
-
import { getPluginApi, transformRscCssExport, vitePluginRsc, vitePluginRscMinimal } from "./plugin-
|
|
6
|
+
import { getPluginApi, transformRscCssExport, vitePluginRsc, vitePluginRscMinimal } from "./plugin-BI8kK-GE.js";
|
|
7
7
|
import "./cjs-WQBk0zA_.js";
|
|
8
8
|
import "./shared-AvKUASD5.js";
|
|
9
9
|
|