From 4ae7f9bd4a8f9c0cfbf3612ffa41b0d017cc8878 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 5 Jan 2026 13:32:10 +0900 Subject: [PATCH 1/2] docs: add RSC cleanup analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the migration analysis for packages/react-server to use @vitejs/plugin-rsc. This includes import path mappings, files requiring updates, and utility functions that would need to be copied. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/2026-01-05-rsc-cleanup.md | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/2026-01-05-rsc-cleanup.md diff --git a/docs/2026-01-05-rsc-cleanup.md b/docs/2026-01-05-rsc-cleanup.md new file mode 100644 index 000000000..459e0c07d --- /dev/null +++ b/docs/2026-01-05-rsc-cleanup.md @@ -0,0 +1,88 @@ +# RSC Packages Cleanup + +## Summary + +Removed the following packages from this repository: + +- **`packages/rsc`** - Now maintained as Vite's official package [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) +- **`packages/rsc-react-router`** - Now officially supported by React Router team at https://reactrouter.com/how-to/react-server-components + +## react-server Migration to @vitejs/plugin-rsc + +`packages/react-server` can be migrated to use `@vitejs/plugin-rsc` with the following changes: + +### Import Path Mappings + +| @hiogawa/vite-rsc | @vitejs/plugin-rsc | +|---|---| +| `/react/browser` | `/browser` | +| `/react/rsc` | `/rsc` | +| `/react/ssr` | `/ssr` | +| `/core/plugin` (default export) | main export | + +### Files Requiring Updates (11 files) + +| File | Current Import | +|------|---------------| +| `src/entry/browser.tsx` | `@hiogawa/vite-rsc/react/browser` | +| `src/entry/server.tsx` | `@hiogawa/vite-rsc/react/rsc` | +| `src/entry/ssr.tsx` | `@hiogawa/vite-rsc/react/ssr` | +| `src/features/client-component/browser.tsx` | `@hiogawa/vite-rsc/react/browser` | +| `src/features/client-component/server.tsx` | `@hiogawa/vite-rsc/react/rsc` | +| `src/features/client-component/ssr.tsx` | `@hiogawa/vite-rsc/react/ssr` | +| `src/features/client-component/plugin.ts` | `@hiogawa/vite-rsc/vite-utils` | +| `src/features/server-action/browser.tsx` | `@hiogawa/vite-rsc/react/browser` | +| `src/features/server-action/server.tsx` | `@hiogawa/vite-rsc/react/rsc` | +| `src/features/server-action/ssr.tsx` | `@hiogawa/vite-rsc/react/ssr` | +| `src/plugin/index.ts` | `@hiogawa/vite-rsc/core/plugin`, `@hiogawa/vite-rsc/plugin` | + +### Utility Functions Not Exported by @vitejs/plugin-rsc + +These need to be copied into react-server or alternative implementations found: + +#### 1. `vitePluginFindSourceMapURL` (~60 lines) + +- **Source**: `packages/rsc/src/plugin.ts:1315-1375` +- **Purpose**: Provides source map URLs for React's error overlay +- **Used at**: `packages/react-server/src/plugin/index.ts:332` + +```typescript +export function vitePluginFindSourceMapURL(): Plugin[] { + return [ + { + name: "rsc:findSourceMapURL", + apply: "serve", + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + const url = new URL(req.url!, `http://localhost`); + if (url.pathname === "/__vite_rsc_findSourceMapURL") { + // ... handle source map URL lookup + } + next(); + }); + }, + }, + ]; +} +``` + +#### 2. `normalizeViteImportAnalysisUrl` (~15 lines + dependencies ~100 lines) + +- **Source**: `packages/rsc/src/vite-utils.ts` +- **Purpose**: Normalizes client reference IDs to match Vite's import analysis +- **Used at**: `packages/react-server/src/features/client-component/plugin.ts:121` + +This function depends on several helper utilities from the same file: +- `VALID_ID_PREFIX`, `NULL_BYTE_PLACEHOLDER`, `FS_PREFIX` +- `wrapId`, `unwrapId`, `withTrailingSlash`, `cleanUrl` +- `splitFileAndPostfix`, `slash`, `injectQuery`, `joinUrlSegments` +- `normalizeResolvedIdToUrl` + +### React Runtime APIs (Should Be Compatible) + +These are re-exports from react-server-dom and should work the same: + +- `renderToReadableStream`, `createFromReadableStream` +- `decodeReply`, `loadServerAction`, `decodeAction`, `decodeFormState` +- `encodeReply`, `createFromFetch`, `setServerCallback` +- `setRequireModule`, `registerClientReference`, `registerServerReference` From 50f8b53192cae69f301280e3b6779ad3d064f19d Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 5 Jan 2026 14:40:13 +0900 Subject: [PATCH 2/2] chore(rsc): remove packages/rsc and packages/rsc-react-router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - packages/rsc is now maintained as @vitejs/plugin-rsc - packages/rsc-react-router is now officially supported by React Router - Update packages/react-server to use published @hiogawa/vite-rsc@0.4.9 - Remove vite-ecosystem-ci scripts - Update GitHub workflows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yml | 48 - .github/workflows/pkg-pr-new.yml | 24 - README.md | 15 +- package.json | 4 - packages/react-server/misc/overrides.mjs | 1 - packages/react-server/package.json | 2 +- packages/rsc-react-router/README.md | 22 +- packages/rsc-react-router/package.json | 35 - packages/rsc-react-router/src/client.tsx | 19 - .../rsc-react-router/src/entry.browser.tsx | 38 - .../rsc-react-router/src/entry.rsc.single.tsx | 8 - packages/rsc-react-router/src/entry.rsc.tsx | 31 - packages/rsc-react-router/src/entry.ssr.tsx | 27 - packages/rsc-react-router/src/plugin.ts | 178 -- packages/rsc-react-router/src/types.d.ts | 11 - packages/rsc-react-router/tsconfig.json | 12 - packages/rsc-react-router/tsdown.config.ts | 17 - packages/rsc/CHANGELOG.md | 164 -- packages/rsc/README.md | 433 +--- packages/rsc/e2e/basic.test.ts | 937 --------- packages/rsc/e2e/fixture.ts | 190 -- packages/rsc/e2e/helper.ts | 44 - packages/rsc/e2e/react-router.test.ts | 183 -- packages/rsc/e2e/ssg.test.ts | 35 - packages/rsc/e2e/starter.test.ts | 94 - packages/rsc/e2e/tsconfig.json | 8 - packages/rsc/examples/basic/README.md | 9 - packages/rsc/examples/basic/package.json | 32 - .../rsc/examples/basic/public/favicon.ico | Bin 4286 -> 0 bytes packages/rsc/examples/basic/src/client.tsx | 3 - .../basic/src/routes/action-bind/client.tsx | 12 - .../basic/src/routes/action-bind/form.tsx | 16 - .../basic/src/routes/action-bind/server.tsx | 96 - .../routes/action-error/error-boundary.tsx | 40 - .../basic/src/routes/action-error/server.tsx | 19 - .../src/routes/action-from-client/action.tsx | 27 - .../src/routes/action-from-client/client.tsx | 25 - .../basic/src/routes/action-state/client.tsx | 19 - .../basic/src/routes/action-state/server.tsx | 21 - .../basic/src/routes/action/action.tsx | 16 - .../basic/src/routes/action/server.tsx | 15 - .../rsc/examples/basic/src/routes/client.tsx | 26 - .../routes/deps/client-in-server/client.tsx | 8 - .../routes/deps/client-in-server/server.tsx | 22 - .../routes/deps/server-in-client/client.tsx | 6 - .../routes/deps/server-in-server/server.tsx | 6 - .../src/routes/hmr-client-dep/client-dep.tsx | 3 - .../src/routes/hmr-client-dep/client.tsx | 16 - .../routes/module-invalidation/server-dep.tsx | 3 - .../src/routes/module-invalidation/server.tsx | 18 - .../basic/src/routes/payload/client.tsx | 29 - .../basic/src/routes/payload/server.tsx | 26 - .../rsc/examples/basic/src/routes/root.tsx | 97 - .../basic/src/routes/serialization/action.tsx | 6 - .../basic/src/routes/serialization/client.tsx | 18 - .../basic/src/routes/serialization/server.tsx | 27 - .../src/routes/style-client-no-ssr/client.css | 3 - .../src/routes/style-client-no-ssr/client.tsx | 7 - .../src/routes/style-client-no-ssr/server.tsx | 11 - .../src/routes/style-client/client-dep.css | 3 - .../src/routes/style-client/client-dep.tsx | 5 - .../basic/src/routes/style-client/client.css | 5 - .../src/routes/style-client/client.module.css | 3 - .../basic/src/routes/style-client/client.tsx | 17 - .../basic/src/routes/style-server/server.css | 3 - .../src/routes/style-server/server.module.css | 3 - .../basic/src/routes/style-server/server.tsx | 13 - .../basic/src/routes/tailwind/client.tsx | 5 - .../basic/src/routes/tailwind/server.tsx | 3 - .../src/routes/temporary-reference/action.tsx | 10 - .../src/routes/temporary-reference/client.tsx | 21 - .../basic/src/routes/use-cache/server.tsx | 105 - .../rsc/examples/basic/src/server.ssr.tsx | 1 - packages/rsc/examples/basic/src/server.tsx | 29 - packages/rsc/examples/basic/src/styles.css | 13 - .../examples/basic/src/use-cache-runtime.tsx | 94 - .../basic/test-dep/client-in-server/client.js | 8 - .../test-dep/client-in-server/package.json | 11 - .../basic/test-dep/client-in-server/server.js | 6 - .../test-dep/client-in-server2/client.js | 18 - .../test-dep/client-in-server2/package.json | 12 - .../test-dep/client-in-server2/server.js | 10 - .../basic/test-dep/server-in-client/client.js | 21 - .../test-dep/server-in-client/package.json | 11 - .../basic/test-dep/server-in-client/server.js | 12 - .../test-dep/server-in-server/package.json | 11 - .../basic/test-dep/server-in-server/server.js | 19 - packages/rsc/examples/basic/tsconfig.json | 17 - packages/rsc/examples/basic/vite.config.ts | 178 -- packages/rsc/examples/basic/wrangler.jsonc | 11 - packages/rsc/examples/hono/package.json | 15 - packages/rsc/examples/hono/public/favicon.ico | Bin 4286 -> 0 bytes packages/rsc/examples/hono/src/client.tsx | 36 - packages/rsc/examples/hono/src/server.tsx | 34 - packages/rsc/examples/hono/tsconfig.json | 17 - packages/rsc/examples/hono/vite.config.ts | 20 - packages/rsc/examples/react-router/README.md | 30 - .../rsc/examples/react-router/app/paper.css | 150 -- .../rsc/examples/react-router/app/root.tsx | 56 - .../rsc/examples/react-router/app/routes.ts | 6 - .../react-router/app/routes/about.tsx | 20 - .../react-router/app/routes/client.tsx | 22 - .../react-router/app/routes/home.actions.ts | 7 - .../react-router/app/routes/home.client.tsx | 12 - .../examples/react-router/app/routes/home.css | 3 - .../examples/react-router/app/routes/home.tsx | 56 - .../react-router/app/routes/root.client.tsx | 44 - .../app/routes/test-action-state/client.tsx | 19 - .../app/routes/test-action-state/server.tsx | 20 - .../rsc/examples/react-router/app/styles.css | 32 - .../examples/react-router/cf/entry.rsc.tsx | 13 - .../examples/react-router/cf/entry.ssr.tsx | 13 - .../examples/react-router/cf/vite.config.ts | 72 - .../react-router/cf/wrangler.rsc.jsonc | 8 - .../react-router/cf/wrangler.ssr.jsonc | 9 - .../rsc/examples/react-router/package.json | 34 - .../examples/react-router/public/favicon.ico | Bin 15086 -> 0 bytes .../react-router-vite/entry.browser.tsx | 38 - .../react-router-vite/entry.rsc.tsx | 31 - .../react-router-vite/entry.ssr.single.tsx | 8 - .../react-router-vite/entry.ssr.tsx | 27 - .../react-router/react-router-vite/plugin.ts | 119 -- .../react-router-vite/server-hmr.tsx | 19 - .../react-router/react-router-vite/types.d.ts | 11 - .../react-router/react-router.config.ts | 5 - .../rsc/examples/react-router/tsconfig.json | 16 - .../rsc/examples/react-router/vite.config.ts | 33 - packages/rsc/examples/ssg/README.md | 15 - packages/rsc/examples/ssg/package.json | 25 - packages/rsc/examples/ssg/public/favicon.ico | Bin 4286 -> 0 bytes packages/rsc/examples/ssg/src/counter.tsx | 11 - .../ssg/src/framework/entry.browser.tsx | 97 - .../examples/ssg/src/framework/entry.rsc.tsx | 38 - .../examples/ssg/src/framework/entry.ssr.tsx | 31 - .../rsc/examples/ssg/src/framework/shared.tsx | 7 - .../rsc/examples/ssg/src/posts/counter.mdx | 7 - packages/rsc/examples/ssg/src/posts/oxc.mdx | 3 - .../rsc/examples/ssg/src/posts/rolldown.mdx | 3 - packages/rsc/examples/ssg/src/posts/vite.mdx | 3 - .../rsc/examples/ssg/src/posts/vitest.mdx | 3 - packages/rsc/examples/ssg/src/react.d.ts | 3 - packages/rsc/examples/ssg/src/root.tsx | 70 - packages/rsc/examples/ssg/tsconfig.json | 18 - packages/rsc/examples/ssg/vite.config.ts | 96 - .../rsc/examples/starter-cf-single/README.md | 23 - .../examples/starter-cf-single/package.json | 25 - .../starter-cf-single/public/vite.svg | 1 - .../examples/starter-cf-single/src/action.tsx | 11 - .../starter-cf-single/src/assets/react.svg | 1 - .../examples/starter-cf-single/src/client.tsx | 13 - .../src/framework/entry.browser.tsx | 127 -- .../src/framework/entry.rsc.tsx | 95 - .../src/framework/entry.ssr.tsx | 55 - .../src/framework/react.d.ts | 3 - .../examples/starter-cf-single/src/index.css | 112 - .../examples/starter-cf-single/src/root.tsx | 70 - .../examples/starter-cf-single/tsconfig.json | 18 - .../examples/starter-cf-single/vite.config.ts | 46 - .../examples/starter-cf-single/wrangler.jsonc | 8 - packages/rsc/examples/starter/README.md | 36 - packages/rsc/examples/starter/package.json | 24 - packages/rsc/examples/starter/public/vite.svg | 1 - packages/rsc/examples/starter/src/action.tsx | 11 - .../rsc/examples/starter/src/assets/react.svg | 1 - packages/rsc/examples/starter/src/client.tsx | 13 - .../starter/src/framework/entry.browser.tsx | 127 -- .../starter/src/framework/entry.rsc.tsx | 103 - .../starter/src/framework/entry.ssr.tsx | 53 - .../examples/starter/src/framework/react.d.ts | 3 - packages/rsc/examples/starter/src/index.css | 112 - packages/rsc/examples/starter/src/root.tsx | 70 - packages/rsc/examples/starter/tsconfig.json | 18 - packages/rsc/examples/starter/vite.config.ts | 73 - packages/rsc/package.json | 46 - packages/rsc/playwright.config.ts | 34 - packages/rsc/src/browser.ts | 23 - packages/rsc/src/core/browser.ts | 19 - packages/rsc/src/core/plugin.ts | 42 - packages/rsc/src/core/rsc.ts | 130 -- packages/rsc/src/core/shared.ts | 25 - packages/rsc/src/core/ssr.ts | 27 - packages/rsc/src/extra/browser.tsx | 126 -- packages/rsc/src/extra/rsc.tsx | 93 - packages/rsc/src/extra/ssr.tsx | 55 - packages/rsc/src/index.ts | 2 - packages/rsc/src/plugin.ts | 1803 ----------------- packages/rsc/src/react/browser.ts | 62 - packages/rsc/src/react/rsc.ts | 85 - packages/rsc/src/react/ssr.ts | 22 - packages/rsc/src/rsc-html-stream/browser.ts | 4 - packages/rsc/src/rsc-html-stream/ssr.ts | 7 - packages/rsc/src/rsc.tsx | 33 - packages/rsc/src/ssr.tsx | 65 - packages/rsc/src/types/index.ts | 27 - packages/rsc/src/types/react.ts | 3 - packages/rsc/src/types/virtual.d.ts | 22 - packages/rsc/src/utils/encryption-runtime.ts | 48 - packages/rsc/src/utils/encryption-utils.ts | 115 -- packages/rsc/src/utils/rpc.ts | 76 - packages/rsc/src/vite-utils.ts | 126 -- packages/rsc/tsconfig.json | 13 - packages/rsc/tsdown.config.ts | 49 - packages/rsc/types/index.d.ts | 19 - packages/rsc/types/virtual.d.ts | 5 - pnpm-lock.yaml | 1144 +---------- 205 files changed, 41 insertions(+), 10987 deletions(-) delete mode 100644 packages/rsc-react-router/package.json delete mode 100644 packages/rsc-react-router/src/client.tsx delete mode 100644 packages/rsc-react-router/src/entry.browser.tsx delete mode 100644 packages/rsc-react-router/src/entry.rsc.single.tsx delete mode 100644 packages/rsc-react-router/src/entry.rsc.tsx delete mode 100644 packages/rsc-react-router/src/entry.ssr.tsx delete mode 100644 packages/rsc-react-router/src/plugin.ts delete mode 100644 packages/rsc-react-router/src/types.d.ts delete mode 100644 packages/rsc-react-router/tsconfig.json delete mode 100644 packages/rsc-react-router/tsdown.config.ts delete mode 100644 packages/rsc/CHANGELOG.md delete mode 100644 packages/rsc/e2e/basic.test.ts delete mode 100644 packages/rsc/e2e/fixture.ts delete mode 100644 packages/rsc/e2e/helper.ts delete mode 100644 packages/rsc/e2e/react-router.test.ts delete mode 100644 packages/rsc/e2e/ssg.test.ts delete mode 100644 packages/rsc/e2e/starter.test.ts delete mode 100644 packages/rsc/e2e/tsconfig.json delete mode 100644 packages/rsc/examples/basic/README.md delete mode 100644 packages/rsc/examples/basic/package.json delete mode 100644 packages/rsc/examples/basic/public/favicon.ico delete mode 100644 packages/rsc/examples/basic/src/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-bind/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-bind/form.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-bind/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-error/error-boundary.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-error/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-from-client/action.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-from-client/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-state/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action-state/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action/action.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/action/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/deps/client-in-server/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/deps/client-in-server/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/deps/server-in-client/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/deps/server-in-server/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/hmr-client-dep/client-dep.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/hmr-client-dep/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/module-invalidation/server-dep.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/module-invalidation/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/payload/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/payload/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/root.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/serialization/action.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/serialization/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/serialization/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/style-client-no-ssr/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/style-client/client-dep.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-client/client-dep.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/style-client/client.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-client/client.module.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-client/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/style-server/server.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-server/server.module.css delete mode 100644 packages/rsc/examples/basic/src/routes/style-server/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/tailwind/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/tailwind/server.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/temporary-reference/action.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/temporary-reference/client.tsx delete mode 100644 packages/rsc/examples/basic/src/routes/use-cache/server.tsx delete mode 100644 packages/rsc/examples/basic/src/server.ssr.tsx delete mode 100644 packages/rsc/examples/basic/src/server.tsx delete mode 100644 packages/rsc/examples/basic/src/styles.css delete mode 100644 packages/rsc/examples/basic/src/use-cache-runtime.tsx delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server/client.js delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server/package.json delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server/server.js delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server2/client.js delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server2/package.json delete mode 100644 packages/rsc/examples/basic/test-dep/client-in-server2/server.js delete mode 100644 packages/rsc/examples/basic/test-dep/server-in-client/client.js delete mode 100644 packages/rsc/examples/basic/test-dep/server-in-client/package.json delete mode 100644 packages/rsc/examples/basic/test-dep/server-in-client/server.js delete mode 100644 packages/rsc/examples/basic/test-dep/server-in-server/package.json delete mode 100644 packages/rsc/examples/basic/test-dep/server-in-server/server.js delete mode 100644 packages/rsc/examples/basic/tsconfig.json delete mode 100644 packages/rsc/examples/basic/vite.config.ts delete mode 100644 packages/rsc/examples/basic/wrangler.jsonc delete mode 100644 packages/rsc/examples/hono/package.json delete mode 100644 packages/rsc/examples/hono/public/favicon.ico delete mode 100644 packages/rsc/examples/hono/src/client.tsx delete mode 100644 packages/rsc/examples/hono/src/server.tsx delete mode 100644 packages/rsc/examples/hono/tsconfig.json delete mode 100644 packages/rsc/examples/hono/vite.config.ts delete mode 100644 packages/rsc/examples/react-router/README.md delete mode 100644 packages/rsc/examples/react-router/app/paper.css delete mode 100644 packages/rsc/examples/react-router/app/root.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes.ts delete mode 100644 packages/rsc/examples/react-router/app/routes/about.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/client.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/home.actions.ts delete mode 100644 packages/rsc/examples/react-router/app/routes/home.client.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/home.css delete mode 100644 packages/rsc/examples/react-router/app/routes/home.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/root.client.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/test-action-state/client.tsx delete mode 100644 packages/rsc/examples/react-router/app/routes/test-action-state/server.tsx delete mode 100644 packages/rsc/examples/react-router/app/styles.css delete mode 100644 packages/rsc/examples/react-router/cf/entry.rsc.tsx delete mode 100644 packages/rsc/examples/react-router/cf/entry.ssr.tsx delete mode 100644 packages/rsc/examples/react-router/cf/vite.config.ts delete mode 100644 packages/rsc/examples/react-router/cf/wrangler.rsc.jsonc delete mode 100644 packages/rsc/examples/react-router/cf/wrangler.ssr.jsonc delete mode 100644 packages/rsc/examples/react-router/package.json delete mode 100644 packages/rsc/examples/react-router/public/favicon.ico delete mode 100644 packages/rsc/examples/react-router/react-router-vite/entry.browser.tsx delete mode 100644 packages/rsc/examples/react-router/react-router-vite/entry.rsc.tsx delete mode 100644 packages/rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx delete mode 100644 packages/rsc/examples/react-router/react-router-vite/entry.ssr.tsx delete mode 100644 packages/rsc/examples/react-router/react-router-vite/plugin.ts delete mode 100644 packages/rsc/examples/react-router/react-router-vite/server-hmr.tsx delete mode 100644 packages/rsc/examples/react-router/react-router-vite/types.d.ts delete mode 100644 packages/rsc/examples/react-router/react-router.config.ts delete mode 100644 packages/rsc/examples/react-router/tsconfig.json delete mode 100644 packages/rsc/examples/react-router/vite.config.ts delete mode 100644 packages/rsc/examples/ssg/README.md delete mode 100644 packages/rsc/examples/ssg/package.json delete mode 100644 packages/rsc/examples/ssg/public/favicon.ico delete mode 100644 packages/rsc/examples/ssg/src/counter.tsx delete mode 100644 packages/rsc/examples/ssg/src/framework/entry.browser.tsx delete mode 100644 packages/rsc/examples/ssg/src/framework/entry.rsc.tsx delete mode 100644 packages/rsc/examples/ssg/src/framework/entry.ssr.tsx delete mode 100644 packages/rsc/examples/ssg/src/framework/shared.tsx delete mode 100644 packages/rsc/examples/ssg/src/posts/counter.mdx delete mode 100644 packages/rsc/examples/ssg/src/posts/oxc.mdx delete mode 100644 packages/rsc/examples/ssg/src/posts/rolldown.mdx delete mode 100644 packages/rsc/examples/ssg/src/posts/vite.mdx delete mode 100644 packages/rsc/examples/ssg/src/posts/vitest.mdx delete mode 100644 packages/rsc/examples/ssg/src/react.d.ts delete mode 100644 packages/rsc/examples/ssg/src/root.tsx delete mode 100644 packages/rsc/examples/ssg/tsconfig.json delete mode 100644 packages/rsc/examples/ssg/vite.config.ts delete mode 100644 packages/rsc/examples/starter-cf-single/README.md delete mode 100644 packages/rsc/examples/starter-cf-single/package.json delete mode 100644 packages/rsc/examples/starter-cf-single/public/vite.svg delete mode 100644 packages/rsc/examples/starter-cf-single/src/action.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/src/assets/react.svg delete mode 100644 packages/rsc/examples/starter-cf-single/src/client.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/src/framework/entry.browser.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/src/framework/entry.rsc.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/src/framework/entry.ssr.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/src/framework/react.d.ts delete mode 100644 packages/rsc/examples/starter-cf-single/src/index.css delete mode 100644 packages/rsc/examples/starter-cf-single/src/root.tsx delete mode 100644 packages/rsc/examples/starter-cf-single/tsconfig.json delete mode 100644 packages/rsc/examples/starter-cf-single/vite.config.ts delete mode 100644 packages/rsc/examples/starter-cf-single/wrangler.jsonc delete mode 100644 packages/rsc/examples/starter/README.md delete mode 100644 packages/rsc/examples/starter/package.json delete mode 100644 packages/rsc/examples/starter/public/vite.svg delete mode 100644 packages/rsc/examples/starter/src/action.tsx delete mode 100644 packages/rsc/examples/starter/src/assets/react.svg delete mode 100644 packages/rsc/examples/starter/src/client.tsx delete mode 100644 packages/rsc/examples/starter/src/framework/entry.browser.tsx delete mode 100644 packages/rsc/examples/starter/src/framework/entry.rsc.tsx delete mode 100644 packages/rsc/examples/starter/src/framework/entry.ssr.tsx delete mode 100644 packages/rsc/examples/starter/src/framework/react.d.ts delete mode 100644 packages/rsc/examples/starter/src/index.css delete mode 100644 packages/rsc/examples/starter/src/root.tsx delete mode 100644 packages/rsc/examples/starter/tsconfig.json delete mode 100644 packages/rsc/examples/starter/vite.config.ts delete mode 100644 packages/rsc/package.json delete mode 100644 packages/rsc/playwright.config.ts delete mode 100644 packages/rsc/src/browser.ts delete mode 100644 packages/rsc/src/core/browser.ts delete mode 100644 packages/rsc/src/core/plugin.ts delete mode 100644 packages/rsc/src/core/rsc.ts delete mode 100644 packages/rsc/src/core/shared.ts delete mode 100644 packages/rsc/src/core/ssr.ts delete mode 100644 packages/rsc/src/extra/browser.tsx delete mode 100644 packages/rsc/src/extra/rsc.tsx delete mode 100644 packages/rsc/src/extra/ssr.tsx delete mode 100644 packages/rsc/src/index.ts delete mode 100644 packages/rsc/src/plugin.ts delete mode 100644 packages/rsc/src/react/browser.ts delete mode 100644 packages/rsc/src/react/rsc.ts delete mode 100644 packages/rsc/src/react/ssr.ts delete mode 100644 packages/rsc/src/rsc-html-stream/browser.ts delete mode 100644 packages/rsc/src/rsc-html-stream/ssr.ts delete mode 100644 packages/rsc/src/rsc.tsx delete mode 100644 packages/rsc/src/ssr.tsx delete mode 100644 packages/rsc/src/types/index.ts delete mode 100644 packages/rsc/src/types/react.ts delete mode 100644 packages/rsc/src/types/virtual.d.ts delete mode 100644 packages/rsc/src/utils/encryption-runtime.ts delete mode 100644 packages/rsc/src/utils/encryption-utils.ts delete mode 100644 packages/rsc/src/utils/rpc.ts delete mode 100644 packages/rsc/src/vite-utils.ts delete mode 100644 packages/rsc/tsconfig.json delete mode 100644 packages/rsc/tsdown.config.ts delete mode 100644 packages/rsc/types/index.d.ts delete mode 100644 packages/rsc/types/virtual.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87907c280..55ca6f3b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,54 +45,6 @@ jobs: - run: pnpm -C packages/import-attributes build - run: pnpm -C packages/import-attributes test-e2e - # superseded by @vitejs/plugin-rsc - # https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc - # test-rsc: - # name: test-rsc (${{ matrix.os }} / ${{ matrix.browser }}) - # runs-on: ${{ matrix.os }} - # strategy: - # # TODO: shard? - # matrix: - # os: [ubuntu-latest, macos-latest, windows-latest] - # browser: [chromium] - # include: - # - os: ubuntu-latest - # browser: firefox - # - os: macos-latest - # browser: webkit - # fail-fast: false - # steps: - # - uses: actions/checkout@v4 - # - uses: ./.github/actions/setup - # with: - # build: false - # playwright: false - # - run: pnpm vite-ecosystem-ci:build - # - run: pnpm exec playwright install ${{ matrix.browser }} - # - run: pnpm -C packages/rsc test-e2e-ci --project=${{ matrix.browser }} - # env: - # TEST_ISOLATED: true - # - uses: actions/upload-artifact@v4 - # if: always() - # with: - # name: test-results-${{ matrix.os }}-${{ matrix.browser }} - # path: | - # packages/rsc/test-results - - # test-rolldown: - # name: test-rolldown - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - uses: ./.github/actions/setup - # with: - # build: false - # - run: pnpm vite-ecosystem-ci:build - # - run: pnpm i -w vite@npm:rolldown-vite@latest && pnpm i --no-frozen-lockfile - # - run: pnpm -C packages/rsc test-e2e --grep-invert cloudflare - # env: - # TEST_ISOLATED: true - # test-react-server-basic: # runs-on: ubuntu-latest # steps: diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index c0c2e9d31..427b8a7cd 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -13,26 +13,6 @@ jobs: with: playwright: false - # stable - - run: | - cp -rf packages/rsc packages/rsc-stable - - # canary - - run: | - pnpm i -w react@canary react-dom@canary react-server-dom-webpack@canary - pnpm i --no-frozen-lockfile - pnpm -C packages/rsc build - cp -rf packages/rsc packages/rsc-canary - sed -i 's#"name": "@hiogawa/vite-rsc"#"name": "@hiogawa/vite-rsc-canary"#' packages/rsc-canary/package.json - - # experimental - - run: | - pnpm i -w react@experimental react-dom@experimental react-server-dom-webpack@experimental - pnpm i --no-frozen-lockfile - pnpm -C packages/rsc build - cp -rf packages/rsc packages/rsc-experimental - sed -i 's#"name": "@hiogawa/vite-rsc"#"name": "@hiogawa/vite-rsc-experimental"#' packages/rsc-experimental/package.json - # strip prepack to avoid duplicate builds - run: | for pkg in packages/*/package.json; do @@ -43,10 +23,6 @@ jobs: pnpx pkg-pr-new publish --comment=off \ packages/react-server \ packages/react-server-next \ - packages/rsc-stable \ - packages/rsc-canary \ - packages/rsc-experimental \ - packages/rsc-react-router \ packages/transforms \ packages/import-attributes \ packages/vite-plugin-ssr-middleware \ diff --git a/README.md b/README.md index 172726a2d..58b479c52 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,6 @@ Random collection of vite plugins ## plugins -### [`@hiogawa/vite-rsc`](./packages/rsc) - -> [!important] -> `@hiogawa/vite-rsc` is now maintained as Vite's official package [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc). - -Framework-less Vite RSC plugin - ### [`@hiogawa/react-server`](./packages/react-server) Next.js-like RSC framework on Vite @@ -24,9 +17,9 @@ and [`@vavite/expose-vite-dev-server`](https://github.com/cyco130/vavite/tree/ma ### How to release a package -Example: `packages/rsc` +Example: `packages/transforms` -1. Manually bump `packages/rsc/package.json` version -2. Update changelog via `pnpm changelog packages/rsc` +1. Manually bump `packages/transforms/package.json` version +2. Update changelog via `pnpm changelog packages/transforms` 3. Create a PR and merge to main -4. Locally run `pnpm release packages/rsc` +4. Locally run `pnpm release packages/transforms` diff --git a/package.json b/package.json index 51e113330..e6f90e530 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,6 @@ "tsc": "tsc -b packages/*/tsconfig.json packages/*/e2e/tsconfig.json packages/*/examples/*/tsconfig.json", "tsc-dev": "pnpm tsc --watch --preserveWatchOutput", "test": "pnpm -r --sequential test run", - "vite-ecosystem-ci:build": "pnpm -r --filter '@hiogawa/vite-rsc...' build", - "vite-ecosystem-ci:before-test": "playwright install chromium", - "vite-ecosystem-ci:test": "pnpm -C packages/rsc test-e2e", "lint": "biome check --write .", "lint-check": "biome check ." }, @@ -50,7 +47,6 @@ "react-dom": "$react-dom", "react-server-dom-webpack": "$react-server-dom-webpack", "@hiogawa/react-server": "workspace:*", - "@hiogawa/vite-rsc": "workspace:*", "@playwright/test": "$@playwright/test", "@types/react": "$@types/react", "@types/react-dom": "$@types/react-dom", diff --git a/packages/react-server/misc/overrides.mjs b/packages/react-server/misc/overrides.mjs index afa3aa4de..2daae4765 100644 --- a/packages/react-server/misc/overrides.mjs +++ b/packages/react-server/misc/overrides.mjs @@ -8,7 +8,6 @@ const deps = ["react-server", "transforms", "vite-plugin-ssr-middleware"]; const overrides = Object.fromEntries( deps.map((dep) => [`@hiogawa/${dep}`, `file:${resolve(packages, dep)}`]), ); -overrides["@hiogawa/vite-rsc"] = `file:${resolve(packages, "rsc")}`; editJson(argv[2], (pkg) => { Object.assign(((pkg.pnpm ??= {}).overrides ??= {}), overrides); diff --git a/packages/react-server/package.json b/packages/react-server/package.json index 511cdd510..8de8f4097 100644 --- a/packages/react-server/package.json +++ b/packages/react-server/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@hiogawa/transforms": "workspace:*", - "@hiogawa/vite-rsc": "workspace:*", + "@hiogawa/vite-rsc": "^0.4.9", "es-module-lexer": "^1.6.0", "fast-glob": "^3.3.3", "vitefu": "^1.0.5" diff --git a/packages/rsc-react-router/README.md b/packages/rsc-react-router/README.md index add8e5ca9..cc1dafac6 100644 --- a/packages/rsc-react-router/README.md +++ b/packages/rsc-react-router/README.md @@ -1,20 +1,2 @@ -# @hiogawa/vite-rsc-react-router - -This is a package to integrate [Experimental React Router RSC support](https://remix.run/blog/rsc-preview) on Vite using [`@hiogawa/vite-rsc`](https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc). It provides a basic [`routes.ts`](https://reactrouter.com/explanation/special-files#routests) support. See [rsc-movies](https://github.com/hi-ogawa/rsc-movies/) for an example. - -## Usage - -- `vite.config.ts` - -```js -import rsc from "@hiogawa/vite-rsc/plugin"; -import { reactRouter } from "@hiogawa/vite-rsc-react-router/plugin"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [ - reactRouter(), - rsc(), - ], -}); -``` +React Server Components are now officially supported by React Router. +See https://reactrouter.com/how-to/react-server-components diff --git a/packages/rsc-react-router/package.json b/packages/rsc-react-router/package.json deleted file mode 100644 index 48002dd09..000000000 --- a/packages/rsc-react-router/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-react-router", - "version": "0.0.0", - "homepage": "https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc-react-router", - "repository": { - "type": "git", - "url": "git+https://github.com/hi-ogawa/vite-plugins.git", - "directory": "packages/rsc-react-router" - }, - "license": "MIT", - "type": "module", - "exports": { - "./*": "./dist/*.js", - "./package.json": "./package.json" - }, - "files": ["dist"], - "scripts": { - "dev": "tsdown --sourcemap --watch src", - "build": "tsdown", - "prepack": "tsdown --clean" - }, - "devDependencies": { - "@hiogawa/vite-rsc": "workspace:*", - "@react-router/dev": "0.0.0-experimental-23decd7bc", - "react-router": "0.0.0-experimental-23decd7bc" - }, - "peerDependencies": { - "@hiogawa/vite-rsc": "*", - "@react-router/dev": "*", - "react": "*", - "react-dom": "*", - "react-router": "*", - "vite": "*" - } -} diff --git a/packages/rsc-react-router/src/client.tsx b/packages/rsc-react-router/src/client.tsx deleted file mode 100644 index 2e228408f..000000000 --- a/packages/rsc-react-router/src/client.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -import React from "react"; -import { useNavigate } from "react-router"; - -export function ServerHmr() { - if (import.meta.hot) { - const navigate = useNavigate(); - React.useEffect(() => { - const refetch = () => - navigate(window.location.pathname, { replace: true }); - import.meta.hot!.on("rsc:update", refetch); - return () => { - import.meta.hot!.off("rsc:update", refetch); - }; - }, [navigate]); - } - return null; -} diff --git a/packages/rsc-react-router/src/entry.browser.tsx b/packages/rsc-react-router/src/entry.browser.tsx deleted file mode 100644 index 8e730eaa7..000000000 --- a/packages/rsc-react-router/src/entry.browser.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - createFromReadableStream, - createTemporaryReferenceSet, - encodeReply, - setServerCallback, -} from "@hiogawa/vite-rsc/browser"; -import * as React from "react"; -import { hydrateRoot } from "react-dom/client"; -import { - unstable_RSCHydratedRouter as RSCHydratedRouter, - type unstable_RSCPayload as RSCPayload, - unstable_createCallServer as createCallServer, - unstable_getRSCStream as getRSCStream, -} from "react-router"; - -setServerCallback( - createCallServer({ - createFromReadableStream, - encodeReply, - createTemporaryReferenceSet, - }), -); - -createFromReadableStream(getRSCStream()).then( - (payload: RSCPayload) => { - React.startTransition(() => { - hydrateRoot( - document, - - - , - ); - }); - }, -); diff --git a/packages/rsc-react-router/src/entry.rsc.single.tsx b/packages/rsc-react-router/src/entry.rsc.single.tsx deleted file mode 100644 index ed8d4d1f7..000000000 --- a/packages/rsc-react-router/src/entry.rsc.single.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { fetchServer } from "./entry.rsc"; - -export default async function handler(requrest: Request): Promise { - const ssr = await import.meta.viteRsc.loadModule< - typeof import("./entry.ssr") - >("ssr", "index"); - return ssr.default(requrest, fetchServer); -} diff --git a/packages/rsc-react-router/src/entry.rsc.tsx b/packages/rsc-react-router/src/entry.rsc.tsx deleted file mode 100644 index 444a3fcc2..000000000 --- a/packages/rsc-react-router/src/entry.rsc.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - createTemporaryReferenceSet, - decodeAction, - decodeReply, - loadServerAction, - renderToReadableStream, -} from "@hiogawa/vite-rsc/rsc"; -import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router"; - -import routes from "virtual:react-router-routes"; - -export async function fetchServer(request: Request): Promise { - return await matchRSCServerRequest({ - createTemporaryReferenceSet, - decodeReply, - decodeAction, - loadServerAction, - request, - routes, - generateResponse(match, options) { - return new Response(renderToReadableStream(match.payload, options), { - status: match.statusCode, - headers: match.headers, - }); - }, - }); -} - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc-react-router/src/entry.ssr.tsx b/packages/rsc-react-router/src/entry.ssr.tsx deleted file mode 100644 index bea77e484..000000000 --- a/packages/rsc-react-router/src/entry.ssr.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { createFromReadableStream } from "@hiogawa/vite-rsc/ssr"; -import * as ReactDomServer from "react-dom/server.edge"; -import { - unstable_RSCStaticRouter as RSCStaticRouter, - unstable_routeRSCServerRequest as routeRSCServerRequest, -} from "react-router"; - -export default async function handler( - request: Request, - fetchServer: (request: Request) => Promise, -): Promise { - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - return routeRSCServerRequest({ - request, - fetchServer, - createFromReadableStream: (body) => createFromReadableStream(body), - renderHTML(getPayload) { - return ReactDomServer.renderToReadableStream( - , - { - bootstrapScriptContent, - }, - ); - }, - }); -} diff --git a/packages/rsc-react-router/src/plugin.ts b/packages/rsc-react-router/src/plugin.ts deleted file mode 100644 index 49a213f8f..000000000 --- a/packages/rsc-react-router/src/plugin.ts +++ /dev/null @@ -1,178 +0,0 @@ -import assert from "node:assert/strict"; -import * as childProcess from "node:child_process"; -import path from "node:path"; -import type { Config } from "@react-router/dev/config"; -import type { RouteConfigEntry } from "@react-router/dev/routes"; -import { - type EnvironmentOptions, - type Plugin, - createIdResolver, - runnerImport, -} from "vite"; - -const PKG_NAME = "@hiogawa/vite-rsc-react-router"; - -export function reactRouter(options?: { - typegen?: boolean; -}): Plugin[] { - let idResolver: ReturnType; - let typegenProcess: childProcess.ChildProcess | undefined; - - return [ - { - name: "react-router:config", - config() { - const toEnvironmentOption = (entry: string) => - ({ - build: { - rollupOptions: { - input: { - index: `${PKG_NAME}/${entry}`, - }, - }, - }, - }) satisfies EnvironmentOptions; - return { - environments: { - client: toEnvironmentOption("entry.browser"), - ssr: toEnvironmentOption("entry.ssr"), - rsc: toEnvironmentOption("entry.rsc.single"), - }, - }; - }, - configEnvironment(name, _config, _env) { - return { - resolve: { - noExternal: [PKG_NAME], - }, - optimizeDeps: { - include: - name === "client" - ? ["react-router", "react-router/internal/react-server-client"] - : [], - exclude: [PKG_NAME], - }, - }; - }, - configResolved(config) { - idResolver = createIdResolver(config); - }, - resolveId(source) { - if (source === "virtual:react-router-routes") { - return "\0" + source; - } - }, - async load(id) { - if (id === "\0virtual:react-router-routes") { - const findFile = (id: string) => idResolver(this.environment, id); - const config = await readReactRouterConfig(findFile); - this.addWatchFile(config.configFile); - this.addWatchFile(config.routesFile); - const code = generateRoutesCode(config); - return code; - } - }, - }, - { - name: "react-router:typegen", - apply: (_config, env) => env.command === "serve" && !!options?.typegen, - buildStart() { - typegenProcess?.kill(); - typegenProcess = childProcess.spawn("react-router", [ - "typegen", - "--watch", - ]); - }, - buildEnd() { - typegenProcess?.kill(); - typegenProcess = undefined; - }, - }, - ]; -} - -async function readReactRouterConfig( - findFile: (id: string) => Promise, -) { - // find react-router.config.ts - const configFile = await findFile("./react-router.config"); - assert(configFile, "Cannot find 'react-router.config' file"); - const configImport = await runnerImport<{ default: Config }>(configFile); - const appDirectory = path.resolve( - configImport.module.default.appDirectory ?? "app", - ); - - // find routes.ts - const routesFile = await findFile(path.join(appDirectory, "routes")); - assert(routesFile, "Cannot find 'routes' file"); - const routesImport = await runnerImport<{ - default: RouteConfigEntry[]; - }>(routesFile); - - // find root.tsx - const rootFile = await findFile(path.join(appDirectory, "root")); - assert(rootFile, "Cannot find 'root' file"); - - const routes = [ - { - id: "root", - path: "", - file: rootFile + "?vite-rsc-css-export=Layout", - children: routesImport.module.default, - }, - ]; - - return { configFile, routesFile, appDirectory, routes }; -} - -// copied from -// https://github.com/jacob-ebey/parcel-plugin-react-router/blob/9385be813534537dfb0fe640a3e5c5607be3b61d/packages/resolver/src/resolver.ts - -function generateRoutesCode(config: { - appDirectory: string; - routes: RouteConfigEntry[]; -}) { - let code = "export default ["; - const closeRouteSymbol = Symbol("CLOSE_ROUTE"); - let stack: Array = [ - ...config.routes, - ]; - while (stack.length > 0) { - const route = stack.pop(); - if (!route) break; - if (route === closeRouteSymbol) { - code += "]},"; - continue; - } - code += "{"; - // TODO: route-module transform - code += `lazy: () => import(${JSON.stringify(path.resolve(config.appDirectory, route.file))}),`; - code += `id: ${JSON.stringify(route.id || createRouteId(route.file, config.appDirectory))},`; - if (typeof route.path === "string") { - code += `path: ${JSON.stringify(route.path)},`; - } - if (route.index) { - code += `index: true,`; - } - if (route.caseSensitive) { - code += `caseSensitive: true,`; - } - if (route.children) { - code += ["children:["]; - stack.push(closeRouteSymbol); - stack.push(...[...route.children].reverse()); - } else { - code += "},"; - } - } - code += "];\n"; - - return code; -} - -function createRouteId(file: string, appDirectory: string) { - return path - .relative(appDirectory, file) - .replace(/\\+/, "/") - .slice(0, -path.extname(file).length); -} diff --git a/packages/rsc-react-router/src/types.d.ts b/packages/rsc-react-router/src/types.d.ts deleted file mode 100644 index 349fcb3c8..000000000 --- a/packages/rsc-react-router/src/types.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// -/// - -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} - -declare module "virtual:react-router-routes" { - const routes: any; - export default routes; -} diff --git a/packages/rsc-react-router/tsconfig.json b/packages/rsc-react-router/tsconfig.json deleted file mode 100644 index 591d19e6e..000000000 --- a/packages/rsc-react-router/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src", "*.ts"], - "compilerOptions": { - "noPropertyAccessFromIndexSignature": false, - "noImplicitReturns": false, - "checkJs": false, - "declaration": true, - "isolatedDeclarations": true, - "jsx": "react-jsx" - } -} diff --git a/packages/rsc-react-router/tsdown.config.ts b/packages/rsc-react-router/tsdown.config.ts deleted file mode 100644 index ce417be8e..000000000 --- a/packages/rsc-react-router/tsdown.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from "tsdown"; - -export default defineConfig({ - entry: [ - "src/plugin.ts", - "src/entry.browser.tsx", - "src/entry.rsc.tsx", - "src/entry.rsc.single.tsx", - "src/entry.ssr.tsx", - "src/client.tsx", - ], - format: ["esm"], - external: [/^virtual:/], - dts: { - sourcemap: process.argv.slice(2).includes("--sourcemap"), - }, -}) as any; diff --git a/packages/rsc/CHANGELOG.md b/packages/rsc/CHANGELOG.md deleted file mode 100644 index c81384ba1..000000000 --- a/packages/rsc/CHANGELOG.md +++ /dev/null @@ -1,164 +0,0 @@ -# Changelog - -## v0.4.9 (2025-07-03) - -- feat: re-export plugin from base exports entry ([#1125](https://github.com/hi-ogawa/vite-plugins/pull/1125)) -- feat: re-export `transformHoistInlineDirective` ([#1122](https://github.com/hi-ogawa/vite-plugins/pull/1122)) -- fix: don't copy vite manifest from rsc to client ([#1118](https://github.com/hi-ogawa/vite-plugins/pull/1118)) - -## v0.4.8 (2025-07-01) - -- fix: copy all server assets to client by default and output `__vite_rsc_encryption_key` to fs directly ([#1102](https://github.com/hi-ogawa/vite-plugins/pull/1102)) -- fix: stable client build ([#1094](https://github.com/hi-ogawa/vite-plugins/pull/1094)) - -## v0.4.7 (2025-06-28) - -- feat: re-export `encodeReply` and `createTemporaryReferenceSet` from `react-server-dom/client` in `rsc` ([#1089](https://github.com/hi-ogawa/vite-plugins/pull/1089)) -- chore: add `use cache` example ([#1089](https://github.com/hi-ogawa/vite-plugins/pull/1089)) -- refactor: output code without indent ([#1087](https://github.com/hi-ogawa/vite-plugins/pull/1087)) - -## v0.4.6 (2025-06-27) - -- fix: correctly resolve server function created by 3rd party package during dev ([#1067](https://github.com/hi-ogawa/vite-plugins/pull/1067)) -- fix: correctly resolve client boundary created by server package during dev ([#1050](https://github.com/hi-ogawa/vite-plugins/pull/1050)) -- fix: copy only css assets from server build to client build by default ([#1072](https://github.com/hi-ogawa/vite-plugins/pull/1072)) -- fix: fix single quote string in `loadModule('ssr', 'index')` ([#1064](https://github.com/hi-ogawa/vite-plugins/pull/1064)) -- fix: stabilize server build by externalizing encryption key file ([#1069](https://github.com/hi-ogawa/vite-plugins/pull/1069)) -- fix: check build instead of `import.meta.env.DEV` ([#1083](https://github.com/hi-ogawa/vite-plugins/pull/1083)) -- perf: strip code during scan build ([#1066](https://github.com/hi-ogawa/vite-plugins/pull/1066)) -- feat: support preserving client reference original value ([#1078](https://github.com/hi-ogawa/vite-plugins/pull/1078)) -- feat: add `enableActionEncryption` option for debugging purpose ([#1084](https://github.com/hi-ogawa/vite-plugins/pull/1084)) -- feat: add `ignoredClientInServerPackageWarning` option ([#1065](https://github.com/hi-ogawa/vite-plugins/pull/1065)) - -## v0.4.5 (2025-06-22) - -- feat: rsc css transform for default export identifier ([#1046](https://github.com/hi-ogawa/vite-plugins/pull/1046)) -- feat: add `import.meta.viteRsc.loadBootstrapScriptContent` ([#1042](https://github.com/hi-ogawa/vite-plugins/pull/1042)) -- fix: only include jsx/tsx for rsc css export transform ([#1034](https://github.com/hi-ogawa/vite-plugins/pull/1034)) -- fix: ensure server-only and client-only not externalized ([#1045](https://github.com/hi-ogawa/vite-plugins/pull/1045)) -- fix: use static import for `loadCss` virtuals during build ([#1043](https://github.com/hi-ogawa/vite-plugins/pull/1043)) - -## v0.4.4 (2025-06-20) - -- feat: automatic rsc css export transform ([#1030](https://github.com/hi-ogawa/vite-plugins/pull/1030)) -- feat: add plugin to workaround cloudflare error ([#1014](https://github.com/hi-ogawa/vite-plugins/pull/1014)) -- feat: add load module dev proxy ([#1012](https://github.com/hi-ogawa/vite-plugins/pull/1012)) -- feat: add `serverHandler` option to allow using ssr environment as main handler ([#1008](https://github.com/hi-ogawa/vite-plugins/pull/1008)) -- feat: support `loadModule(environment, entry)` ([#1007](https://github.com/hi-ogawa/vite-plugins/pull/1007)) -- refactor: tweak renderHtml types and naming ([#1029](https://github.com/hi-ogawa/vite-plugins/pull/1029)) - -## v0.4.3 (2025-06-18) - -- feat: add rsc css export transform helper ([#1002](https://github.com/hi-ogawa/vite-plugins/pull/1002)) -- feat: support `loadCss(importer)` ([#1001](https://github.com/hi-ogawa/vite-plugins/pull/1001)) - -## v0.4.2 (2025-06-17) - -- fix: allow custom `outDir` + chore: cloudflare single worker setup ([#990](https://github.com/hi-ogawa/vite-plugins/pull/990)) -- fix: transform `__webpack_require__` global ([#980](https://github.com/hi-ogawa/vite-plugins/pull/980)) -- fix: inline and optimize react deps in ssr environment ([#982](https://github.com/hi-ogawa/vite-plugins/pull/982)) -- refactor: resolve self runtime import instead of `dedupe` ([#975](https://github.com/hi-ogawa/vite-plugins/pull/975)) -- refactor: emit assets manifest during `writeBundle` ([#972](https://github.com/hi-ogawa/vite-plugins/pull/972)) -- refactor: use `../` instead of `./../` path in output ([#963](https://github.com/hi-ogawa/vite-plugins/pull/963)) - -## v0.4.1 (2025-06-15) - -- fix: re-publish to fix vendored dependency - -## v0.4.0 (2025-06-15) - -- refactor!: rework multi environment API (bootstrap script) ([#958](https://github.com/hi-ogawa/vite-plugins/pull/958)) -- refactor!: rework multi environment API (ssr module) ([#957](https://github.com/hi-ogawa/vite-plugins/pull/957)) -- refactor!: simplify plugin options in favor of `rollupOptions.input` ([#956](https://github.com/hi-ogawa/vite-plugins/pull/956)) -- feat: expose `rsc-html-stream` utils ([#950](https://github.com/hi-ogawa/vite-plugins/pull/950)) -- fix: fix missing rsc css on build ([#949](https://github.com/hi-ogawa/vite-plugins/pull/949)) - -## v0.3.4 (2025-06-12) - -- fix: fix internal import to allow stable react vendor chunk ([#824](https://github.com/hi-ogawa/vite-plugins/pull/824)) -- fix: compat for old react plugin ([#939](https://github.com/hi-ogawa/vite-plugins/pull/939)) - -## v0.3.3 (2025-06-12) - -- feat: support rolldown-vite ([#931](https://github.com/hi-ogawa/vite-plugins/pull/931)) -- fix: allow usage without react plugin ([#934](https://github.com/hi-ogawa/vite-plugins/pull/934)) -- chore: docs ([#921](https://github.com/hi-ogawa/vite-plugins/pull/921)) - -## v0.3.2 (2025-06-10) - -- feat: auto initialize ([#925](https://github.com/hi-ogawa/vite-plugins/pull/925)) -- fix: emit assets manifest only in server build ([#929](https://github.com/hi-ogawa/vite-plugins/pull/929)) -- refactor: inline react-server-dom in ssr (2) ([#927](https://github.com/hi-ogawa/vite-plugins/pull/927)) -- chore: add `@cloudflare/vite-plugin` example ([#926](https://github.com/hi-ogawa/vite-plugins/pull/926)) - -## v0.3.1 (2025-06-06) - -- refactor: vendor react-server-dom ([#854](https://github.com/hi-ogawa/vite-plugins/pull/854)) - -## v0.3.0 (2025-06-05) - -- feat!: rsc css code split ([#876](https://github.com/hi-ogawa/vite-plugins/pull/876)) -- feat: encrypt closure bind values ([#897](https://github.com/hi-ogawa/vite-plugins/pull/897)) -- fix: client element as bound arg encryption ([#905](https://github.com/hi-ogawa/vite-plugins/pull/905)) -- fix: throw on client reference call on server ([#900](https://github.com/hi-ogawa/vite-plugins/pull/900)) - -## v0.2.4 (2025-05-26) - -- fix: fix stale css import in non-boundary client module ([#887](https://github.com/hi-ogawa/vite-plugins/pull/887)) -- fix: fix non-client-boundary client module hmr in tailwind example ([#886](https://github.com/hi-ogawa/vite-plugins/pull/886)) - -## v0.2.3 (2025-05-22) - -- fix: support Windows ([#884](https://github.com/hi-ogawa/vite-plugins/pull/884)) -- fix: remove stale ssr styles during dev ([#879](https://github.com/hi-ogawa/vite-plugins/pull/879)) -- fix: add `vary` header to avoid rsc payload on tab re-open ([#877](https://github.com/hi-ogawa/vite-plugins/pull/877)) - -## v0.2.2 (2025-05-18) - -- fix: emit server assets and copy to client ([#861](https://github.com/hi-ogawa/vite-plugins/pull/861)) -- fix: css modules hmr ([#860](https://github.com/hi-ogawa/vite-plugins/pull/860)) -- fix: fix `collectCssByUrl` error ([#856](https://github.com/hi-ogawa/vite-plugins/pull/856)) -- fix: show invalid transform error with code frame ([#871](https://github.com/hi-ogawa/vite-plugins/pull/871)) -- perf: preload client reference deps before non-cached import ([#850](https://github.com/hi-ogawa/vite-plugins/pull/850)) - -## v0.2.1 (2025-05-13) - -- feat: automatic client package heuristics ([#830](https://github.com/hi-ogawa/vite-plugins/pull/830)) -- fix: add browser entry to `optimizeDeps.entries` ([#846](https://github.com/hi-ogawa/vite-plugins/pull/846)) -- fix: resolve self package from project root ([#845](https://github.com/hi-ogawa/vite-plugins/pull/845)) -- refactor: use `rsc-html-stream` ([#843](https://github.com/hi-ogawa/vite-plugins/pull/843)) - -## v0.2.0 (2025-05-12) - -- feat: apply tree-shaking to all client references (2nd approach) ([#838](https://github.com/hi-ogawa/vite-plugins/pull/838)) -- feat: support nonce ([#813](https://github.com/hi-ogawa/vite-plugins/pull/813)) -- feat: support css in rsc environment ([#825](https://github.com/hi-ogawa/vite-plugins/pull/825)) -- feat: support css in client references ([#823](https://github.com/hi-ogawa/vite-plugins/pull/823)) -- fix: handle html escape and binary data in ssr rsc payload ([#839](https://github.com/hi-ogawa/vite-plugins/pull/839)) -- fix: wrap virtual to workaround module runner entry issues ([#832](https://github.com/hi-ogawa/vite-plugins/pull/832)) -- fix: scan build in two environments ([#820](https://github.com/hi-ogawa/vite-plugins/pull/820)) -- refactor: simplify client reference mapping ([#836](https://github.com/hi-ogawa/vite-plugins/pull/836)) -- refactor!: remove `entries.css` ([#831](https://github.com/hi-ogawa/vite-plugins/pull/831)) -- refactor: client reference ssr preinit/preload via proxy and remove `prepareDestination` ([#828](https://github.com/hi-ogawa/vite-plugins/pull/828)) -- refactor: tweak asset links api ([#826](https://github.com/hi-ogawa/vite-plugins/pull/826)) - -## v0.1.1 (2025-05-07) - -- fix: statically import client references virtual ([#815](https://github.com/hi-ogawa/vite-plugins/pull/815)) -- fix: fix base for findSourceMapURL ([#812](https://github.com/hi-ogawa/vite-plugins/pull/812)) -- fix: fix module runner line offset in `findSourceMapURL` ([#810](https://github.com/hi-ogawa/vite-plugins/pull/810)) - -## v0.1.0 (2025-05-01) - -- feat: support `findSourceMapURL` for `createServerReference` ([#796](https://github.com/hi-ogawa/vite-plugins/pull/796)) -- feat: support `findSourceMapURL` for component stack and replay logs ([#779](https://github.com/hi-ogawa/vite-plugins/pull/779)) -- feat: support temporary references ([#776](https://github.com/hi-ogawa/vite-plugins/pull/776)) -- feat: support custom base ([#775](https://github.com/hi-ogawa/vite-plugins/pull/775)) -- feat: refactor assets manifest and expose it to rsc build ([#767](https://github.com/hi-ogawa/vite-plugins/pull/767)) -- feat: ssr modulepreload only for build ([#763](https://github.com/hi-ogawa/vite-plugins/pull/763)) -- feat: tree shake unused reference exports ([#761](https://github.com/hi-ogawa/vite-plugins/pull/761)) -- feat: re-export react-server-dom ([#744](https://github.com/hi-ogawa/vite-plugins/pull/744)) -- feat: support css entry ([#737](https://github.com/hi-ogawa/vite-plugins/pull/737)) -- feat wrap client packages in virtual (support `clientPackages` options) ([#718](https://github.com/hi-ogawa/vite-plugins/pull/718)) -- feat: modulepreload client reference on ssr ([#703](https://github.com/hi-ogawa/vite-plugins/pull/703)) -- feat: create vite-rsc ([#692](https://github.com/hi-ogawa/vite-plugins/pull/692)) diff --git a/packages/rsc/README.md b/packages/rsc/README.md index 7887ef114..a8f8b8cbd 100644 --- a/packages/rsc/README.md +++ b/packages/rsc/README.md @@ -1,432 +1 @@ -> [!important] -> `@hiogawa/vite-rsc` is now maintained as Vite's official package [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc). - -# @hiogawa/vite-rsc - -This package provides [React Server Components](https://react.dev/reference/rsc/server-components) (RSC) support for Vite. - -## Features - -- **Framework-less RSC experience**: The plugin implements [RSC conventions](https://react.dev/reference/rsc/server-components) and provides low level `react-server-dom` runtime API without framework-specific abstractions. -- **CSS support**: CSS is automatically code-split both at client and server components and they are injected upon rendering. -- **HMR support**: Enables editing both client and server components without full page reloads. -- **Runtime agnostic**: Built on [Vite environment API](https://vite.dev/guide/api-environment.html) and works with other runtimes (e.g., [`@cloudflare/vite-plugin`](https://github.com/cloudflare/workers-sdk/tree/main/packages/vite-plugin-cloudflare)). - -## Getting Started - -You can start a project by copying an example locally by: - -```sh -npx degit hi-ogawa/vite-plugins/packages/rsc/examples/starter my-app -``` - -## Examples - -- [`./examples/starter`](./examples/starter) - - This example provides an in-depth overview of API with inline comments to explain how they function within RSC-powered React application. -- [`./examples/react-router`](./examples/react-router) - - This demonstrates how to integrate [experimental React Router RSC API](https://remix.run/blog/rsc-preview) with this plugin. - It also includes `@cloudflare/vite-plugin` integration. -- [`./examples/basic`](./examples/basic) - - This is mainly used for e2e testing and include various advanced RSC usages (e.g. `"use cache"` example). - It also uses a high level `@hiogawa/vite-rsc/extra/{rsc,ssr,browser}` API for quick setup. -- [`./examples/ssg`](./examples/ssg) - - Static site generation (SSG) example with MDX and client components for interactivity. - -## Basic Concepts - -This example is a simplified version of [`./examples/starter`](./examples/starter). You can read [`./examples/starter/src/framework/entry.{rsc,ssr,browser}.tsx`](./examples/starter/src/framework) for more in-depth commentary, which includes server function handling and client-side RSC re-fetching/re-rendering. - -This is the diagram to show the basic flow of RSC rendering process. See also https://github.com/hi-ogawa/vite-plugins/discussions/606. - -```mermaid -graph TD - - subgraph "rsc environment" - A["React virtual dom tree"] --> |"[@hiogawa/vite-rsc/rsc]
renderToReadableStream"| B1["RSC Stream"]; - end - - B1 --> B2 - B1 --> B3 - - subgraph "ssr environment" - B2["RSC Stream"] --> |"[@hiogawa/vite-rsc/ssr]
createFromReadableStream"| C1["React virtual dom tree"]; - C1 --> |"[react-dom/server]
SSR"| E["HTML String/Stream"]; - end - - subgraph "client environment" - B3["RSC Stream"] --> |"[@hiogawa/vite-rsc/browser]
createFromReadableStream"| C2["React virtual dom tree"]; - C2 --> |"[react-dom/client]
CSR: mount, hydration"| D["DOM Elements"]; - end - - style A fill:#D6EAF8,stroke:#333,stroke-width:2px - style B1 fill:#FEF9E7,stroke:#333,stroke-width:2px - style B2 fill:#FEF9E7,stroke:#333,stroke-width:2px - style B3 fill:#FEF9E7,stroke:#333,stroke-width:2px - style C1 fill:#D6EAF8,stroke:#333,stroke-width:2px - style C2 fill:#D6EAF8,stroke:#333,stroke-width:2px - style D fill:#D5F5E3,stroke:#333,stroke-width:2px - style E fill:#FADBD8,stroke:#333,stroke-width:2px -``` - -- [`vite.config.ts`](./examples/starter/vite.config.ts) - -```js -import rsc from "@hiogawa/vite-rsc"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [ - // add plugin - rsc(), - ], - - // specify entry point for each environment. - environments: { - // `rsc` environment loads modules with `react-server` condition. - // this environment is responsible for: - // - RSC stream serialization (React VDOM -> RSC stream) - // - server functions handling - rsc: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.rsc.tsx", - }, - }, - }, - }, - - // `ssr` environment loads modules without `react-server` condition. - // this environment is responsible for: - // - RSC stream deserialization (RSC stream -> React VDOM) - // - traditional SSR (React VDOM -> HTML string/stream) - // (NOTE: as it can be seen in the above diagram. SSR is technically an optional mechanism.) - ssr: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.ssr.tsx", - }, - }, - }, - }, - - // client environment is used for hydration and client-side rendering - // this environment is responsible for: - // - RSC stream deserialization (RSC stream -> React VDOM) - // - traditional CSR (React VDOM -> Browser DOM tree mount/hydration) - // - refetch and re-render RSC - // - calling server functions - client: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.browser.tsx", - }, - }, - }, - }, - }, -}); -``` - -- [`entry.rsc.tsx`](./examples/starter/src/framework/entry.rsc.tsx) - -```tsx -import * as ReactServer from "@hiogawa/vite-rsc/rsc"; // re-export of react-server-dom/server.edge - -// the plugin assumes `rsc` entry having default export of request handler -export default async function handler(request: Request): Promise { - // serialization React VDOM to RSC stream - const root =

Test

; - const rscStream = ReactServer.renderToReadableStream(root); - - // respond direct RSC stream request based on framework's convention - if (request.url.endsWith(".rsc")) { - return new Response(rscStream, { - headers: { - 'Content-type': 'text/x-component;charset=utf-8' - } - }); - } - - // delegate to SSR environment for html rendering - // `loadModule` is a helper API provided by the plugin for multi environment interaction. - const ssrEntry = await import.meta.viteRsc.loadModule("ssr", "index"); - const htmlStream = await ssrEntry.handleSsr(rscStream); - - // respond html - return new Response(htmlStream, { - headers: { - 'Content-type': 'text/html' - } - }) -} -``` - -- [`entry.ssr.tsx`](./examples/starter/src/framework/entry.ssr.tsx) - -```tsx -import * as ReactClient from "@hiogawa/vite-rsc/ssr"; // re-export of react-server-dom/client.edge -import * as ReactDOMServer from "react-dom/server.edge"; - -export async function handleSsr(rscStream: ReadableStream) { - // deserialize RSC stream back to React VDOM - const root = await ReactClient.createFromReadableStream(rscStream); - - // helper API to allow referencing browser entry content from SSR environment - const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index"); - - // render html (traditional SSR) - const htmlStream = ReactDOMServer.renderToReadableStream(root, { - bootstrapScriptContent, - }); - - return htmlStream; -} -``` - -- [`entry.browser.tsx`](./examples/starter/src/framework/entry.browser.tsx) - -```tsx -import * as ReactClient from "@hiogawa/vite-rsc/browser"; // re-export of react-server-dom/client.browser -import * as ReactDOMClient from "react-dom/client"; - -async function main() { - // fetch and deserialize RSC stream back to React VDOM - const rscResponse = await fetch(window.location.href + ".rsc); - const root = await ReactClient.createFromReadableStream(rscResponse.body); - - // hydration (traditional CSR) - ReactDOMClient.hydrateRoot(document, root); -} - -main(); -``` - -## `react-server-dom` API - -### `@hiogawa/vite-rsc/rsc` - -This module re-exports RSC runtime API provided by `react-server-dom/server.edge` - -- `renderToReadableStream`: RSC serialization (React VDOM -> RSC stream) -- `createFromReadableStream`: RSC deserialization (RSC stream -> React VDOM). This is also available on rsc environment itself. For example, it allows saving serailized RSC and deserializing it for later use. -- `decodeAction/decodeReply/loadServerAction`: server function related... - -### `@hiogawa/vite-rsc/ssr` - -This module re-exports RSC runtime API provided by `react-server-dom/client.edge` - -- `createFromReadableStream`: RSC deserialization (RSC stream -> React VDOM) - -### `@hiogawa/vite-rsc/browser` - -This module re-exports RSC runtime API provided by `react-server-dom/client.browser` - -- `createFromReadableStream`: RSC deserialization (RSC stream -> React VDOM) -- `createFromFetch`: a robust way of `createFromReadableStream((await fetch("...")).body)` -- `encodeReply/setServerCallback`: server function related... - -## Environment helper API - -The plugin provides an additional helper for multi environment interaction. - -### available on `rsc` or `ssr` environment - -#### `import.meta.viteRsc.loadModule` - -- Type: `(environmentName: "ssr" | "rsc", entryName: string) => Promise` - -This allows importing `ssr` environment module specified by `environments.ssr.build.rollupOptions.input[entryName]` inside `rsc` environment and vice versa. - -During development, by default, this API assumes both `rsc` and `ssr` environments execute under the main Vite process. When enabling `rsc({ loadModuleDevProxy: true })` plugin option, the loaded module is implemented as a proxy with `fetch`-based RPC to call in node environment on the main Vite process, which for example, allows `rsc` environment inside cloudflare workers to access `ssr` environment on the main Vite process. - -During production build, this API will be rewritten into a static import of the specified entry of other environment build and the modules are executed inside the same runtime. - -For example, - -```js -// ./entry.rsc.tsx -const ssrModule = await import.meta.viteRsc.loadModule("ssr", "index"); -ssrModule.renderHTML(...); - -// ./entry.ssr.tsx (with environments.ssr.build.rollupOptions.input.index = "./entry.ssr.tsx") -export function renderHTML(...) {} -``` - -### available on `rsc` environment - -#### `import.meta.viteRsc.loadCss` - -- Type: `(importer?: string) => React.ReactNode` - -This allows collecting css which is imported through a current server module and injecting them inside server components. - -```tsx -import "./test.css"; -import dep from "./dep.tsx"; - -export function ServerPage() { - // this will include css assets for "test.css" - // and any css transitively imported through "dep.tsx" - return <> - {import.meta.viteRsc.loadCss()} - ... - -} -``` - -Where specifying `loadCss()`, it will collect css through the server module resolved by ``. - -```tsx -// virtual:my-framework-helper -export function Assets() { - return <> - {import.meta.viteRsc.loadCss("/routes/home.tsx")} - {import.meta.viteRsc.loadCss("/routes/about.tsx")} - {...} - -} - -// user-app.tsx -import { Assets } from "virtual:my-framework-helper"; - -export function UserApp() { - return - - - - ... - -} -``` - -#### `?vite-rsc-css-export=` - -This special query convention provides automatic injection of `import.meta.viteRsc.loadCss`. - -For example, - -```tsx -// my-route.tsx -export function Page(props) { - return
...
-} - -// my-route.css?vite-rsc-css-export=Page -function Page(props) { - return
...
-} - -function __Page(props) { - return <> - {import.meta.viteRsc.loadCss()} - - -} - -export { __Page as Page } -``` - -### available on `ssr` environment - -#### `import.meta.viteRsc.loadBootstrapScriptContent("index")` - -This provides a raw js code to execute a browser entry file specified by `environments.client.build.rollupOptions.input.index`. This is intended to be used with React DOM SSR API, such as [`renderToReadableStream`](https://react.dev/reference/react-dom/server/renderToReadableStream) - -```js -import bootstrapScriptContent from "virtual:vite-rsc/bootstrap-script-content" -import { renderToReadableStream } from "react-dom/server.edge"; - -const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent("index") -const htmlStream = await renderToReadableStream(reactNode, { bootstrapScriptContent }); -``` - -### available on `client` environment - -#### `rsc:update` event - -This event is fired when server modules are updated, which can be used to trigger re-fetching and re-rendering of RSC components on browser. - -```js -import * as ReactClient from "@hiogawa/vite-rsc/browser"; - -import.meta.hot.on("rsc:update", async () => { - // re-fetch RSC stream - const rscPayload = await ReactClient.createFromFetch(fetch(window.location.href + ".rsc")) - // re-render ... -}); -``` - -## Plugin API - -### `@hiogawa/vite-rsc` - -```js -import rsc from "@hiogawa/vite-rsc"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [ - rsc({ - // this is only a shorthand of specifying each rollup input via - // `environments[name].build.rollupOptions.input.index` - entries: { - rsc: "...", - ssr: "...", - client: "...", - }, - - // by default, the plugin sets up middleware - // using `default` export of `rsc` environment `index` entry. - // this behavior can be customized by `serverHandler` option. - serverHandler: false, - - // when `loadModuleDevProxy: true`, `import.meta.viteRsc.loadModule` is implemented - // through `fetch` based RPC, which allows, for example, rsc environment inside - // cloudflare workers to communicate with node ssr environment on main Vite process. - loadModuleDevProxy: true, - - // by default, `loadCss()` helper is injected based on certain heuristics. - // if it breaks, it can be opt-out or selectively applied based on files. - rscCssTransform: { filter: id => id.includes("/my-app/") }, - - // by default, the plugin uses a build-time generated encryption key for - // "use server" closure argument binding. - // This can be overwritten by configuring `defineEncryptionKey` option, - // for example, to obtain a key through environment variable during runtime. - // cf. https://nextjs.org/docs/app/guides/data-security#overwriting-encryption-keys-advanced - defineEncryptionKey: "process.env.MY_ENCRYPTION_KEY", - }), - ], -}); -``` - -## Higher level API - -This is a wrapper of `react-server-dom` API and helper API to setup a minimal RSC app without writing own framework code like [`./examples/starter/src/framework`](./examples/starter/src/framework/). See [`./examples/basic`](./examples/basic/) for how this API is used. - -### `@hiogawa/vite-rsc/extra/rsc` - -- `renderRequest` - -### `@hiogawa/vite-rsc/extra/ssr` - -- `renderHtml` - -### `@hiogawa/vite-rsc/extra/browser` - -- `hydrate` - -## Credits - -This project builds on fundamental techniques and insights from pioneering Vite RSC implementations. -Additionally, Parcel and React Router's work on standardizing the RSC bundler/app responsibility has guided this plugin's API design: - -- [Waku](https://github.com/wakujs/waku) -- [@lazarv/react-server](https://github.com/lazarv/react-server) -- [@jacob-ebey/vite-react-server-dom](https://github.com/jacob-ebey/vite-plugins/tree/main/packages/vite-react-server-dom) -- [React Router RSC](https://remix.run/blog/rsc-preview) -- [Parcel RSC](https://parceljs.org/recipes/rsc) +The code is moved to https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc diff --git a/packages/rsc/e2e/basic.test.ts b/packages/rsc/e2e/basic.test.ts deleted file mode 100644 index 95b57c1e2..000000000 --- a/packages/rsc/e2e/basic.test.ts +++ /dev/null @@ -1,937 +0,0 @@ -import { createHash } from "node:crypto"; -import { readFileSync } from "node:fs"; -import { type Page, expect, test } from "@playwright/test"; -import { type Fixture, setupIsolatedFixture, useFixture } from "./fixture"; -import { expectNoReload, testNoJs, waitForHydration } from "./helper"; - -// TODO: parallel? - -test.describe("dev-default", () => { - const f = useFixture({ root: "examples/basic", mode: "dev" }); - defineTest(f); -}); - -test.describe("build-default", () => { - const f = useFixture({ root: "examples/basic", mode: "build" }); - defineTest(f); -}); - -test.describe("dev-base", () => { - const f = useFixture({ - root: "examples/basic", - mode: "dev", - cliOptions: { - env: { - TEST_BASE: "true", - }, - }, - }); - defineTest(f); -}); - -test.describe("build-base", () => { - const f = useFixture({ - root: "examples/basic", - mode: "build", - cliOptions: { - env: { - TEST_BASE: "true", - }, - }, - }); - defineTest(f); -}); - -test.describe(() => { - // disabled by default - if (!process.env.TEST_ISOLATED) return; - - let tmpRoot = "/tmp/test-vite-rsc"; - test.beforeAll(async () => { - await setupIsolatedFixture({ src: "examples/basic", dest: tmpRoot }); - }); - - test.describe("dev-isolated", () => { - const f = useFixture({ root: tmpRoot, mode: "dev" }); - defineTest(f); - }); - - test.describe("build-isolated", () => { - const f = useFixture({ root: tmpRoot, mode: "build" }); - defineTest(f); - }); -}); - -function defineTest(f: Fixture) { - test("basic", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - }); - - test("client component", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await page.getByRole("button", { name: "client-counter: 0" }).click(); - await page.getByRole("button", { name: "client-counter: 1" }).click(); - }); - - test("server action @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testAction(page); - }); - - testNoJs("server action @nojs", async ({ page }) => { - await page.goto(f.url()); - await testAction(page); - }); - - async function testAction(page: Page) { - await page.getByRole("button", { name: "server-counter: 0" }).click(); - await page.getByRole("button", { name: "server-counter: 1" }).click(); - await expect( - page.getByRole("button", { name: "server-counter: 2" }), - ).toBeVisible(); - await page.getByRole("button", { name: "server-counter-reset" }).click(); - await expect( - page.getByRole("button", { name: "server-counter: 0" }), - ).toBeVisible(); - } - - test("useActionState @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testUseActionState(page); - }); - - testNoJs("useActionState @nojs", async ({ page }) => { - await page.goto(f.url()); - await testUseActionState(page); - }); - - async function testUseActionState(page: Page) { - await expect(page.getByTestId("use-action-state")).toContainText( - "test-useActionState: 0", - ); - await page.getByTestId("use-action-state").click(); - await expect(page.getByTestId("use-action-state")).toContainText( - "test-useActionState: 1", - ); - await page.getByTestId("use-action-state").click(); - await expect(page.getByTestId("use-action-state")).toContainText( - "test-useActionState: 2", - ); - } - - test("useActionState with jsx @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testUseActionStateJsx(page); - }); - - testNoJs("useActionState with jsx @nojs", async ({ page }) => { - await page.goto(f.url()); - await testUseActionStateJsx(page, { js: false }); - }); - - async function testUseActionStateJsx(page: Page, options?: { js?: boolean }) { - await page.getByTestId("use-action-state-jsx").getByRole("button").click(); - await expect(page.getByTestId("use-action-state-jsx")).toContainText( - /\(ok\)/, - ); - - // 1st call "works" but it shows an error during reponse and it breaks 2nd call. - // Failed to serialize an action for progressive enhancement: - // Error: React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options. - // [Promise, ] - if (!options?.js) return; - - await page.getByTestId("use-action-state-jsx").getByRole("button").click(); - await expect(page.getByTestId("use-action-state-jsx")).toContainText( - /\(ok\).*\(ok\)/, - ); - } - - test.describe(() => { - test.skip(f.mode !== "build"); - - testNoJs("module preload on ssr", async ({ page }) => { - await page.goto(f.url()); - const srcs = await page - .locator(`head >> link[rel="modulepreload"]`) - .evaluateAll((elements) => - elements.map((el) => el.getAttribute("href")), - ); - const manifest = JSON.parse( - readFileSync( - f.root + "/dist/ssr/__vite_rsc_assets_manifest.js", - "utf-8", - ).slice("export default ".length), - ); - const hashString = (v: string) => - createHash("sha256").update(v).digest().toString("hex").slice(0, 12); - const deps = - manifest.clientReferenceDeps[hashString("src/routes/client.tsx")]; - expect(srcs).toEqual(expect.arrayContaining(deps.js)); - }); - }); - - test.describe(() => { - test.skip(f.mode !== "dev"); - - test("server reference update @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testServerActionUpdate(page, { js: true }); - }); - - test("server reference update @nojs", async ({ page }) => { - await page.goto(f.url()); - await testServerActionUpdate(page, { js: false }); - }); - }); - - async function testServerActionUpdate(page: Page, options: { js: boolean }) { - await page.getByRole("button", { name: "server-counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "server-counter: 1" }), - ).toBeVisible(); - - // update server code - const editor = f.createEditor("src/routes/action/action.tsx"); - editor.edit((s) => - s.replace("const TEST_UPDATE = 1;", "const TEST_UPDATE = 10;"), - ); - await expect(async () => { - if (!options.js) await page.goto(f.url()); - await expect( - page.getByRole("button", { name: "server-counter: 0" }), - ).toBeVisible({ timeout: 10 }); - }).toPass(); - - await page.getByRole("button", { name: "server-counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "server-counter: 10" }), - ).toBeVisible(); - - editor.reset(); - await expect(async () => { - if (!options.js) await page.goto(f.url()); - await expect( - page.getByRole("button", { name: "server-counter: 0" }), - ).toBeVisible({ timeout: 10 }); - }).toPass(); - } - - test.describe(() => { - test.skip(f.mode !== "dev"); - - test("client hmr", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await page.getByRole("button", { name: "client-counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "client-counter: 1" }), - ).toBeVisible(); - - const editor = f.createEditor("src/routes/client.tsx"); - editor.edit((s) => s.replace("client-counter", "client-[edit]-counter")); - await expect( - page.getByRole("button", { name: "client-[edit]-counter: 1" }), - ).toBeVisible(); - - // check next ssr is also updated - const res = await page.goto(f.url()); - expect(await res?.text()).toContain("client-[edit]-counter"); - await waitForHydration(page); - editor.reset(); - await page.getByRole("button", { name: "client-counter: 0" }).click(); - }); - - test("non-boundary client hmr", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - - const locator = page.getByTestId("test-hmr-client-dep"); - await expect(locator).toHaveText("test-hmr-client-dep: 0[ok]"); - await locator.locator("button").click(); - await expect(locator).toHaveText("test-hmr-client-dep: 1[ok]"); - - const editor = f.createEditor("src/routes/hmr-client-dep/client-dep.tsx"); - editor.edit((s) => s.replace("[ok]", "[ok-edit]")); - await expect(locator).toHaveText("test-hmr-client-dep: 1[ok-edit]"); - - // check next ssr is also updated - const res = await page.reload(); - expect(await res?.text()).toContain("[ok-edit]"); - - await waitForHydration(page); - editor.reset(); - await expect(locator).toHaveText("test-hmr-client-dep: 0[ok]"); - }); - - test("server hmr", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - const editor = f.createEditor("src/routes/action/server.tsx"); - editor.edit((s) => s.replace("server-counter", "server-[edit]-counter")); - await expect( - page.getByRole("button", { name: "server-[edit]-counter: 0" }), - ).toBeVisible(); - editor.reset(); - await expect( - page.getByRole("button", { name: "server-counter: 0" }), - ).toBeVisible(); - }); - - test("module invalidation", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - - // change child module state - const locator = page.getByTestId("test-module-invalidation-server"); - await expect(locator).toContainText("[dep: 0]"); - locator.getByRole("button").click(); - await expect(locator).toContainText("[dep: 1]"); - - // change parent module - const editor = f.createEditor( - "src/routes/module-invalidation/server.tsx", - ); - editor.edit((s) => s.replace("[dep:", "[dep-edit:")); - - // preserve child module state - await expect(locator).toContainText("[dep-edit: 1]"); - editor.reset(); - await expect(locator).toContainText("[dep: 1]"); - }); - }); - - test("css @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await testCss(page); - }); - - testNoJs("css @nojs", async ({ page }) => { - await page.goto(f.url()); - await testCss(page); - }); - - async function testCss(page: Page, color = "rgb(255, 165, 0)") { - await expect(page.locator(".test-style-client")).toHaveCSS("color", color); - await expect(page.locator(".test-style-server")).toHaveCSS("color", color); - } - - test.describe(() => { - test.skip(f.mode !== "dev"); - - test("css hmr client", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - - await using _ = await expectNoReload(page); - const editor = f.createEditor("src/routes/style-client/client.css"); - editor.edit((s) => s.replaceAll("rgb(255, 165, 0)", "rgb(0, 165, 255)")); - await expect(page.locator(".test-style-client")).toHaveCSS( - "color", - "rgb(0, 165, 255)", - ); - editor.edit((s) => - s.replaceAll( - `color: rgb(0, 165, 255);`, - `/* color: rgb(0, 165, 255); */`, - ), - ); - await expect(page.locator(".test-style-client")).toHaveCSS( - "color", - "rgb(0, 0, 0)", - ); - // wait longer for multiple edits - await page.waitForTimeout(100); - editor.reset(); - await expect(page.locator(".test-style-client")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - }); - - test("adding/removing css client @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testAddRemoveCssClient(page, { js: true }); - }); - - testNoJs("adding/removing css client @nojs", async ({ page }) => { - await page.goto(f.url()); - await testAddRemoveCssClient(page, { js: false }); - }); - - async function testAddRemoveCssClient( - page: Page, - options: { js: boolean }, - ) { - await expect(page.locator(".test-style-client-dep")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - - // remove css import - const editor = f.createEditor("src/routes/style-client/client-dep.tsx"); - editor.edit((s) => - s.replaceAll( - `import "./client-dep.css";`, - `/* import "./client-dep.css"; */`, - ), - ); - await page.waitForTimeout(100); - await expect(async () => { - if (!options.js) await page.reload(); - await expect(page.locator(".test-style-client-dep")).toHaveCSS( - "color", - "rgb(0, 0, 0)", - { timeout: 10 }, - ); - }).toPass(); - - // add back css import - editor.reset(); - await page.waitForTimeout(100); - await expect(async () => { - if (!options.js) await page.reload(); - await expect(page.locator(".test-style-client-dep")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - { timeout: 10 }, - ); - }).toPass(); - } - - test("css hmr server", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - - await using _ = await expectNoReload(page); - const editor = f.createEditor("src/routes/style-server/server.css"); - editor.edit((s) => s.replaceAll("rgb(255, 165, 0)", "rgb(0, 165, 255)")); - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(0, 165, 255)", - ); - editor.edit((s) => - s.replaceAll( - `color: rgb(0, 165, 255);`, - `/* color: rgb(0, 165, 255); */`, - ), - ); - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(0, 0, 0)", - ); - editor.reset(); - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - }); - - // TODO: need a way to add/remove links on server hmr. for now, it requires a manually reload. - test.skip("adding/removing css server @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testAddRemoveCssServer(page, { js: true }); - }); - - testNoJs("adding/removing css server @nojs", async ({ page }) => { - await page.goto(f.url()); - await testAddRemoveCssServer(page, { js: false }); - }); - - async function testAddRemoveCssServer( - page: Page, - options: { js: boolean }, - ) { - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - - // remove css import - const editor = f.createEditor("src/routes/style-server/server.tsx"); - editor.edit((s) => - s.replaceAll(`import "./server.css";`, `/* import "./server.css"; */`), - ); - await page.waitForTimeout(100); - await expect(async () => { - if (!options.js) await page.reload(); - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(0, 0, 0)", - { timeout: 10 }, - ); - }).toPass(); - - // add back css import - editor.reset(); - await page.waitForTimeout(100); - await expect(async () => { - if (!options.js) await page.reload(); - await expect(page.locator(".test-style-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - { timeout: 10 }, - ); - }).toPass(); - } - }); - - test("css client no ssr", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await page.locator("a[href='?test-client-style-no-ssr']").click(); - await expect(page.locator(".test-style-client-no-ssr")).toHaveCSS( - "color", - "rgb(0, 200, 100)", - ); - }); - - test("css module client @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("css-module-client")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - - if (f.mode !== "dev") return; - - // test client css module HMR - await using _ = await expectNoReload(page); - const editor = f.createEditor("src/routes/style-client/client.module.css"); - editor.edit((s) => s.replaceAll("rgb(255, 165, 0)", "rgb(0, 165, 255)")); - await expect(page.getByTestId("css-module-client")).toHaveCSS( - "color", - "rgb(0, 165, 255)", - ); - editor.reset(); - await expect(page.getByTestId("css-module-client")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - }); - - test("css module server @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("css-module-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - - if (f.mode !== "dev") return; - - // test server css module HMR - await using _ = await expectNoReload(page); - const editor = f.createEditor("src/routes/style-server/server.module.css"); - editor.edit((s) => s.replaceAll("rgb(255, 165, 0)", "rgb(0, 165, 255)")); - await expect(page.getByTestId("css-module-server")).toHaveCSS( - "color", - "rgb(0, 165, 255)", - ); - editor.reset(); - await expect(page.getByTestId("css-module-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - }); - - testNoJs("css module @nojs", async ({ page }) => { - await page.goto(f.url()); - await expect(page.getByTestId("css-module-client")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - await expect(page.getByTestId("css-module-server")).toHaveCSS( - "color", - "rgb(255, 165, 0)", - ); - }); - - test("tailwind @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await testTailwind(page); - }); - - testNoJs("tailwind @nojs", async ({ page }) => { - await page.goto(f.url()); - await testTailwind(page); - }); - - async function testTailwind(page: Page) { - await expect(page.locator(".test-tw-client")).toHaveCSS( - "color", - // blue-500 - "rgb(0, 0, 255)", - ); - await expect(page.locator(".test-tw-server")).toHaveCSS( - "color", - // red-500 - "rgb(255, 0, 0)", - ); - } - - test.describe(() => { - test.skip(f.mode !== "dev"); - - test("tailwind hmr", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await testTailwind(page); - - await using _ = await expectNoReload(page); - - const clientFile = f.createEditor("src/routes/tailwind/client.tsx"); - clientFile.edit((s) => s.replaceAll("text-[#00f]", "text-[#88f]")); - await expect(page.locator(".test-tw-client")).toHaveCSS( - "color", - "rgb(136, 136, 255)", - ); - clientFile.reset(); - await expect(page.locator(".test-tw-client")).toHaveCSS( - "color", - "rgb(0, 0, 255)", - ); - - const serverFile = f.createEditor("src/routes/tailwind/server.tsx"); - serverFile.edit((s) => s.replaceAll("text-[#f00]", "text-[#f88]")); - await expect(page.locator(".test-tw-server")).toHaveCSS( - "color", - "rgb(255, 136, 136)", - ); - serverFile.reset(); - await expect(page.locator(".test-tw-server")).toHaveCSS( - "color", - "rgb(255, 0, 0)", - ); - }); - - testNoJs("no FOUC after server restart @nojs", async ({ page }) => { - const res = await page.request.get(f.url("/__test_restart")); - expect(await res.text()).toBe("ok"); - await new Promise((r) => setTimeout(r, 100)); - await page.goto(f.url("./")); - await testCss(page); - await testTailwind(page); - }); - }); - - test("temporary references @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await page - .getByRole("button", { name: "test-temporary-reference" }) - .click(); - await expect(page.getByTestId("temporary-reference")).toContainText( - "result: [server [client]]", - ); - }); - - test("server action error @js", async ({ page }) => { - // it doesn't seem possible to assert react error stack mapping on playwright. - // this need to be verified manually on browser devtools console. - await page.goto(f.url()); - await waitForHydration(page); - await page - .getByRole("button", { name: "test-server-action-error" }) - .click(); - await expect(page.getByText("ErrorBoundary caught")).toBeVisible(); - await page.getByRole("button", { name: "reset-error" }).click(); - await expect( - page.getByRole("button", { name: "test-server-action-error" }), - ).toBeVisible(); - }); - - test("hydrate while streaming @js", async ({ page }) => { - // client is interactive before suspense is resolved - await page.goto(f.url("./?test-suspense=1000"), { waitUntil: "commit" }); - await waitForHydration(page); - await expect(page.getByTestId("suspense")).toContainText( - "suspense-fallback", - ); - await expect(page.getByTestId("suspense")).toContainText( - "suspense-resolved", - ); - }); - - test("ssr rsc payload encoding", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("ssr-rsc-payload")).toHaveText( - "test1: true, test2: true, test3: false, test4: true", - ); - - await page.goto(f.url("./?test-payload-binary")); - await waitForHydration(page); - await expect(page.getByTestId("ssr-rsc-payload")).toHaveText( - "test1: true, test2: true, test3: true, test4: true", - ); - }); - - test("action bind simple @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testActionBindSimple(page); - }); - - testNoJs("action bind simple @nojs", async ({ page }) => { - await page.goto(f.url()); - await testActionBindSimple(page); - }); - - async function testActionBindSimple(page: Page) { - await expect(page.getByTestId("test-server-action-bind-simple")).toHaveText( - "[?]", - ); - await page - .getByRole("button", { name: "test-server-action-bind-simple" }) - .click(); - await expect(page.getByTestId("test-server-action-bind-simple")).toHaveText( - "true", - ); - await page - .getByRole("button", { name: "test-server-action-bind-reset" }) - .click(); - } - - test("action bind client @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testActionBindClient(page); - }); - - // this doesn't work on Next either https://github.com/hi-ogawa/reproductions/tree/main/next-rsc-client-action-bind - testNoJs.skip("action bind client @nojs", async ({ page }) => { - await page.goto(f.url()); - await testActionBindClient(page); - }); - - async function testActionBindClient(page: Page) { - await expect(page.getByTestId("test-server-action-bind-client")).toHaveText( - "[?]", - ); - await page - .getByRole("button", { name: "test-server-action-bind-client" }) - .click(); - await expect(page.getByTestId("test-server-action-bind-client")).toHaveText( - "true", - ); - await page - .getByRole("button", { name: "test-server-action-bind-reset" }) - .click(); - } - - test("action bind action @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await testActionBindAction(page); - }); - - testNoJs("action bind action @nojs", async ({ page }) => { - await page.goto(f.url()); - await testActionBindAction(page); - }); - - async function testActionBindAction(page: Page) { - await expect(page.getByTestId("test-server-action-bind-action")).toHaveText( - "[?]", - ); - await page - .getByRole("button", { name: "test-server-action-bind-action" }) - .click(); - await expect(page.getByTestId("test-server-action-bind-action")).toHaveText( - "[true,true]", - ); - await page - .getByRole("button", { name: "test-server-action-bind-reset" }) - .click(); - } - - test("test serialization @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("serialization")).toHaveText("?"); - await page.getByTestId("serialization").click(); - await expect(page.getByTestId("serialization")).toHaveText("ok"); - }); - - test("client-in-server package", async ({ page }) => { - await page.goto(f.url()); - await expect(page.getByTestId("client-in-server")).toHaveText( - "[test-client-in-server-dep: true]", - ); - await expect(page.getByTestId("provider-in-server")).toHaveText( - "[test-provider-in-server-dep: true]", - ); - }); - - test("server-in-server package", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("server-in-server")).toHaveText( - "server-in-server: 0", - ); - await page.getByTestId("server-in-server").click(); - await expect(page.getByTestId("server-in-server")).toHaveText( - "server-in-server: 1", - ); - await page.reload(); - await expect(page.getByTestId("server-in-server")).toHaveText( - "server-in-server: 1", - ); - }); - - test("server-in-client package", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("server-in-client")).toHaveText( - "server-in-client: ?", - ); - await page.getByTestId("server-in-client").click(); - await expect(page.getByTestId("server-in-client")).toHaveText( - "server-in-client: 1", - ); - await page.reload(); - await waitForHydration(page); - await expect(page.getByTestId("server-in-client")).toHaveText( - "server-in-client: ?", - ); - await page.getByTestId("server-in-client").click(); - await expect(page.getByTestId("server-in-client")).toHaveText( - "server-in-client: 2", - ); - }); - - test("use cache function", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - const locator = page.getByTestId("test-use-cache-fn"); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 0, cacheFnCount: 0)", - ); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 1, cacheFnCount: 1)", - ); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 2, cacheFnCount: 1)", - ); - await locator.getByRole("textbox").fill("test"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 3, cacheFnCount: 2)", - ); - await locator.getByRole("textbox").fill("test"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 4, cacheFnCount: 2)", - ); - - // revalidate cache - await locator.getByRole("textbox").fill("revalidate"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 5, cacheFnCount: 3)", - ); - await locator.getByRole("textbox").fill("test"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 6, cacheFnCount: 4)", - ); - }); - - test("use cache component", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - const static1 = await page - .getByTestId("test-use-cache-component-static") - .textContent(); - const dynamic1 = await page - .getByTestId("test-use-cache-component-dynamic") - .textContent(); - await page.waitForTimeout(100); - await page.reload(); - const static2 = await page - .getByTestId("test-use-cache-component-static") - .textContent(); - const dynamic2 = await page - .getByTestId("test-use-cache-component-dynamic") - .textContent(); - expect({ static2, dynamic2 }).toEqual({ - static2: expect.stringMatching(static1!), - dynamic2: expect.not.stringMatching(dynamic1!), - }); - }); - - test("use cache closure", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - const locator = page.getByTestId("test-use-cache-closure"); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 0, innerFnCount: 0)", - ); - - // (x, y) - await locator.getByPlaceholder("outer").fill("x"); - await locator.getByPlaceholder("inner").fill("y"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 1, innerFnCount: 1)", - ); - - // (x, y) - await locator.getByPlaceholder("outer").fill("x"); - await locator.getByPlaceholder("inner").fill("y"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 2, innerFnCount: 1)", - ); - - // (xx, y) - await locator.getByPlaceholder("outer").fill("xx"); - await locator.getByPlaceholder("inner").fill("y"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 3, innerFnCount: 2)", - ); - - // (xx, y) - await locator.getByPlaceholder("outer").fill("xx"); - await locator.getByPlaceholder("inner").fill("y"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 4, innerFnCount: 2)", - ); - - // (xx, yy) - await locator.getByPlaceholder("outer").fill("xx"); - await locator.getByPlaceholder("inner").fill("yy"); - await locator.getByRole("button").click(); - await expect(locator.locator("span")).toHaveText( - "(actionCount: 5, innerFnCount: 3)", - ); - }); -} diff --git a/packages/rsc/e2e/fixture.ts b/packages/rsc/e2e/fixture.ts deleted file mode 100644 index cc5e01d2f..000000000 --- a/packages/rsc/e2e/fixture.ts +++ /dev/null @@ -1,190 +0,0 @@ -import assert from "node:assert"; -import { type SpawnOptions, spawn } from "node:child_process"; -import fs from "node:fs"; -import path from "node:path"; -import { stripVTControlCharacters, styleText } from "node:util"; -import test from "@playwright/test"; -import { x } from "tinyexec"; - -function runCli(options: { command: string; label?: string } & SpawnOptions) { - const [name, ...args] = options.command.split(" "); - const child = x(name!, args, { nodeOptions: options }).process!; - const label = `[${options.label ?? "cli"}]`; - child.stdout!.on("data", (data) => { - if (process.env.TEST_DEBUG) { - console.log(styleText("cyan", label), data.toString()); - } - }); - child.stderr!.on("data", (data) => { - console.log(styleText("magenta", label), data.toString()); - }); - const done = new Promise((resolve) => { - child.on("exit", (code) => { - if (code !== 0 && code !== 143 && process.platform !== "win32") { - console.log(styleText("magenta", `${label}`), `exit code ${code}`); - } - resolve(); - }); - }); - - async function findPort(): Promise { - let stdout = ""; - return new Promise((resolve) => { - child.stdout!.on("data", (data) => { - stdout += stripVTControlCharacters(String(data)); - const match = stdout.match(/http:\/\/localhost:(\d+)/); - if (match) { - resolve(Number(match[1])); - } - }); - }); - } - - function kill() { - if (process.platform === "win32") { - spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"]); - } else { - child.kill(); - } - } - - return { proc: child, done, findPort, kill }; -} - -export type Fixture = ReturnType; - -export function useFixture(options: { - root: string; - mode?: "dev" | "build"; - command?: string; - buildCommand?: string; - cliOptions?: SpawnOptions; -}) { - let cleanup: (() => Promise) | undefined; - let baseURL!: string; - - const cwd = path.resolve(options.root); - - // TODO: `beforeAll` is called again on any test failure. - // https://playwright.dev/docs/test-retries - test.beforeAll(async () => { - if (options.mode === "dev") { - const proc = runCli({ - command: options.command ?? `pnpm dev`, - label: `${options.root}:dev`, - cwd, - ...options.cliOptions, - }); - const port = await proc.findPort(); - // TODO: use `test.extend` to set `baseURL`? - baseURL = `http://localhost:${port}`; - cleanup = async () => { - proc.kill(); - await proc.done; - }; - } - if (options.mode === "build") { - if (!process.env.TEST_SKIP_BUILD) { - const proc = runCli({ - command: options.buildCommand ?? `pnpm build`, - label: `${options.root}:build`, - cwd, - ...options.cliOptions, - }); - await proc.done; - } - const proc = runCli({ - command: options.command ?? `pnpm preview`, - label: `${options.root}:preview`, - cwd, - ...options.cliOptions, - }); - const port = await proc.findPort(); - baseURL = `http://localhost:${port}`; - cleanup = async () => { - proc.kill(); - await proc.done; - }; - } - }); - - test.afterAll(async () => { - await cleanup?.(); - }); - - const originalFiles: Record = {}; - - function createEditor(filepath: string) { - filepath = path.resolve(cwd, filepath); - const init = fs.readFileSync(filepath, "utf-8"); - originalFiles[filepath] ??= init; - let current = init; - return { - edit(editFn: (data: string) => string): void { - const next = editFn(current); - assert(next !== current, "Edit function did not change the content"); - current = next; - fs.writeFileSync(filepath, next); - }, - reset(): void { - fs.writeFileSync(filepath, originalFiles[filepath]!); - }, - }; - } - - test.afterAll(async () => { - for (const [filepath, content] of Object.entries(originalFiles)) { - fs.writeFileSync(filepath, content); - } - }); - - return { - mode: options.mode, - root: cwd, - url: (url: string = "./") => new URL(url, baseURL).href, - createEditor, - }; -} - -export async function setupIsolatedFixture(options: { - src: string; - dest: string; -}) { - // copy fixture - fs.rmSync(options.dest, { recursive: true, force: true }); - fs.cpSync(options.src, options.dest, { recursive: true }); - fs.rmSync(path.join(options.dest, "node_modules"), { - recursive: true, - force: true, - }); - - // setup package.json overrides - const packagesDir = path.join(import.meta.dirname, "..", ".."); - const overrides = { - "@hiogawa/transforms": `file:${path.join(packagesDir, "transforms")}`, - "@hiogawa/vite-rsc": `file:${path.join(packagesDir, "rsc")}`, - }; - editFileJson(path.join(options.dest, "package.json"), (pkg: any) => { - Object.assign(((pkg.pnpm ??= {}).overrides ??= {}), overrides); - return pkg; - }); - - // install - await x("pnpm", ["i"], { - nodeOptions: { - cwd: options.dest, - stdio: process.env.TEST_DEBUG ? "inherit" : undefined, - }, - }); -} - -function editFileJson(filepath: string, edit: (s: string) => string) { - fs.writeFileSync( - filepath, - JSON.stringify( - edit(JSON.parse(fs.readFileSync(filepath, "utf-8"))), - null, - 2, - ), - ); -} diff --git a/packages/rsc/e2e/helper.ts b/packages/rsc/e2e/helper.ts deleted file mode 100644 index bfdb6ddc5..000000000 --- a/packages/rsc/e2e/helper.ts +++ /dev/null @@ -1,44 +0,0 @@ -import test, { type Page, expect } from "@playwright/test"; - -export const testNoJs = test.extend({ - javaScriptEnabled: ({}, use) => use(false), -}); - -export async function waitForHydration(page: Page) { - await expect - .poll( - () => - page - .locator("body") - .evaluate( - (el) => - el && - Object.keys(el).some((key) => key.startsWith("__reactFiber")), - ), - { timeout: 3000 }, - ) - .toBeTruthy(); -} - -export async function expectNoReload(page: Page) { - // inject custom meta - await page.evaluate(() => { - const el = document.createElement("meta"); - el.setAttribute("name", "x-reload-check"); - document.head.append(el); - }); - - // TODO: playwright prints a weird error on dispose error, - // so maybe we shouldn't abuse this pattern :( - return { - [Symbol.asyncDispose]: async () => { - // check if meta is preserved - await expect(page.locator(`meta[name="x-reload-check"]`)).toBeAttached({ - timeout: 1, - }); - await page.evaluate(() => { - document.querySelector(`meta[name="x-reload-check"]`)!.remove(); - }); - }, - }; -} diff --git a/packages/rsc/e2e/react-router.test.ts b/packages/rsc/e2e/react-router.test.ts deleted file mode 100644 index 75094f14b..000000000 --- a/packages/rsc/e2e/react-router.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { createHash } from "node:crypto"; -import path from "node:path"; -import { expect, test } from "@playwright/test"; -import { type Fixture, useFixture } from "./fixture"; -import { expectNoReload, testNoJs, waitForHydration } from "./helper"; - -test.describe("dev-default", () => { - const f = useFixture({ root: "examples/react-router", mode: "dev" }); - defineTest(f); -}); - -test.describe("build-default", () => { - const f = useFixture({ root: "examples/react-router", mode: "build" }); - defineTest(f); -}); - -test.describe("dev-cloudflare", () => { - const f = useFixture({ - root: "examples/react-router", - mode: "dev", - command: "pnpm cf-dev", - }); - defineTest(f); -}); - -test.describe("build-cloudflare", () => { - const f = useFixture({ - root: "examples/react-router", - mode: "build", - buildCommand: "pnpm cf-build", - command: "pnpm cf-preview", - }); - defineTest(f); -}); - -function defineTest(f: Fixture) { - test("loader", async ({ page }) => { - await page.goto(f.url()); - await expect( - page.getByText(`loaderData: {"name":"Unknown"}`), - ).toBeVisible(); - }); - - test("client", async ({ page }) => { - await page.goto(f.url("./about")); - await waitForHydration(page); - await page.getByRole("button", { name: "Client counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "Client counter: 1" }), - ).toBeVisible(); - }); - - test("navigation", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - - await page.getByText("This is the home page.").click(); - - await page.getByRole("link", { name: "About" }).click(); - await page.waitForURL(f.url("./about")); - await page.getByText("This is the about page.").click(); - - await page.getByRole("link", { name: "Home" }).click(); - await page.waitForURL(f.url()); - await page.getByText("This is the home page.").click(); - }); - - test.describe(() => { - test.skip(f.mode !== "build"); - - testNoJs("ssr modulepreload", async ({ page }) => { - await page.goto(f.url()); - const srcs = await page - .locator(`head >> link[rel="modulepreload"]`) - .evaluateAll((elements) => - elements.map((el) => el.getAttribute("href")), - ); - const { default: manifest } = await import( - path.resolve(f.root, "dist/ssr/__vite_rsc_assets_manifest.js") - ); - const hashString = (v: string) => - createHash("sha256").update(v).digest().toString("hex").slice(0, 12); - const deps = - manifest.clientReferenceDeps[hashString("app/routes/home.client.tsx")]; - expect(srcs).toEqual(expect.arrayContaining(deps.js)); - }); - }); - - test.describe(() => { - test.skip(f.mode !== "dev"); - - test("client hmr", async ({ page }) => { - await page.goto(f.url("./about")); - await waitForHydration(page); - await using _ = await expectNoReload(page); - - await page.getByRole("button", { name: "Client counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "Client counter: 1" }), - ).toBeVisible(); - - const editor = f.createEditor("app/routes/about.tsx"); - editor.edit((s) => - s.replace("Client counter:", "Client [edit] counter:"), - ); - - await expect( - page.getByRole("button", { name: "Client [edit] counter: 1" }), - ).toBeVisible(); - }); - - test("server hmr", async ({ page }) => { - await page.goto(f.url("/")); - await waitForHydration(page); - await using _ = await expectNoReload(page); - - await page.getByText("This is the home page.").click(); - - const editor = f.createEditor("app/routes/home.tsx"); - editor.edit((s) => - s.replace("This is the home page.", "This is the home [edit] page."), - ); - - await page.getByText("This is the home [edit] page.").click(); - }); - }); - - test("server css code split", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.locator(".test-style-home")).toHaveCSS( - "color", - "rgb(250, 150, 0)", - ); - - // client side navigation to "/about" keeps "/" styles - await page.getByRole("link", { name: "About" }).click(); - await page.waitForURL(f.url("./about")); - await expect(page.locator(".test-style-home")).toHaveCSS( - "color", - "rgb(250, 150, 0)", - ); - - // SSR of "/about" doesn't include "/" styles - await page.goto(f.url("./about")); - await waitForHydration(page); - await expect(page.locator(".test-style-home")).not.toHaveCSS( - "color", - "rgb(250, 150, 0)", - ); - - // client side navigation to "/" loads "/" styles - await page.getByRole("link", { name: "Home" }).click(); - await page.waitForURL(f.url()); - await expect(page.locator(".test-style-home")).toHaveCSS( - "color", - "rgb(250, 150, 0)", - ); - }); - - test("vite-rsc-css-export", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByTestId("root-style")).toHaveCSS( - "color", - "rgb(0, 0, 255)", - ); - }); - - test("useActionState", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await page.getByTestId("use-action-state-jsx").getByRole("button").click(); - await expect(page.getByTestId("use-action-state-jsx")).toContainText( - /\(ok\)/, - ); - await page.getByTestId("use-action-state-jsx").getByRole("button").click(); - await expect(page.getByTestId("use-action-state-jsx")).toContainText( - /\(ok\).*\(ok\)/, - ); - }); -} diff --git a/packages/rsc/e2e/ssg.test.ts b/packages/rsc/e2e/ssg.test.ts deleted file mode 100644 index 4ebeda083..000000000 --- a/packages/rsc/e2e/ssg.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { type Fixture, useFixture } from "./fixture"; -import { waitForHydration } from "./helper"; - -test.describe("dev", () => { - const f = useFixture({ - root: "examples/ssg", - mode: "dev", - }); - defineTestSsg(f); -}); - -test.describe("build", () => { - const f = useFixture({ - root: "examples/ssg", - mode: "build", - }); - defineTestSsg(f); -}); - -function defineTestSsg(f: Fixture) { - test("basic", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - - if (f.mode === "build") { - const t1 = await page.getByTestId("timestamp").textContent(); - await page.waitForTimeout(100); - await page.reload(); - await waitForHydration(page); - const t2 = await page.getByTestId("timestamp").textContent(); - expect(t2).toBe(t1); - } - }); -} diff --git a/packages/rsc/e2e/starter.test.ts b/packages/rsc/e2e/starter.test.ts deleted file mode 100644 index a9e897b4e..000000000 --- a/packages/rsc/e2e/starter.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { type Fixture, useFixture } from "./fixture"; -import { expectNoReload, testNoJs, waitForHydration } from "./helper"; - -test.describe("dev-default", () => { - const f = useFixture({ root: "examples/starter", mode: "dev" }); - defineTest(f); -}); - -test.describe("build-default", () => { - const f = useFixture({ root: "examples/starter", mode: "build" }); - defineTest(f); -}); - -test.describe("dev-cloudflare", () => { - const f = useFixture({ root: "examples/starter-cf-single", mode: "dev" }); - defineTest(f); -}); - -test.describe("build-cloudflare", () => { - const f = useFixture({ root: "examples/starter-cf-single", mode: "build" }); - defineTest(f); -}); - -function defineTest(f: Fixture) { - test("basic", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - }); - - test("client component", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await page.getByRole("button", { name: "Client Counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "Client Counter: 1" }), - ).toBeVisible(); - }); - - test("server action @js", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await using _ = await expectNoReload(page); - await page.getByRole("button", { name: "Server Counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "Server Counter: 1" }), - ).toBeVisible(); - }); - - testNoJs("server action @nojs", async ({ page }) => { - await page.goto(f.url()); - await page.getByRole("button", { name: "Server Counter: 1" }).click(); - await expect( - page.getByRole("button", { name: "Server Counter: 2" }), - ).toBeVisible(); - }); - - test("client hmr", async ({ page }) => { - test.skip(f.mode === "build"); - - await page.goto(f.url()); - await waitForHydration(page); - await page.getByRole("button", { name: "Client Counter: 0" }).click(); - await expect( - page.getByRole("button", { name: "Client Counter: 1" }), - ).toBeVisible(); - - const editor = f.createEditor(`src/client.tsx`); - editor.edit((s) => s.replace("Client Counter", "Client [edit] Counter")); - await expect( - page.getByRole("button", { name: "Client [edit] Counter: 1" }), - ).toBeVisible(); - - // check next ssr is also updated - const res = await page.goto(f.url()); - expect(await res?.text()).toContain("Client [edit] Counter"); - await waitForHydration(page); - editor.reset(); - await page.getByRole("button", { name: "Client Counter: 0" }).click(); - }); - - test("image assets", async ({ page }) => { - await page.goto(f.url()); - await waitForHydration(page); - await expect(page.getByAltText("Vite logo")).not.toHaveJSProperty( - "naturalWidth", - 0, - ); - await expect(page.getByAltText("React logo")).not.toHaveJSProperty( - "naturalWidth", - 0, - ); - }); -} diff --git a/packages/rsc/e2e/tsconfig.json b/packages/rsc/e2e/tsconfig.json deleted file mode 100644 index fc2422768..000000000 --- a/packages/rsc/e2e/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "noPropertyAccessFromIndexSignature": false, - "noImplicitReturns": false, - "checkJs": false - } -} diff --git a/packages/rsc/examples/basic/README.md b/packages/rsc/examples/basic/README.md deleted file mode 100644 index f96083231..000000000 --- a/packages/rsc/examples/basic/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# rsc basic - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/basic) - -https://vite-rsc-basic.hiro18181.workers.dev - -```sh -npx giget gh:hi-ogawa/vite-plugins/packages/rsc/examples/basic my-app -``` diff --git a/packages/rsc/examples/basic/package.json b/packages/rsc/examples/basic/package.json deleted file mode 100644 index 3a45dccee..000000000 --- a/packages/rsc/examples/basic/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-basic", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build --app", - "preview": "vite preview", - "cf-build": "CF_BUILD=1 pnpm build", - "cf-preview": "wrangler dev", - "cf-release": "wrangler deploy" - }, - "dependencies": { - "@hiogawa/vite-rsc": "latest", - "react": "latest", - "react-dom": "latest" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.1.4", - "@types/react": "latest", - "@types/react-dom": "latest", - "@vitejs/plugin-react": "latest", - "@vitejs/test-dep-client-in-server": "file:./test-dep/client-in-server", - "@vitejs/test-dep-client-in-server2": "file:./test-dep/client-in-server2", - "@vitejs/test-dep-server-in-client": "file:./test-dep/server-in-client", - "@vitejs/test-dep-server-in-server": "file:./test-dep/server-in-server", - "tailwindcss": "^4.1.4", - "vite": "latest", - "vite-plugin-inspect": "^11.2.0" - } -} diff --git a/packages/rsc/examples/basic/public/favicon.ico b/packages/rsc/examples/basic/public/favicon.ico deleted file mode 100644 index 4aff076603f8f01fb3b7d8b5e38df77cc1efdd69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmc(jeOQ&{9mmi9Y5(N4wKaEHmR6cpj%~hXo13+jb7bn2986aeCEr<~nOgZmpr$E` zf`o4brE!#(M1gZu646vl&`1pxMdw?|n=m>c+@F0P;mO>tlyk0ib#r~sdCqh0=e|Gp z@BY2q2Z!Sa`s>)yVZAyw{@CGY>u@-_O1kCfFIo4mH+}bzU$dHTI2`!e8Vx4;1ZKh^ zxKi)yD84^;xC-;kZcMM8u;Hxl=0 zrl(}R$D9P?+i+ezRaK9fv=7rK8`IkjJ!J!H{(<@MS+!R~`O?{>ox=Qa3-rsRXz(63 z2OX9VwCuy2qfp=E%do9`=6nq%BuDe7!jtKI6u6hZ(Up|S59#T~^xp-0ue5F~Z+jVS zZek&3&_;~E69#PN+~91w#MclR`Z@H~e)M!gs0@p&odJ103qFqWoKTLTHzP(@mAxzj(#e6hoycovD(ij}ivB{e#lRuP=cG5k$iaRo)dtx=G zM`-QOi1(><%*tYDlHdB;$}2ES5-{NlF)zQ3nIet}%c&n1$C&sM{8FwGkZ_g`sUGg! z3T>ptZyW}H`KjW=1Y|*Rxo@*u@#a=udF@Tij3~@2VhCG&oq>tVm=kl5JGI?yq#tEV znyQW7d%s`BnUNog;R7)^G4l^7CpCJ{a9%Yn`Yp_y*_byXFtZ{t(-+7Viy(9{&V^aD zbs2uD6#Nr0*t#nRmP8cFH{WZ{6~oeCpO^7?twAxY_*(EPM7@rgXEDr!SrITj3c}}8 zG&hNdv%li*9q=>hZiT(2yL8>xDH{YUC=?&&*$uMcN6JZFoDoXp$8j^YkZG`J2F#yD zT~q{{;?mf*+{vkkRh*ogz;80kX{(g`^V}P1yAv!$FZ=}TTrvCu$T`)zJ@6=Iw#2~7 zDX=^omRbzcx$<@-Pv=~u!|o<}xvTke<^?+EHqb5)?vdJw!(HPN#foO70Yl-k>>`W6NT?SD*WDt)y&<{AiS7Soz8*XK=RiTNo5?#oDKo7j&K*4as9+{Y$WBf^0FF zhPW_7T*Y)e2oGsl4=Su|Js>7q|IHS&LZG8pDR+w@NcCf#pc|Y1m!a|mxHMN<5IFx1 z8~>pkE;}TKz}oRNCQKpRUBH8fptD@dQYW$4vY4(*To35FgLJ?22Ui7UQ&lU1W0kw( zmA{hV*u|UYZ&JK{x(u?0L*`#0{co^Y3@IiNbQp!w zO~^{sk=0O8+H8YsRd7@>zI!0-dJcAo!6iFvdtPxEsQr*FoZ{FpidEZFc~oV-Tk*|$ zOiZ@A>Uvn-1u7yJ!OXeJSCKGPdDF_>p{gGfEe6%GMd?tjc)TqC{6ur_9{_tS27fUO z62lPLLUzcOFS-?jc~K-4?ZfXl{PI|{{KR7G7oUNpZc?8j4+TjnD#%v;R^FZ=O;ZgC zQw<4K9kXibq*$#{4s=ZGD|!+NHE*G=kG!mVVn4kRE-IKSOQ7zG>Zpg*Dnk!_{PpKI z^ege$`kG68tF7N7moCV*M=`td75h4u#3o_hR4h!F4JOMLvlF2rN59`Pl%M4|o^y(g zS}=__`)A81R}DOVN=Mz3(8JOR)qGpx>fXX;*=W+gG@L^E>t@wck4JM=z=<;1T8?r+ z2K1tp)T{Jd($Pnl{u<(`)5^1QqbH<3;_5B+5|_m^P~VlR|NpaD%c*J7PrU}Yugsfq z6=KWbwaXB4Ua4MOgTyu9jE-TFWv}nL35VJirUPP1tyZ~^yM!slY|{1Zn*D!(@9X_P D`VsgZ diff --git a/packages/rsc/examples/basic/src/client.tsx b/packages/rsc/examples/basic/src/client.tsx deleted file mode 100644 index 4c085f42d..000000000 --- a/packages/rsc/examples/basic/src/client.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { hydrate } from "@hiogawa/vite-rsc/extra/browser"; - -hydrate(); diff --git a/packages/rsc/examples/basic/src/routes/action-bind/client.tsx b/packages/rsc/examples/basic/src/routes/action-bind/client.tsx deleted file mode 100644 index cc50ddfb8..000000000 --- a/packages/rsc/examples/basic/src/routes/action-bind/client.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client"; - -import React from "react"; - -export function ActionBindClient() { - const hydrated = React.useSyncExternalStore( - React.useCallback(() => () => {}, []), - () => true, - () => false, - ); - return <>{String(hydrated)}; -} diff --git a/packages/rsc/examples/basic/src/routes/action-bind/form.tsx b/packages/rsc/examples/basic/src/routes/action-bind/form.tsx deleted file mode 100644 index c754458b7..000000000 --- a/packages/rsc/examples/basic/src/routes/action-bind/form.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestServerActionBindClientForm(props: { - action: () => Promise; -}) { - const [result, formAction] = React.useActionState(props.action, "[?]"); - - return ( -
- - {result} -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/action-bind/server.tsx b/packages/rsc/examples/basic/src/routes/action-bind/server.tsx deleted file mode 100644 index 412adeb01..000000000 --- a/packages/rsc/examples/basic/src/routes/action-bind/server.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// based on test cases in -// https://github.com/vercel/next.js/blob/ad898de735c393d98960a68c8d9eaeee32206c57/test/e2e/app-dir/actions/app/encryption/page.js - -import { ActionBindClient } from "./client"; -import { TestServerActionBindClientForm } from "./form"; - -export function TestServerActionBindReset() { - return ( -
{ - "use server"; - testServerActionBindSimpleState = "[?]"; - testServerActionBindActionState = "[?]"; - testServerActionBindClientState++; - }} - > - -
- ); -} - -let testServerActionBindSimpleState = "[?]"; - -export function TestServerActionBindSimple() { - const outerValue = "outerValue"; - - return ( -
{ - "use server"; - const result = String(formData.get("value")) === outerValue; - testServerActionBindSimpleState = JSON.stringify(result); - }} - > - - - - {testServerActionBindSimpleState} - -
- ); -} - -let testServerActionBindClientState = 0; - -export function TestServerActionBindClient() { - // client element as server action bound argument - const client = ; - - const action = async () => { - "use server"; - return client; - }; - - return ( - - ); -} - -let testServerActionBindActionState = "[?]"; - -export function TestServerActionBindAction() { - async function otherAction() { - "use server"; - return "otherActionValue"; - } - - function wrapAction(value: string, action: () => Promise) { - return async function (formValue: string) { - "use server"; - const actionValue = await action(); - return [actionValue === "otherActionValue", formValue === value]; - }; - } - - const action = wrapAction("ok", otherAction); - - return ( -
{ - "use server"; - const result = await action(String(formData.get("value"))); - testServerActionBindActionState = JSON.stringify(result); - }} - > - - - - {testServerActionBindActionState} - -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/action-error/error-boundary.tsx b/packages/rsc/examples/basic/src/routes/action-error/error-boundary.tsx deleted file mode 100644 index 625f3a69d..000000000 --- a/packages/rsc/examples/basic/src/routes/action-error/error-boundary.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import * as React from "react"; - -interface Props { - children?: React.ReactNode; -} - -interface State { - error: Error | null; -} - -export default class ErrorBoundary extends React.Component { - constructor(props: Props) { - super(props); - this.state = { error: null }; - } - - static getDerivedStateFromError(error: Error) { - return { error }; - } - - render() { - if (this.state.error) { - return ( -
- ErrorBoundary caught '{this.state.error.message}' - -
- ); - } - return this.props.children; - } -} diff --git a/packages/rsc/examples/basic/src/routes/action-error/server.tsx b/packages/rsc/examples/basic/src/routes/action-error/server.tsx deleted file mode 100644 index 2501c7b37..000000000 --- a/packages/rsc/examples/basic/src/routes/action-error/server.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import ErrorBoundary from "./error-boundary"; - -// see browser console to verify that server action error shows -// server component stack with correct source map - -export function TestServerActionError() { - return ( - -
{ - "use server"; - throw new Error("boom!"); - }} - > - -
-
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/action-from-client/action.tsx b/packages/rsc/examples/basic/src/routes/action-from-client/action.tsx deleted file mode 100644 index 1aa6ff090..000000000 --- a/packages/rsc/examples/basic/src/routes/action-from-client/action.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use server"; - -// test findSourceMapURL for server action imported from client - -export async function notThis() { - // - // - // - notThis2(); -} - -export async function testAction() { - console.log("[test-action-from-client]"); -} - -function notThis2() { - // - // -} - -export async function testAction2() { - console.log("[test-action-from-client-2]"); -} - -export async function testActionState(prev: number) { - return prev + 1; -} diff --git a/packages/rsc/examples/basic/src/routes/action-from-client/client.tsx b/packages/rsc/examples/basic/src/routes/action-from-client/client.tsx deleted file mode 100644 index af0ab71d7..000000000 --- a/packages/rsc/examples/basic/src/routes/action-from-client/client.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import React from "react"; -import { testAction, testAction2, testActionState } from "./action"; - -export function TestActionFromClient() { - return ( -
- - -
- ); -} - -export function TestUseActionState() { - const [state, formAction] = React.useActionState(testActionState, 0); - - return ( -
- -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/action-state/client.tsx b/packages/rsc/examples/basic/src/routes/action-state/client.tsx deleted file mode 100644 index f74d63e97..000000000 --- a/packages/rsc/examples/basic/src/routes/action-state/client.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestActionStateClient(props: { - action: (prev: React.ReactNode) => Promise; -}) { - const [state, formAction, isPending] = React.useActionState( - props.action, - null, - ); - - return ( -
- - {isPending ? "pending..." : state} -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/action-state/server.tsx b/packages/rsc/examples/basic/src/routes/action-state/server.tsx deleted file mode 100644 index bb3720421..000000000 --- a/packages/rsc/examples/basic/src/routes/action-state/server.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { TestActionStateClient } from "./client"; - -// Test case based on -// https://github.com/remix-run/react-router/issues/13882 - -export function TestActionStateServer() { - const time = new Date().toISOString(); // test closure encryption - return ( - { - "use server"; - await new Promise((resolve) => setTimeout(resolve, 500)); - return ( - - [(ok) (time: {time})] {prev} - - ); - }} - /> - ); -} diff --git a/packages/rsc/examples/basic/src/routes/action/action.tsx b/packages/rsc/examples/basic/src/routes/action/action.tsx deleted file mode 100644 index 1a94ac343..000000000 --- a/packages/rsc/examples/basic/src/routes/action/action.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use server"; - -let serverCounter = 0; - -export async function getServerCounter(): Promise { - return serverCounter; -} - -export async function changeServerCounter(formData: FormData): Promise { - const TEST_UPDATE = 1; - serverCounter += Number(formData.get("change")) * TEST_UPDATE; -} - -export async function resetServerCounter(): Promise { - serverCounter = 0; -} diff --git a/packages/rsc/examples/basic/src/routes/action/server.tsx b/packages/rsc/examples/basic/src/routes/action/server.tsx deleted file mode 100644 index cc16a9d23..000000000 --- a/packages/rsc/examples/basic/src/routes/action/server.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { - changeServerCounter, - getServerCounter, - resetServerCounter, -} from "./action"; - -export function ServerCounter() { - return ( -
- - - -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/client.tsx b/packages/rsc/examples/basic/src/routes/client.tsx deleted file mode 100644 index 1da3d2825..000000000 --- a/packages/rsc/examples/basic/src/routes/client.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import React from "react"; - -export function ClientCounter(): React.ReactElement { - const [count, setCount] = React.useState(0); - return ( - - ); -} - -const noop = () => () => {}; -export function Hydrated() { - const hydrated = React.useSyncExternalStore( - noop, - () => true, - () => false, - ); - return [hydrated: {hydrated ? 1 : 0}]; -} - -export function UnusedClientReference() { - console.log("__unused_client_reference__"); -} diff --git a/packages/rsc/examples/basic/src/routes/deps/client-in-server/client.tsx b/packages/rsc/examples/basic/src/routes/deps/client-in-server/client.tsx deleted file mode 100644 index 3acae58bc..000000000 --- a/packages/rsc/examples/basic/src/routes/deps/client-in-server/client.tsx +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -// @ts-ignore -import { TestContextValue } from "@vitejs/test-dep-client-in-server2/client"; - -export function TestContextValueIndirect() { - return ; -} diff --git a/packages/rsc/examples/basic/src/routes/deps/client-in-server/server.tsx b/packages/rsc/examples/basic/src/routes/deps/client-in-server/server.tsx deleted file mode 100644 index 904ebe7d4..000000000 --- a/packages/rsc/examples/basic/src/routes/deps/client-in-server/server.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-ignore -import { TestClientInServerDep } from "@vitejs/test-dep-client-in-server/server"; -// @ts-ignore -import { TestContextProviderInServer } from "@vitejs/test-dep-client-in-server2/server"; -import { TestContextValueIndirect } from "./client"; - -export function TestClientInServer() { - return ( -
-
- [test-client-in-server-dep: ] -
-
- [test-provider-in-server-dep:{" "} - - - - ] -
-
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/deps/server-in-client/client.tsx b/packages/rsc/examples/basic/src/routes/deps/server-in-client/client.tsx deleted file mode 100644 index c3aa6aa05..000000000 --- a/packages/rsc/examples/basic/src/routes/deps/server-in-client/client.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// @ts-ignore -import { TestClient } from "@vitejs/test-dep-server-in-client/client"; - -export function TestServerInClient() { - return ; -} diff --git a/packages/rsc/examples/basic/src/routes/deps/server-in-server/server.tsx b/packages/rsc/examples/basic/src/routes/deps/server-in-server/server.tsx deleted file mode 100644 index c0bc333eb..000000000 --- a/packages/rsc/examples/basic/src/routes/deps/server-in-server/server.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// @ts-ignore -import { ServerCounter } from "@vitejs/test-dep-server-in-server/server"; - -export function TestServerInServer() { - return ; -} diff --git a/packages/rsc/examples/basic/src/routes/hmr-client-dep/client-dep.tsx b/packages/rsc/examples/basic/src/routes/hmr-client-dep/client-dep.tsx deleted file mode 100644 index 22b30bd6c..000000000 --- a/packages/rsc/examples/basic/src/routes/hmr-client-dep/client-dep.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function ClientDep() { - return <>[ok]; -} diff --git a/packages/rsc/examples/basic/src/routes/hmr-client-dep/client.tsx b/packages/rsc/examples/basic/src/routes/hmr-client-dep/client.tsx deleted file mode 100644 index 93387e88d..000000000 --- a/packages/rsc/examples/basic/src/routes/hmr-client-dep/client.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import React from "react"; -import { ClientDep } from "./client-dep"; - -export function TestHmrClientDep() { - const [count, setCount] = React.useState(0); - return ( -
- - -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/module-invalidation/server-dep.tsx b/packages/rsc/examples/basic/src/routes/module-invalidation/server-dep.tsx deleted file mode 100644 index dc3b9be29..000000000 --- a/packages/rsc/examples/basic/src/routes/module-invalidation/server-dep.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const dep = { - value: 0, -}; diff --git a/packages/rsc/examples/basic/src/routes/module-invalidation/server.tsx b/packages/rsc/examples/basic/src/routes/module-invalidation/server.tsx deleted file mode 100644 index dea2bb015..000000000 --- a/packages/rsc/examples/basic/src/routes/module-invalidation/server.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { dep } from "./server-dep"; - -export function TestModuleInvalidationServer() { - return ( -
-
{ - "use server"; - dep.value ^= 1; - }} - > - - [dep: {dep.value}] -
-
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/payload/client.tsx b/packages/rsc/examples/basic/src/routes/payload/client.tsx deleted file mode 100644 index 2e35a4e46..000000000 --- a/packages/rsc/examples/basic/src/routes/payload/client.tsx +++ /dev/null @@ -1,29 +0,0 @@ -"use client"; - -export function TestPayloadClient(props: { - test1?: any; - test2?: any; - test3?: any; - test4?: any; -}) { - const results = { - test1: props.test1 === "🙂", - test2: props.test2 === "", - test3: - props.test3 instanceof Uint8Array && - isSameArray(props.test3, new TextEncoder().encode("🔥").reverse()), - test4: props.test4 === "&><\u2028\u2029", - }; - const formatted = Object.entries(results) - .map(([k, v]) => `${k}: ${String(v)}`) - .join(", "); - return <>{formatted}; -} - -function isSameArray(x: Uint8Array, y: Uint8Array) { - if (x.length !== y.length) return false; - for (let i = 0; i < x.length; i++) { - if (x[i] !== y[i]) return false; - } - return true; -} diff --git a/packages/rsc/examples/basic/src/routes/payload/server.tsx b/packages/rsc/examples/basic/src/routes/payload/server.tsx deleted file mode 100644 index fbc3ea04f..000000000 --- a/packages/rsc/examples/basic/src/routes/payload/server.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { TestPayloadClient } from "./client"; - -export function TestPayloadServer(props: { url: URL }) { - return ( -
- test-payload (binary):{" "} - - throw new Error('boom')"} - test3={ - // disabled by default so that it won't break Stackblitz demo - // https://github.com/stackblitz/webcontainer-core/issues/1861 - props.url.searchParams.has("test-payload-binary") - ? // reverse to have non-utf8 binary data - new TextEncoder() - .encode("🔥") - .reverse() - : null - } - test4={"&><\u2028\u2029"} - /> - -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/root.tsx b/packages/rsc/examples/basic/src/routes/root.tsx deleted file mode 100644 index f4d162566..000000000 --- a/packages/rsc/examples/basic/src/routes/root.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from "react"; -import { - TestServerActionBindAction, - TestServerActionBindClient, - TestServerActionBindReset, - TestServerActionBindSimple, -} from "./action-bind/server"; -import { TestServerActionError } from "./action-error/server"; -import { - TestActionFromClient, - TestUseActionState, -} from "./action-from-client/client"; -import { TestActionStateServer } from "./action-state/server"; -import { ServerCounter } from "./action/server"; -import { ClientCounter, Hydrated } from "./client"; -import { TestClientInServer } from "./deps/client-in-server/server"; -import { TestServerInClient } from "./deps/server-in-client/client"; -import { TestServerInServer } from "./deps/server-in-server/server"; -import { TestHmrClientDep } from "./hmr-client-dep/client"; -import { TestModuleInvalidationServer } from "./module-invalidation/server"; -import { TestPayloadServer } from "./payload/server"; -import { TestSerializationServer } from "./serialization/server"; -import { TestCssClientNoSsr } from "./style-client-no-ssr/server"; -import { TestStyleClient } from "./style-client/client"; -import { TestStyleServer } from "./style-server/server"; -import { TestTailwindClient } from "./tailwind/client"; -import { TestTailwindServer } from "./tailwind/server"; -import { TestTemporaryReference } from "./temporary-reference/client"; -import { TestUseCache } from "./use-cache/server"; - -export function Root(props: { url: URL }) { - return ( - - - vite-rsc - {import.meta.viteRsc.loadCss("/src/routes/root.tsx")} - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -function TestReplayConsoleLogs(props: { url: URL }) { - if (props.url.search.includes("test-replay-console-logs")) { - console.log("[test-replay-console-logs]"); - } - return test-replayConsoleLogs; -} - -function TestSuspense(props: { url: URL }) { - if (props.url.search.includes("test-suspense")) { - const ms = Number(props.url.searchParams.get("test-suspense")) || 1000; - async function Inner() { - await new Promise((resolve) => setTimeout(resolve, ms)); - return
suspense-resolved
; - } - return ( -
- suspense-fallback
}> - - - - ); - } - return test-suspense; -} diff --git a/packages/rsc/examples/basic/src/routes/serialization/action.tsx b/packages/rsc/examples/basic/src/routes/serialization/action.tsx deleted file mode 100644 index dd7bad9d5..000000000 --- a/packages/rsc/examples/basic/src/routes/serialization/action.tsx +++ /dev/null @@ -1,6 +0,0 @@ -"use server"; - -export async function testSerializationAction() { - console.log("[test-serialization-action]"); - return "ok"; -} diff --git a/packages/rsc/examples/basic/src/routes/serialization/client.tsx b/packages/rsc/examples/basic/src/routes/serialization/client.tsx deleted file mode 100644 index f0dc7b4ad..000000000 --- a/packages/rsc/examples/basic/src/routes/serialization/client.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestSerializationClient(props: { action: () => Promise }) { - const [state, setState] = React.useState("?"); - return ( - - ); -} diff --git a/packages/rsc/examples/basic/src/routes/serialization/server.tsx b/packages/rsc/examples/basic/src/routes/serialization/server.tsx deleted file mode 100644 index 4b9e81a46..000000000 --- a/packages/rsc/examples/basic/src/routes/serialization/server.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { - createFromReadableStream, - renderToReadableStream, -} from "@hiogawa/vite-rsc/rsc"; -import { testSerializationAction } from "./action"; -import { TestSerializationClient } from "./client"; - -export function TestSerializationServer() { - const original = ; - let serialized = renderToReadableStream(original); - // debug serialization - if (0) { - serialized = serialized - .pipeThrough(new TextDecoderStream()) - .pipeThrough( - new TransformStream({ - transform(chunk, controller) { - console.log("[test-serialization]", { chunk }); - controller.enqueue(chunk); - }, - }), - ) - .pipeThrough(new TextEncoderStream()); - } - const deserialized = createFromReadableStream(serialized); - return
test-serialization:{deserialized}
; -} diff --git a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.css b/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.css deleted file mode 100644 index 96c363257..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.css +++ /dev/null @@ -1,3 +0,0 @@ -.test-style-client-no-ssr { - color: rgb(0, 200, 100); -} diff --git a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.tsx b/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.tsx deleted file mode 100644 index e90272c03..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/client.tsx +++ /dev/null @@ -1,7 +0,0 @@ -"use client"; - -import "./client.css"; - -export function TestClient() { - return [test]; -} diff --git a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/server.tsx b/packages/rsc/examples/basic/src/routes/style-client-no-ssr/server.tsx deleted file mode 100644 index caabf0759..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client-no-ssr/server.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { TestClient } from "./client"; - -export function TestCssClientNoSsr(props: { url: URL }) { - return ( -
- test-client-style-no-ssr{" "} - show hide{" "} - {props.url.searchParams.has("test-client-style-no-ssr") && } -
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/style-client/client-dep.css b/packages/rsc/examples/basic/src/routes/style-client/client-dep.css deleted file mode 100644 index a58f3cfcf..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client/client-dep.css +++ /dev/null @@ -1,3 +0,0 @@ -.test-style-client-dep { - color: rgb(255, 165, 0); -} diff --git a/packages/rsc/examples/basic/src/routes/style-client/client-dep.tsx b/packages/rsc/examples/basic/src/routes/style-client/client-dep.tsx deleted file mode 100644 index 228896197..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client/client-dep.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import "./client-dep.css"; - -export function TestClientDep() { - return
test-style-client-dep
; -} diff --git a/packages/rsc/examples/basic/src/routes/style-client/client.css b/packages/rsc/examples/basic/src/routes/style-client/client.css deleted file mode 100644 index bd95cc0fd..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client/client.css +++ /dev/null @@ -1,5 +0,0 @@ -/* css imported by client references */ - -.test-style-client { - color: rgb(255, 165, 0); -} diff --git a/packages/rsc/examples/basic/src/routes/style-client/client.module.css b/packages/rsc/examples/basic/src/routes/style-client/client.module.css deleted file mode 100644 index 7b8fea47b..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client/client.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.client { - color: rgb(255, 165, 0); -} diff --git a/packages/rsc/examples/basic/src/routes/style-client/client.tsx b/packages/rsc/examples/basic/src/routes/style-client/client.tsx deleted file mode 100644 index dd8c210d0..000000000 --- a/packages/rsc/examples/basic/src/routes/style-client/client.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import "./client.css"; -import { TestClientDep } from "./client-dep"; -import styles from "./client.module.css"; - -export function TestStyleClient() { - return ( - <> -
test-style-client
-
- test-css-module-client -
- - - ); -} diff --git a/packages/rsc/examples/basic/src/routes/style-server/server.css b/packages/rsc/examples/basic/src/routes/style-server/server.css deleted file mode 100644 index 480fa1388..000000000 --- a/packages/rsc/examples/basic/src/routes/style-server/server.css +++ /dev/null @@ -1,3 +0,0 @@ -.test-style-server { - color: rgb(255, 165, 0); -} diff --git a/packages/rsc/examples/basic/src/routes/style-server/server.module.css b/packages/rsc/examples/basic/src/routes/style-server/server.module.css deleted file mode 100644 index a391a735e..000000000 --- a/packages/rsc/examples/basic/src/routes/style-server/server.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.server { - color: rgb(255, 165, 0); -} diff --git a/packages/rsc/examples/basic/src/routes/style-server/server.tsx b/packages/rsc/examples/basic/src/routes/style-server/server.tsx deleted file mode 100644 index 027223544..000000000 --- a/packages/rsc/examples/basic/src/routes/style-server/server.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import "./server.css"; -import styles from "./server.module.css"; - -export function TestStyleServer() { - return ( - <> -
test-style-server
-
- test-css-module-server -
- - ); -} diff --git a/packages/rsc/examples/basic/src/routes/tailwind/client.tsx b/packages/rsc/examples/basic/src/routes/tailwind/client.tsx deleted file mode 100644 index 68e13664e..000000000 --- a/packages/rsc/examples/basic/src/routes/tailwind/client.tsx +++ /dev/null @@ -1,5 +0,0 @@ -"use client"; - -export function TestTailwindClient() { - return
test-tw-client
; -} diff --git a/packages/rsc/examples/basic/src/routes/tailwind/server.tsx b/packages/rsc/examples/basic/src/routes/tailwind/server.tsx deleted file mode 100644 index 7cad01239..000000000 --- a/packages/rsc/examples/basic/src/routes/tailwind/server.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function TestTailwindServer() { - return
test-tw-server
; -} diff --git a/packages/rsc/examples/basic/src/routes/temporary-reference/action.tsx b/packages/rsc/examples/basic/src/routes/temporary-reference/action.tsx deleted file mode 100644 index df5c5e448..000000000 --- a/packages/rsc/examples/basic/src/routes/temporary-reference/action.tsx +++ /dev/null @@ -1,10 +0,0 @@ -"use server"; - -export async function action(node: React.ReactNode) { - "use server"; - return ( - - [server {node}] - - ); -} diff --git a/packages/rsc/examples/basic/src/routes/temporary-reference/client.tsx b/packages/rsc/examples/basic/src/routes/temporary-reference/client.tsx deleted file mode 100644 index 469512d6c..000000000 --- a/packages/rsc/examples/basic/src/routes/temporary-reference/client.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import React from "react"; -import { action } from "./action"; - -export function TestTemporaryReference() { - const [result, setResult] = React.useState("(none)"); - - return ( -
-
{ - setResult(await action([client])); - }} - > - -
-
result: {result}
-
- ); -} diff --git a/packages/rsc/examples/basic/src/routes/use-cache/server.tsx b/packages/rsc/examples/basic/src/routes/use-cache/server.tsx deleted file mode 100644 index 332c3d448..000000000 --- a/packages/rsc/examples/basic/src/routes/use-cache/server.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { revalidateCache } from "../../use-cache-runtime"; - -export function TestUseCache() { - return ( - <> - - - - - ); -} - -function TestUseCacheFn() { - return ( -
{ - "use server"; - actionCount++; - const argument = formData.get("argument"); - await testFn(argument); - if (argument === "revalidate") { - revalidateCache(testFn); - } - }} - > - - - - (actionCount: {actionCount}, cacheFnCount: {cacheFnCount}) - -
- ); -} - -let actionCount = 0; -let cacheFnCount = 0; - -async function testFn(..._args: unknown[]) { - "use cache"; - cacheFnCount++; -} - -function TestUseCacheComponent() { - // NOTE: wrapping with `span` (or any jsx) is crucial because - // raw string `children` would get included as cache key - // and thus causes `TestComponent` to be evaluated in each render. - return ( - - {new Date().toISOString()} - - ); -} - -async function TestComponent(props: { children?: React.ReactNode }) { - "use cache"; - return ( -
- [test-use-cache-component]{" "} - - (static: {new Date().toISOString()}) - {" "} - - (dynamic: {props.children}) - -
- ); -} - -async function TestUseCacheClosure() { - return ( -
-
{ - "use server"; - actionCount2++; - outerFnArg = formData.get("outer") as string; - innerFnArg = formData.get("inner") as string; - await outerFn(outerFnArg)(innerFnArg); - }} - > - - - -
- - (actionCount: {actionCount2}, innerFnCount: {innerFnCount}) - -
- ); -} - -function outerFn(outer: string) { - async function innerFn(inner: string) { - "use cache"; - innerFnCount++; - console.log({ outer, inner }); - } - return innerFn; -} - -let outerFnArg = ""; -let innerFnArg = ""; -let innerFnCount = 0; -let actionCount2 = 0; diff --git a/packages/rsc/examples/basic/src/server.ssr.tsx b/packages/rsc/examples/basic/src/server.ssr.tsx deleted file mode 100644 index 3151340b3..000000000 --- a/packages/rsc/examples/basic/src/server.ssr.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "@hiogawa/vite-rsc/extra/ssr"; diff --git a/packages/rsc/examples/basic/src/server.tsx b/packages/rsc/examples/basic/src/server.tsx deleted file mode 100644 index 56a33a93a..000000000 --- a/packages/rsc/examples/basic/src/server.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import "./styles.css"; -import { renderRequest } from "@hiogawa/vite-rsc/extra/rsc"; - -export default async function handler(request: Request): Promise { - const url = new URL(request.url); - const { Root } = await import("./routes/root.tsx"); - const root = ( - <> - {import.meta.viteRsc.loadCss()} - - - ); - const nonce = !process.env.NO_CSP ? crypto.randomUUID() : undefined; - const response = await renderRequest(request, root, { nonce }); - if (nonce) { - response.headers.set( - "content-security-policy", - `default-src 'self'; ` + - // `unsafe-eval` is required during dev since React uses eval for findSourceMapURL feature - `script-src 'self' 'nonce-${nonce}' ${import.meta.env.DEV ? `'unsafe-eval'` : ``} ; ` + - `style-src 'self' 'nonce-${nonce}'; `, - ); - } - return response; -} - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc/examples/basic/src/styles.css b/packages/rsc/examples/basic/src/styles.css deleted file mode 100644 index 3cea43845..000000000 --- a/packages/rsc/examples/basic/src/styles.css +++ /dev/null @@ -1,13 +0,0 @@ -@import "tailwindcss" source("./"); - -button { - @apply bg-gray-100 mx-1 px-2 border hover:bg-gray-200 active:bg-gray-300; -} - -input { - @apply mx-1 px-2 border; -} - -a { - @apply text-gray-500 underline hover:text-gray-700; -} diff --git a/packages/rsc/examples/basic/src/use-cache-runtime.tsx b/packages/rsc/examples/basic/src/use-cache-runtime.tsx deleted file mode 100644 index 21c5b57d1..000000000 --- a/packages/rsc/examples/basic/src/use-cache-runtime.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import * as ReactRsc from "@hiogawa/vite-rsc/rsc"; - -// based on -// https://github.com/vercel/next.js/pull/70435 -// https://github.com/vercel/next.js/blob/09a2167b0a970757606b7f91ff2d470f77f13f8c/packages/next/src/server/use-cache/use-cache-wrapper.ts - -const cachedFnMap = new WeakMap(); -const cachedFnCacheEntries = new WeakMap< - Function, - Record> ->(); - -export default function cacheWrapper(fn: (...args: any[]) => Promise) { - if (cachedFnMap.has(fn)) { - return cachedFnMap.get(fn)!; - } - - async function cachedFn(...args: any[]): Promise { - let cacheEntries = cachedFnCacheEntries.get(cachedFn); - if (!cacheEntries) { - cacheEntries = {}; - cachedFnCacheEntries.set(cachedFn, cacheEntries); - } - - // Serialize arguments to a cache key via `encodeReply` from `react-server-dom/client`. - // NOTE: using `renderToReadableStream` here for arguments serialization would end up - // serializing react elements (e.g. children props), which causes - // those arguments to be included as a cache key and it doesn't achieve - // "use cache static shell + dynamic children props" pattern. - // cf. https://nextjs.org/docs/app/api-reference/directives/use-cache#non-serializable-arguments - const clientTemporaryReferences = - ReactRsc.createClientTemporaryReferenceSet(); - const encodedArguments = await ReactRsc.encodeReply(args, { - temporaryReferences: clientTemporaryReferences, - }); - const serializedCacheKey = await replyToCacheKey(encodedArguments); - - // cache `fn` result as stream - // (cache value is promise so that it dedupes concurrent async calls) - const entryPromise = (cacheEntries[serializedCacheKey] ??= (async () => { - const temporaryReferences = ReactRsc.createTemporaryReferenceSet(); - const decodedArgs = await ReactRsc.decodeReply(encodedArguments, { - temporaryReferences, - }); - - // run the original function - const result = await fn(...decodedArgs); - - // serialize result to a ReadableStream - const stream = ReactRsc.renderToReadableStream(result, { - environmentName: "Cache", - temporaryReferences, - }); - return new StreamCacher(stream); - })()); - - // deserialized cached stream - const stream = (await entryPromise).get(); - const result = ReactRsc.createFromReadableStream(stream, { - environmentName: "Cache", - replayConsoleLogs: true, - temporaryReferences: clientTemporaryReferences, - }); - return result; - } - - cachedFnMap.set(fn, cachedFn); - - return cachedFn; -} - -export function revalidateCache(cachedFn: Function) { - cachedFnCacheEntries.delete(cachedFn); -} - -class StreamCacher { - constructor(private stream: ReadableStream) {} - get(): ReadableStream { - const [returnStream, savedStream] = this.stream.tee(); - this.stream = savedStream; - return returnStream; - } -} - -async function replyToCacheKey(reply: string | FormData) { - if (typeof reply === "string") { - return reply; - } - const buffer = await crypto.subtle.digest( - "SHA-256", - await new Response(reply).arrayBuffer(), - ); - return btoa(String.fromCharCode(...new Uint8Array(buffer))); -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server/client.js b/packages/rsc/examples/basic/test-dep/client-in-server/client.js deleted file mode 100644 index 959c1cbfd..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server/client.js +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestClient() { - const [ok] = React.useState(() => true); - return React.createElement("span", null, String(ok)); -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server/package.json b/packages/rsc/examples/basic/test-dep/client-in-server/package.json deleted file mode 100644 index 68ab77952..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@vitejs/test-dep-client-in-server", - "private": true, - "type": "module", - "exports": { - "./server": "./server.js" - }, - "peerDependencies": { - "react": "*" - } -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server/server.js b/packages/rsc/examples/basic/test-dep/client-in-server/server.js deleted file mode 100644 index fda9c7ed1..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server/server.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; -import { TestClient } from "./client.js"; - -export async function TestClientInServerDep() { - return React.createElement(TestClient); -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server2/client.js b/packages/rsc/examples/basic/test-dep/client-in-server2/client.js deleted file mode 100644 index 3854302d2..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server2/client.js +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import React from "react"; - -const testContext = React.createContext(); - -export function TestContextProvider(props) { - return React.createElement( - testContext.Provider, - { value: props.value }, - props.children, - ); -} - -export function TestContextValue() { - const value = React.useContext(testContext); - return React.createElement("span", null, String(value)); -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server2/package.json b/packages/rsc/examples/basic/test-dep/client-in-server2/package.json deleted file mode 100644 index fbc55fef5..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server2/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@vitejs/test-dep-client-in-server2", - "private": true, - "type": "module", - "exports": { - "./server": "./server.js", - "./client": "./client.js" - }, - "peerDependencies": { - "react": "*" - } -} diff --git a/packages/rsc/examples/basic/test-dep/client-in-server2/server.js b/packages/rsc/examples/basic/test-dep/client-in-server2/server.js deleted file mode 100644 index 5a0f8727e..000000000 --- a/packages/rsc/examples/basic/test-dep/client-in-server2/server.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { TestContextProvider } from "./client.js"; - -export function TestContextProviderInServer(props) { - return React.createElement( - TestContextProvider, - { value: props.value }, - props.children, - ); -} diff --git a/packages/rsc/examples/basic/test-dep/server-in-client/client.js b/packages/rsc/examples/basic/test-dep/server-in-client/client.js deleted file mode 100644 index bf5b81e9e..000000000 --- a/packages/rsc/examples/basic/test-dep/server-in-client/client.js +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import React from "react"; -import { changeCounter } from "./server.js"; - -const h = React.createElement; - -export function TestClient() { - const [count, setCount] = React.useState(() => "?"); - - return h( - "button", - { - "data-testid": "server-in-client", - onClick: async () => { - setCount(await changeCounter(1)); - }, - }, - `server-in-client: ${count}`, - ); -} diff --git a/packages/rsc/examples/basic/test-dep/server-in-client/package.json b/packages/rsc/examples/basic/test-dep/server-in-client/package.json deleted file mode 100644 index d172984a1..000000000 --- a/packages/rsc/examples/basic/test-dep/server-in-client/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@vitejs/test-dep-server-in-client", - "private": true, - "type": "module", - "exports": { - "./client": "./client.js" - }, - "peerDependencies": { - "react": "*" - } -} diff --git a/packages/rsc/examples/basic/test-dep/server-in-client/server.js b/packages/rsc/examples/basic/test-dep/server-in-client/server.js deleted file mode 100644 index 2cfcb89b9..000000000 --- a/packages/rsc/examples/basic/test-dep/server-in-client/server.js +++ /dev/null @@ -1,12 +0,0 @@ -"use server"; - -let counter = 0; - -export async function getCounter() { - return counter; -} - -export async function changeCounter(change) { - counter += change; - return counter; -} diff --git a/packages/rsc/examples/basic/test-dep/server-in-server/package.json b/packages/rsc/examples/basic/test-dep/server-in-server/package.json deleted file mode 100644 index 7a84ef530..000000000 --- a/packages/rsc/examples/basic/test-dep/server-in-server/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@vitejs/test-dep-server-in-server", - "private": true, - "type": "module", - "exports": { - "./server": "./server.js" - }, - "peerDependencies": { - "react": "*" - } -} diff --git a/packages/rsc/examples/basic/test-dep/server-in-server/server.js b/packages/rsc/examples/basic/test-dep/server-in-server/server.js deleted file mode 100644 index ff17882cb..000000000 --- a/packages/rsc/examples/basic/test-dep/server-in-server/server.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; - -const h = React.createElement; - -let counter = 0; - -export function ServerCounter() { - return h( - "form", - { - "data-testid": "server-in-server", - action: async () => { - "use server"; - counter++; - }, - }, - h("button", null, `server-in-server: ${counter}`), - ); -} diff --git a/packages/rsc/examples/basic/tsconfig.json b/packages/rsc/examples/basic/tsconfig.json deleted file mode 100644 index b795eea59..000000000 --- a/packages/rsc/examples/basic/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["vite/client", "@hiogawa/vite-rsc/types"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/basic/vite.config.ts b/packages/rsc/examples/basic/vite.config.ts deleted file mode 100644 index cde18b03a..000000000 --- a/packages/rsc/examples/basic/vite.config.ts +++ /dev/null @@ -1,178 +0,0 @@ -import assert from "node:assert"; -import rsc, { transformHoistInlineDirective } from "@hiogawa/vite-rsc"; -import tailwindcss from "@tailwindcss/vite"; -import react from "@vitejs/plugin-react"; -import { type Plugin, defineConfig, parseAstAsync } from "vite"; -import inspect from "vite-plugin-inspect"; - -// log unhandled rejection to debug e2e failures -if (!(globalThis as any).__debugHandlerRegisterd) { - process.on("uncaughtException", (err) => { - console.error("⚠️⚠️⚠️ uncaughtException ⚠️⚠️⚠️", err); - }); - process.on("unhandledRejection", (err) => { - console.error("⚠️⚠️⚠️ unhandledRejection ⚠️⚠️⚠️", err); - }); - (globalThis as any).__debugHandlerRegisterd = true; -} - -export default defineConfig({ - base: process.env.TEST_BASE ? "/custom-base/" : undefined, - clearScreen: false, - plugins: [ - tailwindcss(), - react(), - vitePluginUseCache(), - rsc({ - entries: { - client: "./src/client.tsx", - ssr: "./src/server.ssr.tsx", - rsc: "./src/server.tsx", - }, - // disable auto css injection to manually test `loadCss` feature. - rscCssTransform: false, - ignoredPackageWarnings: [/@vitejs\/test-dep-/], - copyServerAssetsToClient: (fileName) => - fileName !== "__server_secret.txt", - }), - // avoid ecosystem CI fail due to vite-plugin-inspect compatibility - !process.env.ECOSYSTEM_CI && inspect(), - { - // test server restart scenario on e2e - name: "test-api", - configureServer(server) { - server.middlewares.use((req, res, next) => { - const url = new URL(req.url!, "http://localhost"); - if (url.pathname === "/__test_restart") { - setTimeout(() => { - server.restart(); - }, 10); - res.end("ok"); - return; - } - next(); - }); - }, - }, - { - name: "test-client-reference-tree-shaking", - enforce: "post", - writeBundle(_options, bundle) { - for (const chunk of Object.values(bundle)) { - if (chunk.type === "chunk") { - assert(!chunk.code.includes("__unused_client_reference__")); - } - } - }, - }, - { - name: "test-server-assets-security", - buildStart() { - if (this.environment.name === "rsc") { - this.emitFile({ - type: "asset", - fileName: "__server_secret.txt", - source: "__server_secret", - }); - } - }, - writeBundle(_options, bundle) { - if (this.environment.name === "rsc") { - assert(Object.keys(bundle).includes("__server_secret.txt")); - } else { - assert(!Object.keys(bundle).includes("__server_secret.txt")); - } - - const viteManifest = bundle[".vite/manifest.json"]; - assert(viteManifest.type === "asset"); - assert(typeof viteManifest.source === "string"); - if (this.environment.name === "rsc") { - assert(viteManifest.source.includes("src/server.tsx")); - assert(!viteManifest.source.includes("src/client.tsx")); - } - if (this.environment.name === "client") { - assert(!viteManifest.source.includes("src/server.tsx")); - assert(viteManifest.source.includes("src/client.tsx")); - } - }, - }, - { - name: "cf-build", - enforce: "post", - apply: () => !!process.env.CF_BUILD, - configEnvironment() { - return { - keepProcessEnv: false, - define: { - "process.env.NO_CSP": "false", - }, - resolve: { - noExternal: true, - }, - }; - }, - generateBundle() { - if (this.environment.name === "rsc") { - this.emitFile({ - type: "asset", - fileName: "cloudflare.js", - source: `\ -import handler from './index.js'; -export default { fetch: handler }; -`, - }); - } - if (this.environment.name === "client") { - // https://developers.cloudflare.com/workers/static-assets/headers/#custom-headers - this.emitFile({ - type: "asset", - fileName: "_headers", - source: `\ -/favicon.ico - Cache-Control: public, max-age=3600, s-maxage=3600 -/assets/* - Cache-Control: public, max-age=31536000, immutable -`, - }); - } - }, - }, - ], - build: { - minify: false, - manifest: true, - }, - optimizeDeps: { - exclude: [ - "@vitejs/test-dep-client-in-server/client", - "@vitejs/test-dep-client-in-server2/client", - "@vitejs/test-dep-server-in-client/client", - ], - }, -}) as any; - -function vitePluginUseCache(): Plugin[] { - return [ - { - name: "use-cache", - async transform(code) { - if (!code.includes("use cache")) return; - const ast = await parseAstAsync(code); - const result = transformHoistInlineDirective(code, ast, { - runtime: (value) => `__vite_rsc_cache(${value})`, - directive: "use cache", - rejectNonAsyncFunction: true, - noExport: true, - }); - if (!result.output.hasChanged()) return; - result.output.prepend( - `import __vite_rsc_cache from "/src/use-cache-runtime";`, - ); - return { - code: result.output.toString(), - map: result.output.generateMap({ hires: "boundary" }), - }; - }, - }, - ]; -} diff --git a/packages/rsc/examples/basic/wrangler.jsonc b/packages/rsc/examples/basic/wrangler.jsonc deleted file mode 100644 index d5cb469b8..000000000 --- a/packages/rsc/examples/basic/wrangler.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://www.unpkg.com/wrangler@4.13.0/config-schema.json", - "name": "vite-rsc-basic", - "main": "dist/rsc/cloudflare.js", - "assets": { - "directory": "dist/client" - }, - "workers_dev": true, - "compatibility_date": "2025-04-01", - "compatibility_flags": ["nodejs_als"] -} diff --git a/packages/rsc/examples/hono/package.json b/packages/rsc/examples/hono/package.json deleted file mode 100644 index 0eecb980d..000000000 --- a/packages/rsc/examples/hono/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-hono", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build --app", - "preview": "vite preview" - }, - "dependencies": { - "@hiogawa/vite-rsc": "workspace:*", - "hono": "^4.7.5" - } -} diff --git a/packages/rsc/examples/hono/public/favicon.ico b/packages/rsc/examples/hono/public/favicon.ico deleted file mode 100644 index 4aff076603f8f01fb3b7d8b5e38df77cc1efdd69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmc(jeOQ&{9mmi9Y5(N4wKaEHmR6cpj%~hXo13+jb7bn2986aeCEr<~nOgZmpr$E` zf`o4brE!#(M1gZu646vl&`1pxMdw?|n=m>c+@F0P;mO>tlyk0ib#r~sdCqh0=e|Gp z@BY2q2Z!Sa`s>)yVZAyw{@CGY>u@-_O1kCfFIo4mH+}bzU$dHTI2`!e8Vx4;1ZKh^ zxKi)yD84^;xC-;kZcMM8u;Hxl=0 zrl(}R$D9P?+i+ezRaK9fv=7rK8`IkjJ!J!H{(<@MS+!R~`O?{>ox=Qa3-rsRXz(63 z2OX9VwCuy2qfp=E%do9`=6nq%BuDe7!jtKI6u6hZ(Up|S59#T~^xp-0ue5F~Z+jVS zZek&3&_;~E69#PN+~91w#MclR`Z@H~e)M!gs0@p&odJ103qFqWoKTLTHzP(@mAxzj(#e6hoycovD(ij}ivB{e#lRuP=cG5k$iaRo)dtx=G zM`-QOi1(><%*tYDlHdB;$}2ES5-{NlF)zQ3nIet}%c&n1$C&sM{8FwGkZ_g`sUGg! z3T>ptZyW}H`KjW=1Y|*Rxo@*u@#a=udF@Tij3~@2VhCG&oq>tVm=kl5JGI?yq#tEV znyQW7d%s`BnUNog;R7)^G4l^7CpCJ{a9%Yn`Yp_y*_byXFtZ{t(-+7Viy(9{&V^aD zbs2uD6#Nr0*t#nRmP8cFH{WZ{6~oeCpO^7?twAxY_*(EPM7@rgXEDr!SrITj3c}}8 zG&hNdv%li*9q=>hZiT(2yL8>xDH{YUC=?&&*$uMcN6JZFoDoXp$8j^YkZG`J2F#yD zT~q{{;?mf*+{vkkRh*ogz;80kX{(g`^V}P1yAv!$FZ=}TTrvCu$T`)zJ@6=Iw#2~7 zDX=^omRbzcx$<@-Pv=~u!|o<}xvTke<^?+EHqb5)?vdJw!(HPN#foO70Yl-k>>`W6NT?SD*WDt)y&<{AiS7Soz8*XK=RiTNo5?#oDKo7j&K*4as9+{Y$WBf^0FF zhPW_7T*Y)e2oGsl4=Su|Js>7q|IHS&LZG8pDR+w@NcCf#pc|Y1m!a|mxHMN<5IFx1 z8~>pkE;}TKz}oRNCQKpRUBH8fptD@dQYW$4vY4(*To35FgLJ?22Ui7UQ&lU1W0kw( zmA{hV*u|UYZ&JK{x(u?0L*`#0{co^Y3@IiNbQp!w zO~^{sk=0O8+H8YsRd7@>zI!0-dJcAo!6iFvdtPxEsQr*FoZ{FpidEZFc~oV-Tk*|$ zOiZ@A>Uvn-1u7yJ!OXeJSCKGPdDF_>p{gGfEe6%GMd?tjc)TqC{6ur_9{_tS27fUO z62lPLLUzcOFS-?jc~K-4?ZfXl{PI|{{KR7G7oUNpZc?8j4+TjnD#%v;R^FZ=O;ZgC zQw<4K9kXibq*$#{4s=ZGD|!+NHE*G=kG!mVVn4kRE-IKSOQ7zG>Zpg*Dnk!_{PpKI z^ege$`kG68tF7N7moCV*M=`td75h4u#3o_hR4h!F4JOMLvlF2rN59`Pl%M4|o^y(g zS}=__`)A81R}DOVN=Mz3(8JOR)qGpx>fXX;*=W+gG@L^E>t@wck4JM=z=<;1T8?r+ z2K1tp)T{Jd($Pnl{u<(`)5^1QqbH<3;_5B+5|_m^P~VlR|NpaD%c*J7PrU}Yugsfq z6=KWbwaXB4Ua4MOgTyu9jE-TFWv}nL35VJirUPP1tyZ~^yM!slY|{1Zn*D!(@9X_P D`VsgZ diff --git a/packages/rsc/examples/hono/src/client.tsx b/packages/rsc/examples/hono/src/client.tsx deleted file mode 100644 index 78dd2c1eb..000000000 --- a/packages/rsc/examples/hono/src/client.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { fetchRSC } from "@hiogawa/vite-rsc/extra/browser"; -import React from "react"; -import ReactDOM from "react-dom/client"; - -function main() { - const dom = document.getElementById("root")!; - ReactDOM.createRoot(dom).render(); -} - -function App() { - return ( -
-

hello client

- -
- ); -} - -function FetchRsc() { - const [rsc, setRsc] = React.useState(null); - - return ( -
- - {rsc} -
- ); -} - -main(); diff --git a/packages/rsc/examples/hono/src/server.tsx b/packages/rsc/examples/hono/src/server.tsx deleted file mode 100644 index 8f91fac66..000000000 --- a/packages/rsc/examples/hono/src/server.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { renderRequest } from "@hiogawa/vite-rsc/extra/rsc"; -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/api/rsc", (c) => { - const el = ( -
-
Hono!
-
random: ${Math.random().toString(36).slice(2)}
-
- ); - // TODO: request is irrelevant - return renderRequest(c.req.raw, el); -}); - -app.all("/", (c) => { - return renderRequest(c.req.raw, ); -}); - -function Document() { - return ( - - - vite-rsc - - -
- - - ); -} - -export default app.fetch; diff --git a/packages/rsc/examples/hono/tsconfig.json b/packages/rsc/examples/hono/tsconfig.json deleted file mode 100644 index eeb2d95d9..000000000 --- a/packages/rsc/examples/hono/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["vite/client"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/hono/vite.config.ts b/packages/rsc/examples/hono/vite.config.ts deleted file mode 100644 index 3559d023d..000000000 --- a/packages/rsc/examples/hono/vite.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import rsc from "@hiogawa/vite-rsc"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -export default defineConfig({ - clearScreen: false, - plugins: [ - react(), - rsc({ - entries: { - client: "./src/client.tsx", - rsc: "./src/server.tsx", - ssr: "@hiogawa/vite-rsc/extra/ssr", - }, - }), - ], - build: { - minify: false, - }, -}) as any; diff --git a/packages/rsc/examples/react-router/README.md b/packages/rsc/examples/react-router/README.md deleted file mode 100644 index 071e02736..000000000 --- a/packages/rsc/examples/react-router/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# rsc react-router - -https://vite-rsc-react-router.hiro18181.workers.dev - -Vite RSC example based on demo made by React router team with Parcel: -- https://github.com/jacob-ebey/parcel-plugin-react-router/ -- https://github.com/jacob-ebey/experimental-parcel-react-router-starter -- https://github.com/remix-run/react-router/tree/rsc/playground/rsc-vite - -See also [`rsc-movies`](https://github.com/hi-ogawa/rsc-movies/). - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/react-router?file=src%2Froutes%2Froot.tsx) - -Or try it locally by: - -```sh -npx giget gh:hi-ogawa/vite-plugins/packages/rsc/examples/react-router my-app -cd my-app -npm i -npm run dev -npm run build -npm run preview - -# run on @cloudflare/vite-plugin and deploy. -# a separate configuration is found in ./cf/vite.config.ts -npm run cf-dev -npm run cf-build -npm run cf-preview -npm run cf-release -``` diff --git a/packages/rsc/examples/react-router/app/paper.css b/packages/rsc/examples/react-router/app/paper.css deleted file mode 100644 index 4135b347a..000000000 --- a/packages/rsc/examples/react-router/app/paper.css +++ /dev/null @@ -1,150 +0,0 @@ -@theme { - --default-font-family: "Patrick Hand SC", sans-serif; - --default-mono-font-family: "Patrick Hand SC", sans-serif; - - --color-foreground: black; - --color-danger: rgb(167, 52, 45); - --color-secondary: rgb(11, 116, 213); - --color-success: rgb(134, 163, 97); - --color-warning: rgb(221, 205, 69); - --color-border: #cdcccb; - --color-border-active: rgba(0, 0, 0, 0.2); - - --color-paper-background: white; - --color-paper-border: #cdcccb; - --shadow-paper: -1px 5px 35px -9px rgba(0, 0, 0, 0.2); - - --shadow-btn: 15px 28px 25px -18px rgba(0, 0, 0, 0.2); - --shadow-btn-hover: 2px 8px 8px -5px rgba(0, 0, 0, 0.3); - --color-btn-border: black; - --btn-color-danger: var(--color-danger); - --btn-color-secondary: var(--color-secondary); - --btn-color-success: var(--color-success); - --btn-color-warning: var(--color-warning); -} - -@utility paper-border { - @apply border-2 border-border; - border-bottom-left-radius: 25px 115px; - border-bottom-right-radius: 155px 25px; - border-top-left-radius: 15px 225px; - border-top-right-radius: 25px 150px; -} - -@utility no-paper-border { - @apply border-0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -@utility paper-underline { - @apply border-b-3 border-[currentcolor]; - border-bottom-left-radius: 15px 3px; - border-bottom-right-radius: 15px 5px; - border-bottom-style: solid; -} - -@utility paper-underline-hover { - @apply paper-underline border-transparent; - @variant hover { - @apply border-[currentcolor]; - } -} - -@utility paper { - @apply border border-paper-border bg-paper-background p-8 shadow-paper; -} - -@utility breadcrumbs { - @apply flex flex-wrap gap-2; - & > * { - @apply inline-block after:text-lg after:content-[""] not-last:after:ml-2 not-last:after:text-foreground not-last:after:content-["/"]; - } - & > a { - @apply text-secondary; - } -} - -@utility btn { - @apply inline-block cursor-pointer bg-paper-background paper-border px-4 py-2 text-lg shadow-btn transition-[shadow_transition]; - - @variant active { - @apply border-border-active; - } - @variant hover { - @apply translate-y-1 shadow-btn-hover; - } - - &.btn-icon { - @apply aspect-square px-2 py-2; - & img, - & svg { - @apply h-7 w-7; - } - } -} - -@utility btn-* { - border-color: --value(--btn-color- *); - color: --value(--btn-color- *); -} - -@utility btn-sm { - @apply px-2 py-1 text-base; -} - -@utility btn-lg { - @apply px-6 py-3 text-2xl; -} - -@utility label { - @apply mb-1 block font-semibold; -} - -@utility input { - @apply paper-border px-3 py-2; - - @variant disabled { - @apply border-border-active; - } -} - -@utility checkbox { - @apply h-6 w-6 paper-border; - - @variant disabled { - @apply border-border-active; - } -} - -@utility select { - @apply paper-border px-3 py-2; - - @variant disabled { - @apply border-border-active; - } -} - -@layer base { - body { - @apply text-foreground; - } - - * { - @apply outline-secondary; - } -} - -@layer utilities { - .prose { - :where(u):not(:where([class~="not-prose"], [class~="not-prose"] *)) { - @apply paper-underline no-underline; - } - - :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) { - @apply paper-underline-hover no-underline text-secondary; - } - } -} diff --git a/packages/rsc/examples/react-router/app/root.tsx b/packages/rsc/examples/react-router/app/root.tsx deleted file mode 100644 index 097d73147..000000000 --- a/packages/rsc/examples/react-router/app/root.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import "./styles.css"; -import { Link, Outlet } from "react-router"; -import { ServerHmr } from "../react-router-vite/server-hmr"; -import { TestClientState, TestHydrated } from "./routes/client"; -import { DumpError, GlobalNavigationLoadingBar } from "./routes/root.client"; - -export function Layout({ children }: { children: React.ReactNode }) { - console.log("Layout"); - return ( - - - - - React Router Vite - - -
- -
- - - {children} - - - ); -} - -export default function Component() { - console.log("Root"); - return ( - <> - - - ); -} - -export function ErrorBoundary() { - return ; -} diff --git a/packages/rsc/examples/react-router/app/routes.ts b/packages/rsc/examples/react-router/app/routes.ts deleted file mode 100644 index ea305cb74..000000000 --- a/packages/rsc/examples/react-router/app/routes.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type RouteConfig, index, route } from "@react-router/dev/routes"; - -export default [ - index("routes/home.tsx"), - route("about", "routes/about.tsx"), -] satisfies RouteConfig; diff --git a/packages/rsc/examples/react-router/app/routes/about.tsx b/packages/rsc/examples/react-router/app/routes/about.tsx deleted file mode 100644 index 583f190fb..000000000 --- a/packages/rsc/examples/react-router/app/routes/about.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import React from "react"; - -export function Component() { - const [count, setCount] = React.useState(0); - - return ( -
-
-

About

-

This is the about page.

-

[test-style-home]

- -
-
- ); -} diff --git a/packages/rsc/examples/react-router/app/routes/client.tsx b/packages/rsc/examples/react-router/app/routes/client.tsx deleted file mode 100644 index 175be3393..000000000 --- a/packages/rsc/examples/react-router/app/routes/client.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestHydrated() { - const hydrated = React.useSyncExternalStore( - React.useCallback(() => () => {}, []), - () => true, - () => false, - ); - return [hydrated: {hydrated ? 1 : 0}]; -} - -export function TestClientState() { - return ( - - ); -} diff --git a/packages/rsc/examples/react-router/app/routes/home.actions.ts b/packages/rsc/examples/react-router/app/routes/home.actions.ts deleted file mode 100644 index 9860940f3..000000000 --- a/packages/rsc/examples/react-router/app/routes/home.actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -"use server"; - -export async function sayHello(defaultName: string, formData: FormData) { - await new Promise((resolve) => setTimeout(resolve, 500)); - const name = formData.get("name") || defaultName; - console.log(`Hello, ${name}`); -} diff --git a/packages/rsc/examples/react-router/app/routes/home.client.tsx b/packages/rsc/examples/react-router/app/routes/home.client.tsx deleted file mode 100644 index 8f2c4fad7..000000000 --- a/packages/rsc/examples/react-router/app/routes/home.client.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client"; - -import { useFormStatus } from "react-dom"; - -export function PendingButton() { - const status = useFormStatus(); - return ( - - ); -} diff --git a/packages/rsc/examples/react-router/app/routes/home.css b/packages/rsc/examples/react-router/app/routes/home.css deleted file mode 100644 index 7204e2fde..000000000 --- a/packages/rsc/examples/react-router/app/routes/home.css +++ /dev/null @@ -1,3 +0,0 @@ -.test-style-home { - color: rgb(250, 150, 0); -} diff --git a/packages/rsc/examples/react-router/app/routes/home.tsx b/packages/rsc/examples/react-router/app/routes/home.tsx deleted file mode 100644 index cc5ce8b63..000000000 --- a/packages/rsc/examples/react-router/app/routes/home.tsx +++ /dev/null @@ -1,56 +0,0 @@ -namespace Route { - export type LoaderArgs = any; - export type ComponentProps = any; -} - -import { sayHello } from "./home.actions.ts"; -import { PendingButton } from "./home.client.tsx"; -import "./home.css"; -import { TestActionStateServer } from "./test-action-state/server.tsx"; - -export function loader({ request }: Route.LoaderArgs) { - const url = new URL(request.url); - const name = url.searchParams.get("name"); - return { name: name || "Unknown" }; -} - -const Component = ({ loaderData }: Route.ComponentProps) => { - return ( -
-
-

Home

-

This is the home page.

- [test-style-home] -
-          loaderData: {JSON.stringify(loaderData)}
-        
-

Server Action

-
-
- - -
-
- -
-
-
- -
-
-
- ); -}; - -export default Component; diff --git a/packages/rsc/examples/react-router/app/routes/root.client.tsx b/packages/rsc/examples/react-router/app/routes/root.client.tsx deleted file mode 100644 index 8729caa0a..000000000 --- a/packages/rsc/examples/react-router/app/routes/root.client.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { useNavigation, useRouteError } from "react-router"; - -export function GlobalNavigationLoadingBar() { - const navigation = useNavigation(); - - if (navigation.state === "idle") return null; - - return ( -
-
-
- ); -} - -export function DumpError() { - const error = useRouteError(); - const message = - error instanceof Error ? ( -
-
-          {JSON.stringify(
-            {
-              ...error,
-              name: error.name,
-              message: error.message,
-            },
-            null,
-            2,
-          )}
-        
- {error.stack &&
{error.stack}
} -
- ) : ( -
Unknown Error
- ); - return ( - <> -

Oooops

-
{message}
- - ); -} diff --git a/packages/rsc/examples/react-router/app/routes/test-action-state/client.tsx b/packages/rsc/examples/react-router/app/routes/test-action-state/client.tsx deleted file mode 100644 index 5525fdad6..000000000 --- a/packages/rsc/examples/react-router/app/routes/test-action-state/client.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -import React from "react"; - -export function TestActionStateClient(props: { - action: (prev: React.ReactNode) => Promise; -}) { - const [state, formAction, isPending] = React.useActionState( - props.action, - null, - ); - - return ( -
- - {isPending ? "pending..." : state} -
- ); -} diff --git a/packages/rsc/examples/react-router/app/routes/test-action-state/server.tsx b/packages/rsc/examples/react-router/app/routes/test-action-state/server.tsx deleted file mode 100644 index 7044c2e99..000000000 --- a/packages/rsc/examples/react-router/app/routes/test-action-state/server.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { TestActionStateClient } from "./client"; - -// Test case based on -// https://github.com/remix-run/react-router/issues/13882 - -export function TestActionStateServer({ message }: { message: string }) { - return ( - { - "use server"; - await new Promise((resolve) => setTimeout(resolve, 200)); - return ( - - [(ok) ({message})] {prev} - - ); - }} - /> - ); -} diff --git a/packages/rsc/examples/react-router/app/styles.css b/packages/rsc/examples/react-router/app/styles.css deleted file mode 100644 index c66d16481..000000000 --- a/packages/rsc/examples/react-router/app/styles.css +++ /dev/null @@ -1,32 +0,0 @@ -@import "tailwindcss"; -@plugin "@tailwindcss/typography"; - -@import "./paper.css"; - -@theme { - --animate-progress: progress 1s infinite linear; - - @keyframes progress { - 0% { - transform: translateX(0) scaleX(0); - } - 40% { - transform: translateX(0) scaleX(0.4); - } - 100% { - transform: translateX(100%) scaleX(0.5); - } - } -} - -@utility vt-name { - view-transition-name: var(--vt-name); -} - -@utility no-vt { - view-transition-name: none; -} - -@view-transition { - navigation: auto; -} diff --git a/packages/rsc/examples/react-router/cf/entry.rsc.tsx b/packages/rsc/examples/react-router/cf/entry.rsc.tsx deleted file mode 100644 index c7965324f..000000000 --- a/packages/rsc/examples/react-router/cf/entry.rsc.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { fetchServer } from "../react-router-vite/entry.rsc"; - -console.log("[debug:cf-rsc-entry]"); - -export default { - fetch(request: Request) { - return fetchServer(request); - }, -}; - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc/examples/react-router/cf/entry.ssr.tsx b/packages/rsc/examples/react-router/cf/entry.ssr.tsx deleted file mode 100644 index 33a1fb753..000000000 --- a/packages/rsc/examples/react-router/cf/entry.ssr.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import handler from "../react-router-vite/entry.ssr"; - -console.log("[debug:cf-ssr-entry]"); - -// TODO: -// shouldn't "entry.rsc.tsx" be the main server entry -// and optionally call "entry.ssr.tsx" only for rendering html? - -export default { - fetch(request: Request, env: any) { - return handler(request, (request) => env.RSC.fetch(request)); - }, -}; diff --git a/packages/rsc/examples/react-router/cf/vite.config.ts b/packages/rsc/examples/react-router/cf/vite.config.ts deleted file mode 100644 index e5f068be8..000000000 --- a/packages/rsc/examples/react-router/cf/vite.config.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { cloudflare } from "@cloudflare/vite-plugin"; -import rsc from "@hiogawa/vite-rsc"; -import tailwindcss from "@tailwindcss/vite"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; -import inspect from "vite-plugin-inspect"; -import { reactRouter } from "../react-router-vite/plugin"; - -export default defineConfig({ - clearScreen: false, - build: { - minify: false, - }, - plugins: [ - tailwindcss(), - react(), - reactRouter(), - rsc({ - entries: { - client: "./react-router-vite/entry.browser.tsx", - }, - serverHandler: false, - }), - inspect(), - cloudflare({ - configPath: "./cf/wrangler.ssr.jsonc", - viteEnvironment: { - name: "ssr", - }, - auxiliaryWorkers: [ - { - configPath: "./cf/wrangler.rsc.jsonc", - viteEnvironment: { - name: "rsc", - }, - }, - ], - }), - { - name: "react-router-fixup", - transform(code) { - if (code.includes(`import { AsyncLocalStorage } from 'async_hooks';`)) { - code = code.replaceAll("async_hooks", "node:async_hooks"); - code = code.replaceAll( - `global.___reactRouterServerStorage___`, - `globalThis.___reactRouterServerStorage___`, - ); - return code; - } - }, - }, - ], - environments: { - client: { - optimizeDeps: { - include: ["react-router", "react-router/internal/react-server-client"], - }, - }, - ssr: { - optimizeDeps: { - include: ["react-router > cookie", "react-router > set-cookie-parser"], - exclude: ["react-router"], - }, - }, - rsc: { - optimizeDeps: { - include: ["react-router > cookie", "react-router > set-cookie-parser"], - exclude: ["react-router"], - }, - }, - }, -}); diff --git a/packages/rsc/examples/react-router/cf/wrangler.rsc.jsonc b/packages/rsc/examples/react-router/cf/wrangler.rsc.jsonc deleted file mode 100644 index 68270a8be..000000000 --- a/packages/rsc/examples/react-router/cf/wrangler.rsc.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://www.unpkg.com/wrangler@4.19.1/config-schema.json", - "name": "vite-rsc-react-router-rsc", - "main": "./entry.rsc.tsx", - "workers_dev": true, - "compatibility_date": "2025-04-01", - "compatibility_flags": ["nodejs_als"] -} diff --git a/packages/rsc/examples/react-router/cf/wrangler.ssr.jsonc b/packages/rsc/examples/react-router/cf/wrangler.ssr.jsonc deleted file mode 100644 index bfc7066b0..000000000 --- a/packages/rsc/examples/react-router/cf/wrangler.ssr.jsonc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://www.unpkg.com/wrangler@4.19.1/config-schema.json", - "name": "vite-rsc-react-router", - "main": "./entry.ssr.tsx", - "workers_dev": true, - "services": [{ "binding": "RSC", "service": "vite-rsc-react-router-rsc" }], - "compatibility_date": "2025-04-01", - "compatibility_flags": ["nodejs_als"] -} diff --git a/packages/rsc/examples/react-router/package.json b/packages/rsc/examples/react-router/package.json deleted file mode 100644 index 8036905a1..000000000 --- a/packages/rsc/examples/react-router/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-react-router", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build --app", - "preview": "vite preview", - "cf-dev": "vite -c ./cf/vite.config.ts", - "cf-build": "vite -c ./cf/vite.config.ts build", - "cf-preview": "vite -c ./cf/vite.config.ts preview", - "cf-release": "wrangler deploy -c dist/rsc/wrangler.json && wrangler deploy" - }, - "dependencies": { - "@hiogawa/vite-rsc": "latest", - "react": "latest", - "react-dom": "latest", - "react-router": "0.0.0-experimental-23decd7bc" - }, - "devDependencies": { - "@cloudflare/vite-plugin": "^1.13.9", - "@react-router/dev": "0.0.0-experimental-23decd7bc", - "@tailwindcss/typography": "^0.5.16", - "@tailwindcss/vite": "^4.1.4", - "@types/react": "latest", - "@types/react-dom": "latest", - "@vitejs/plugin-react": "latest", - "tailwindcss": "^4.1.4", - "vite": "latest", - "vite-plugin-inspect": "^11.2.0", - "wrangler": "^4.41.0" - } -} diff --git a/packages/rsc/examples/react-router/public/favicon.ico b/packages/rsc/examples/react-router/public/favicon.ico deleted file mode 100644 index 5dbdfcddcb14182535f6d32d1c900681321b1aa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI33v3ic7{|AFEmuJ-;v>ep_G*NPi6KM`qNryCe1PIJ8siIN1WZ(7qVa)RVtmC% z)Ch?tN+afMKm;5@rvorJk zcXnoOc4q51HBQnQH_jn!cAg&XI1?PlX>Kl^k8qq0;zkha`kY$Fxt#=KNJAE9CMdpW zqr4#g8`nTw191(+H4xW8Tmyru2I^3=J1G3emPxkPXA=3{vvuvse_WWSshqaqls^-m zgB7q8&Vk*aYRe?sn$n53dGH#%3y%^vxv{pL*-h0Z4bmb_(k6{FL7HWIz(V*HT#IcS z-wE{)+0x1U!RUPt3gB97%p}@oHxF4|6S*+Yw=_tLtxZ~`S=z6J?O^AfU>7qOX`JNBbV&8+bO0%@fhQitKIJ^O^ zpgIa__qD_y07t@DFlBJ)8SP_#^j{6jpaXt{U%=dx!qu=4u7^21lWEYHPPY5U3TcoQ zX_7W+lvZi>TapNk_X>k-KO%MC9iZp>1E`N34gHKd9tK&){jq2~7OsJ>!G0FzxQFw6G zm&Vb(2#-T|rM|n3>uAsG_hnbvUKFf3#ay@u4uTzia~NY%XgCHfx4^To4BDU@)HlV? z@EN=g^ymETa1sQK{kRwyE4Ax8?wT&GvaG@ASO}{&a17&^v`y z!oPdiSiia^oov(Z)QhG2&|FgE{M9_4hJROGbnj>#$~ZF$-G^|zPj*QApltKe?;u;uKHJ~-V!=VLkg7Kgct)l7u39f@%VG8e3f$N-B zAu3a4%ZGf)r+jPAYCSLt73m_J3}p>}6Tx0j(wg4vvKhP!DzgiWANiE;Ppvp}P2W@m z-VbYn+NXFF?6ngef5CfY6ZwKnWvNV4z6s^~yMXw2i5mv}jC$6$46g?G|CPAu{W5qF zDobS=zb2ILX9D827g*NtGe5w;>frjanY{f)hrBP_2ehBt1?`~ypvg_Ot4x1V+43P@Ve8>qd)9NX_jWdLo`Zfy zoeam9)@Dpym{4m@+LNxXBPjPKA7{3a&H+~xQvr>C_A;7=JrfK~$M2pCh>|xLz>W6SCs4qC|#V`)# z)0C|?$o>jzh<|-cpf

K7osU{Xp5PG4-K+L2G=)c3f&}H&M3wo7TlO_UJjQ-Oq&_ zjAc9=nNIYz{c3zxOiS5UfcE1}8#iI4@uy;$Q7>}u`j+OU0N<*Ezx$k{x_27+{s2Eg z`^=rhtIzCm!_UcJ?Db~Lh-=_))PT3{Q0{Mwdq;0>ZL%l3+;B&4!&xm#%HYAK|;b456Iv&&f$VQHf` z>$*K9w8T+paVwc7fLfMlhQ4)*zL_SG{~v4QR;IuX-(oRtYAhWOlh`NLoX0k$RUYMi z2Y!bqpdN}wz8q`-%>&Le@q|jFw92ErW-hma-le?S z-@OZt2EEUm4wLsuEMkt4zlyy29_3S50JAcQHTtgTC{P~%-mvCTzrjXOc|{}N`Cz`W zSj7CrXfa7lcsU0J(0uSX6G`54t^7}+OLM0n(|g4waOQ}bd3%!XLh?NX9|8G_|06Ie zD5F1)w5I~!et7lA{G^;uf7aqT`KE&2qx9|~O;s6t!gb`+zVLJyT2T)l*8l(j diff --git a/packages/rsc/examples/react-router/react-router-vite/entry.browser.tsx b/packages/rsc/examples/react-router/react-router-vite/entry.browser.tsx deleted file mode 100644 index 8e730eaa7..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/entry.browser.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - createFromReadableStream, - createTemporaryReferenceSet, - encodeReply, - setServerCallback, -} from "@hiogawa/vite-rsc/browser"; -import * as React from "react"; -import { hydrateRoot } from "react-dom/client"; -import { - unstable_RSCHydratedRouter as RSCHydratedRouter, - type unstable_RSCPayload as RSCPayload, - unstable_createCallServer as createCallServer, - unstable_getRSCStream as getRSCStream, -} from "react-router"; - -setServerCallback( - createCallServer({ - createFromReadableStream, - encodeReply, - createTemporaryReferenceSet, - }), -); - -createFromReadableStream(getRSCStream()).then( - (payload: RSCPayload) => { - React.startTransition(() => { - hydrateRoot( - document, - - - , - ); - }); - }, -); diff --git a/packages/rsc/examples/react-router/react-router-vite/entry.rsc.tsx b/packages/rsc/examples/react-router/react-router-vite/entry.rsc.tsx deleted file mode 100644 index 444a3fcc2..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/entry.rsc.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - createTemporaryReferenceSet, - decodeAction, - decodeReply, - loadServerAction, - renderToReadableStream, -} from "@hiogawa/vite-rsc/rsc"; -import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router"; - -import routes from "virtual:react-router-routes"; - -export async function fetchServer(request: Request): Promise { - return await matchRSCServerRequest({ - createTemporaryReferenceSet, - decodeReply, - decodeAction, - loadServerAction, - request, - routes, - generateResponse(match, options) { - return new Response(renderToReadableStream(match.payload, options), { - status: match.statusCode, - headers: match.headers, - }); - }, - }); -} - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx b/packages/rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx deleted file mode 100644 index 1a2acb7c2..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/entry.ssr.single.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import baseHandler from "./entry.ssr"; - -export default async function handler(request: Request) { - const rsc = await import.meta.viteRsc.loadModule< - typeof import("./entry.rsc") - >("rsc", "index"); - return baseHandler(request, rsc.fetchServer); -} diff --git a/packages/rsc/examples/react-router/react-router-vite/entry.ssr.tsx b/packages/rsc/examples/react-router/react-router-vite/entry.ssr.tsx deleted file mode 100644 index bea77e484..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/entry.ssr.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { createFromReadableStream } from "@hiogawa/vite-rsc/ssr"; -import * as ReactDomServer from "react-dom/server.edge"; -import { - unstable_RSCStaticRouter as RSCStaticRouter, - unstable_routeRSCServerRequest as routeRSCServerRequest, -} from "react-router"; - -export default async function handler( - request: Request, - fetchServer: (request: Request) => Promise, -): Promise { - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - return routeRSCServerRequest({ - request, - fetchServer, - createFromReadableStream: (body) => createFromReadableStream(body), - renderHTML(getPayload) { - return ReactDomServer.renderToReadableStream( - , - { - bootstrapScriptContent, - }, - ); - }, - }); -} diff --git a/packages/rsc/examples/react-router/react-router-vite/plugin.ts b/packages/rsc/examples/react-router/react-router-vite/plugin.ts deleted file mode 100644 index a34665f40..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/plugin.ts +++ /dev/null @@ -1,119 +0,0 @@ -import assert from "node:assert/strict"; -import path from "node:path"; -import type { Config } from "@react-router/dev/config"; -import type { RouteConfigEntry } from "@react-router/dev/routes"; -import { type Plugin, createIdResolver, runnerImport } from "vite"; - -export function reactRouter(): Plugin[] { - let idResolver: ReturnType; - - return [ - { - name: "react-router:config", - configResolved(config) { - idResolver = createIdResolver(config); - }, - resolveId(source) { - if (source === "virtual:react-router-routes") { - return "\0" + source; - } - }, - async load(id) { - if (id === "\0virtual:react-router-routes") { - const findFile = (id: string) => idResolver(this.environment, id); - const config = await readReactRouterConfig(findFile); - this.addWatchFile(config.configFile); - this.addWatchFile(config.routesFile); - const code = generateRoutesCode(config); - return code; - } - }, - }, - ]; -} - -async function readReactRouterConfig( - findFile: (id: string) => Promise, -) { - // find react-router.config.ts - const configFile = await findFile("./react-router.config"); - assert(configFile, "Cannot find 'react-router.config' file"); - const configImport = await runnerImport<{ default: Config }>(configFile); - const appDirectory = path.resolve( - configImport.module.default.appDirectory ?? "app", - ); - - // find routes.ts - const routesFile = await findFile(path.join(appDirectory, "routes")); - assert(routesFile, "Cannot find 'routes' file"); - const routesImport = await runnerImport<{ - default: RouteConfigEntry[]; - }>(routesFile); - - // find root.tsx - const rootFile = await findFile(path.join(appDirectory, "root")); - assert(rootFile, "Cannot find 'root' file"); - - const routes = [ - { - id: "root", - path: "", - file: rootFile, - children: routesImport.module.default, - }, - ]; - - return { configFile, routesFile, appDirectory, routes }; -} - -// copied from -// https://github.com/jacob-ebey/parcel-plugin-react-router/blob/9385be813534537dfb0fe640a3e5c5607be3b61d/packages/resolver/src/resolver.ts - -function generateRoutesCode(config: { - appDirectory: string; - routes: RouteConfigEntry[]; -}) { - let code = "export default ["; - const closeRouteSymbol = Symbol("CLOSE_ROUTE"); - let stack: Array = [ - ...config.routes, - ]; - while (stack.length > 0) { - const route = stack.pop(); - if (!route) break; - if (route === closeRouteSymbol) { - code += "]},"; - continue; - } - code += "{"; - // TODO: route-module transform - code += `lazy: () => import(${JSON.stringify(path.resolve(config.appDirectory, route.file))}),`; - code += `id: ${JSON.stringify(route.id || createRouteId(route.file, config.appDirectory))},`; - if (typeof route.path === "string") { - code += `path: ${JSON.stringify(route.path)},`; - } - if (route.index) { - code += `index: true,`; - } - if (route.caseSensitive) { - code += `caseSensitive: true,`; - } - if (route.children) { - code += ["children:["]; - stack.push(closeRouteSymbol); - stack.push(...[...route.children].reverse()); - } else { - code += "},"; - } - } - code += "];\n"; - - return code; -} - -function createRouteId(file: string, appDirectory: string) { - return path - .relative(appDirectory, file) - .replace(/\\+/, "/") - .slice(0, -path.extname(file).length); -} diff --git a/packages/rsc/examples/react-router/react-router-vite/server-hmr.tsx b/packages/rsc/examples/react-router/react-router-vite/server-hmr.tsx deleted file mode 100644 index 2e228408f..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/server-hmr.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -import React from "react"; -import { useNavigate } from "react-router"; - -export function ServerHmr() { - if (import.meta.hot) { - const navigate = useNavigate(); - React.useEffect(() => { - const refetch = () => - navigate(window.location.pathname, { replace: true }); - import.meta.hot!.on("rsc:update", refetch); - return () => { - import.meta.hot!.off("rsc:update", refetch); - }; - }, [navigate]); - } - return null; -} diff --git a/packages/rsc/examples/react-router/react-router-vite/types.d.ts b/packages/rsc/examples/react-router/react-router-vite/types.d.ts deleted file mode 100644 index 349fcb3c8..000000000 --- a/packages/rsc/examples/react-router/react-router-vite/types.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// -/// - -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} - -declare module "virtual:react-router-routes" { - const routes: any; - export default routes; -} diff --git a/packages/rsc/examples/react-router/react-router.config.ts b/packages/rsc/examples/react-router/react-router.config.ts deleted file mode 100644 index 3bc5f5877..000000000 --- a/packages/rsc/examples/react-router/react-router.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Config } from "@react-router/dev/config"; - -export default { - appDirectory: "./app", -} satisfies Config; diff --git a/packages/rsc/examples/react-router/tsconfig.json b/packages/rsc/examples/react-router/tsconfig.json deleted file mode 100644 index 20b648a36..000000000 --- a/packages/rsc/examples/react-router/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/react-router/vite.config.ts b/packages/rsc/examples/react-router/vite.config.ts deleted file mode 100644 index fd93f3de3..000000000 --- a/packages/rsc/examples/react-router/vite.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import rsc from "@hiogawa/vite-rsc"; -import tailwindcss from "@tailwindcss/vite"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; -import inspect from "vite-plugin-inspect"; -import { reactRouter } from "./react-router-vite/plugin"; - -export default defineConfig({ - clearScreen: false, - build: { - minify: false, - }, - plugins: [ - tailwindcss(), - react(), - reactRouter(), - rsc({ - entries: { - client: "./react-router-vite/entry.browser.tsx", - ssr: "./react-router-vite/entry.ssr.single.tsx", - rsc: "./react-router-vite/entry.rsc.tsx", - }, - serverHandler: { - environmentName: "ssr", - entryName: "index", - }, - }), - inspect(), - ], - optimizeDeps: { - include: ["react-router", "react-router/internal/react-server-client"], - }, -}) as any; diff --git a/packages/rsc/examples/ssg/README.md b/packages/rsc/examples/ssg/README.md deleted file mode 100644 index c5da5218d..000000000 --- a/packages/rsc/examples/ssg/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# SSG + MDX example - -This example demonstrates: - -- Client component inside MDX -- MDX HMR -- Static site generation - -## usage - -```js -pnpm dev -pnpm build -pnpm preview -``` diff --git a/packages/rsc/examples/ssg/package.json b/packages/rsc/examples/ssg/package.json deleted file mode 100644 index 756ecc194..000000000 --- a/packages/rsc/examples/ssg/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-ssg", - "version": "0.0.0", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@hiogawa/vite-rsc": "latest", - "react": "latest", - "react-dom": "latest" - }, - "devDependencies": { - "@mdx-js/rollup": "^3.1.0", - "@types/react": "latest", - "@types/react-dom": "latest", - "@vitejs/plugin-react": "latest", - "vite": "latest", - "vite-plugin-inspect": "latest" - } -} diff --git a/packages/rsc/examples/ssg/public/favicon.ico b/packages/rsc/examples/ssg/public/favicon.ico deleted file mode 100644 index 4aff076603f8f01fb3b7d8b5e38df77cc1efdd69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmc(jeOQ&{9mmi9Y5(N4wKaEHmR6cpj%~hXo13+jb7bn2986aeCEr<~nOgZmpr$E` zf`o4brE!#(M1gZu646vl&`1pxMdw?|n=m>c+@F0P;mO>tlyk0ib#r~sdCqh0=e|Gp z@BY2q2Z!Sa`s>)yVZAyw{@CGY>u@-_O1kCfFIo4mH+}bzU$dHTI2`!e8Vx4;1ZKh^ zxKi)yD84^;xC-;kZcMM8u;Hxl=0 zrl(}R$D9P?+i+ezRaK9fv=7rK8`IkjJ!J!H{(<@MS+!R~`O?{>ox=Qa3-rsRXz(63 z2OX9VwCuy2qfp=E%do9`=6nq%BuDe7!jtKI6u6hZ(Up|S59#T~^xp-0ue5F~Z+jVS zZek&3&_;~E69#PN+~91w#MclR`Z@H~e)M!gs0@p&odJ103qFqWoKTLTHzP(@mAxzj(#e6hoycovD(ij}ivB{e#lRuP=cG5k$iaRo)dtx=G zM`-QOi1(><%*tYDlHdB;$}2ES5-{NlF)zQ3nIet}%c&n1$C&sM{8FwGkZ_g`sUGg! z3T>ptZyW}H`KjW=1Y|*Rxo@*u@#a=udF@Tij3~@2VhCG&oq>tVm=kl5JGI?yq#tEV znyQW7d%s`BnUNog;R7)^G4l^7CpCJ{a9%Yn`Yp_y*_byXFtZ{t(-+7Viy(9{&V^aD zbs2uD6#Nr0*t#nRmP8cFH{WZ{6~oeCpO^7?twAxY_*(EPM7@rgXEDr!SrITj3c}}8 zG&hNdv%li*9q=>hZiT(2yL8>xDH{YUC=?&&*$uMcN6JZFoDoXp$8j^YkZG`J2F#yD zT~q{{;?mf*+{vkkRh*ogz;80kX{(g`^V}P1yAv!$FZ=}TTrvCu$T`)zJ@6=Iw#2~7 zDX=^omRbzcx$<@-Pv=~u!|o<}xvTke<^?+EHqb5)?vdJw!(HPN#foO70Yl-k>>`W6NT?SD*WDt)y&<{AiS7Soz8*XK=RiTNo5?#oDKo7j&K*4as9+{Y$WBf^0FF zhPW_7T*Y)e2oGsl4=Su|Js>7q|IHS&LZG8pDR+w@NcCf#pc|Y1m!a|mxHMN<5IFx1 z8~>pkE;}TKz}oRNCQKpRUBH8fptD@dQYW$4vY4(*To35FgLJ?22Ui7UQ&lU1W0kw( zmA{hV*u|UYZ&JK{x(u?0L*`#0{co^Y3@IiNbQp!w zO~^{sk=0O8+H8YsRd7@>zI!0-dJcAo!6iFvdtPxEsQr*FoZ{FpidEZFc~oV-Tk*|$ zOiZ@A>Uvn-1u7yJ!OXeJSCKGPdDF_>p{gGfEe6%GMd?tjc)TqC{6ur_9{_tS27fUO z62lPLLUzcOFS-?jc~K-4?ZfXl{PI|{{KR7G7oUNpZc?8j4+TjnD#%v;R^FZ=O;ZgC zQw<4K9kXibq*$#{4s=ZGD|!+NHE*G=kG!mVVn4kRE-IKSOQ7zG>Zpg*Dnk!_{PpKI z^ege$`kG68tF7N7moCV*M=`td75h4u#3o_hR4h!F4JOMLvlF2rN59`Pl%M4|o^y(g zS}=__`)A81R}DOVN=Mz3(8JOR)qGpx>fXX;*=W+gG@L^E>t@wck4JM=z=<;1T8?r+ z2K1tp)T{Jd($Pnl{u<(`)5^1QqbH<3;_5B+5|_m^P~VlR|NpaD%c*J7PrU}Yugsfq z6=KWbwaXB4Ua4MOgTyu9jE-TFWv}nL35VJirUPP1tyZ~^yM!slY|{1Zn*D!(@9X_P D`VsgZ diff --git a/packages/rsc/examples/ssg/src/counter.tsx b/packages/rsc/examples/ssg/src/counter.tsx deleted file mode 100644 index 87c316493..000000000 --- a/packages/rsc/examples/ssg/src/counter.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import React from "react"; - -export function Counter() { - const [count, setCount] = React.useState(0); - - return ( - - ); -} diff --git a/packages/rsc/examples/ssg/src/framework/entry.browser.tsx b/packages/rsc/examples/ssg/src/framework/entry.browser.tsx deleted file mode 100644 index 2cbbe5ced..000000000 --- a/packages/rsc/examples/ssg/src/framework/entry.browser.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import * as ReactClient from "@hiogawa/vite-rsc/browser"; -import { getRscStreamFromHtml } from "@hiogawa/vite-rsc/rsc-html-stream/browser"; -import React from "react"; -import ReactDomClient from "react-dom/client"; -import { RSC_POSTFIX, type RscPayload } from "./shared"; - -async function hydrate(): Promise { - async function onNavigation() { - const url = new URL(window.location.href); - url.pathname = url.pathname + RSC_POSTFIX; - const payload = await ReactClient.createFromFetch(fetch(url)); - setPayload(payload); - } - - const initialPayload = await ReactClient.createFromReadableStream( - getRscStreamFromHtml(), - ); - - let setPayload: (v: RscPayload) => void; - - function BrowserRoot() { - const [payload, setPayload_] = React.useState(initialPayload); - - React.useEffect(() => { - setPayload = (v) => React.startTransition(() => setPayload_(v)); - }, [setPayload_]); - - React.useEffect(() => { - return listenNavigation(() => onNavigation()); - }, []); - - return payload.root; - } - - const browserRoot = ( - - - - ); - - ReactDomClient.hydrateRoot(document, browserRoot); - - if (import.meta.hot) { - import.meta.hot.on("rsc:update", () => { - window.history.replaceState({}, "", window.location.href); - }); - } -} - -function listenNavigation(onNavigation: () => void): () => void { - window.addEventListener("popstate", onNavigation); - - const oldPushState = window.history.pushState; - window.history.pushState = function (...args) { - const res = oldPushState.apply(this, args); - onNavigation(); - return res; - }; - - const oldReplaceState = window.history.replaceState; - window.history.replaceState = function (...args) { - const res = oldReplaceState.apply(this, args); - onNavigation(); - return res; - }; - - function onClick(e: MouseEvent) { - let link = (e.target as Element).closest("a"); - if ( - link && - link instanceof HTMLAnchorElement && - link.href && - (!link.target || link.target === "_self") && - link.origin === location.origin && - !link.hasAttribute("download") && - e.button === 0 && // left clicks only - !e.metaKey && // open in new tab (mac) - !e.ctrlKey && // open in new tab (windows) - !e.altKey && // download - !e.shiftKey && - !e.defaultPrevented - ) { - e.preventDefault(); - history.pushState(null, "", link.href); - } - } - document.addEventListener("click", onClick); - - return () => { - document.removeEventListener("click", onClick); - window.removeEventListener("popstate", onNavigation); - window.history.pushState = oldPushState; - window.history.replaceState = oldReplaceState; - }; -} - -hydrate(); diff --git a/packages/rsc/examples/ssg/src/framework/entry.rsc.tsx b/packages/rsc/examples/ssg/src/framework/entry.rsc.tsx deleted file mode 100644 index bf16fdf11..000000000 --- a/packages/rsc/examples/ssg/src/framework/entry.rsc.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as ReactServer from "@hiogawa/vite-rsc/rsc"; -import { Root, getStaticPaths } from "../root"; -import { RSC_POSTFIX, type RscPayload } from "./shared"; - -export { getStaticPaths }; - -export default async function handler(request: Request): Promise { - let url = new URL(request.url); - let isRscRequest = false; - if (url.pathname.endsWith(RSC_POSTFIX)) { - isRscRequest = true; - url.pathname = url.pathname.slice(0, -RSC_POSTFIX.length); - } - - const rscPayload: RscPayload = { root: }; - const rscStream = ReactServer.renderToReadableStream(rscPayload); - - if (isRscRequest) { - return new Response(rscStream, { - headers: { - "content-type": "text/x-component;charset=utf-8", - vary: "accept", - }, - }); - } - - const ssr = await import.meta.viteRsc.loadModule< - typeof import("./entry.ssr") - >("ssr", "index"); - const htmlStream = await ssr.renderHtml(rscStream); - - return new Response(htmlStream, { - headers: { - "content-type": "text/html;charset=utf-8", - vary: "accept", - }, - }); -} diff --git a/packages/rsc/examples/ssg/src/framework/entry.ssr.tsx b/packages/rsc/examples/ssg/src/framework/entry.ssr.tsx deleted file mode 100644 index 8a4085897..000000000 --- a/packages/rsc/examples/ssg/src/framework/entry.ssr.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { injectRscStreamToHtml } from "@hiogawa/vite-rsc/rsc-html-stream/ssr"; -import * as ReactClient from "@hiogawa/vite-rsc/ssr"; -import React from "react"; -import * as ReactDomServer from "react-dom/server.edge"; -import type { RscPayload } from "./shared"; - -export async function renderHtml(rscStream: ReadableStream) { - const [rscStream1, rscStream2] = rscStream.tee(); - - let payload: Promise; - function SsrRoot() { - payload ??= ReactClient.createFromReadableStream(rscStream1); - const root = React.use(payload).root; - return root; - } - - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - - const htmlStream = await ReactDomServer.renderToReadableStream(, { - bootstrapScriptContent, - }); - // for SSG - await htmlStream.allReady; - - let responseStream: ReadableStream = htmlStream; - responseStream = responseStream.pipeThrough( - injectRscStreamToHtml(rscStream2), - ); - return responseStream; -} diff --git a/packages/rsc/examples/ssg/src/framework/shared.tsx b/packages/rsc/examples/ssg/src/framework/shared.tsx deleted file mode 100644 index 555ef4e9f..000000000 --- a/packages/rsc/examples/ssg/src/framework/shared.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type React from "react"; - -export const RSC_POSTFIX = "_.rsc"; - -export type RscPayload = { - root: React.ReactNode; -}; diff --git a/packages/rsc/examples/ssg/src/posts/counter.mdx b/packages/rsc/examples/ssg/src/posts/counter.mdx deleted file mode 100644 index 3a7396688..000000000 --- a/packages/rsc/examples/ssg/src/posts/counter.mdx +++ /dev/null @@ -1,7 +0,0 @@ -export const title = "Counter in MDX" - -import { Counter } from "../counter"; - -# Counter in MDX - - diff --git a/packages/rsc/examples/ssg/src/posts/oxc.mdx b/packages/rsc/examples/ssg/src/posts/oxc.mdx deleted file mode 100644 index 5cff86b88..000000000 --- a/packages/rsc/examples/ssg/src/posts/oxc.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Oxc - -The fastest JavaScript language toolchain! diff --git a/packages/rsc/examples/ssg/src/posts/rolldown.mdx b/packages/rsc/examples/ssg/src/posts/rolldown.mdx deleted file mode 100644 index 71e2931a0..000000000 --- a/packages/rsc/examples/ssg/src/posts/rolldown.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Rolldown - -The fastest JavaScript bundler! diff --git a/packages/rsc/examples/ssg/src/posts/vite.mdx b/packages/rsc/examples/ssg/src/posts/vite.mdx deleted file mode 100644 index b510d3862..000000000 --- a/packages/rsc/examples/ssg/src/posts/vite.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Vite - -The build tool for the web! diff --git a/packages/rsc/examples/ssg/src/posts/vitest.mdx b/packages/rsc/examples/ssg/src/posts/vitest.mdx deleted file mode 100644 index 9b534e107..000000000 --- a/packages/rsc/examples/ssg/src/posts/vitest.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Vitest - -Next-generation test runner! diff --git a/packages/rsc/examples/ssg/src/react.d.ts b/packages/rsc/examples/ssg/src/react.d.ts deleted file mode 100644 index 10dd886d7..000000000 --- a/packages/rsc/examples/ssg/src/react.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} diff --git a/packages/rsc/examples/ssg/src/root.tsx b/packages/rsc/examples/ssg/src/root.tsx deleted file mode 100644 index 4369cdfeb..000000000 --- a/packages/rsc/examples/ssg/src/root.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Counter } from "./counter"; - -async function getPosts() { - let glob = import.meta.glob("./posts/*.mdx", { eager: true }); - glob = Object.fromEntries( - Object.entries(glob).map(([k, v]) => [ - k.slice("./posts".length, -".mdx".length), - v, - ]), - ); - return glob; -} - -export async function getStaticPaths() { - const posts = await getPosts(); - return ["/", ...Object.keys(posts)]; -} - -export async function Root({ url }: { url: URL }) { - const posts = await getPosts(); - - async function RootContent() { - if (url.pathname === "/") { - return ( -

- ); - } - - const module = posts[url.pathname]; - if (!!module) { - const Component = (module as any).default; - return ; - } - - // TODO: how to 404? - return

Not found

; - } - - return ( - - - - - RSC MDX SSG - - -
-

- RSC + MDX + SSG -

- - - Rendered at {new Date().toISOString()} - -
-
- -
- - - ); -} diff --git a/packages/rsc/examples/ssg/tsconfig.json b/packages/rsc/examples/ssg/tsconfig.json deleted file mode 100644 index 20a227f1b..000000000 --- a/packages/rsc/examples/ssg/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "erasableSyntaxOnly": true, - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["vite/client", "@hiogawa/vite-rsc/types"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/ssg/vite.config.ts b/packages/rsc/examples/ssg/vite.config.ts deleted file mode 100644 index 38094b1aa..000000000 --- a/packages/rsc/examples/ssg/vite.config.ts +++ /dev/null @@ -1,96 +0,0 @@ -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; -import { Readable } from "node:stream"; -import { pathToFileURL } from "node:url"; -import rsc from "@hiogawa/vite-rsc"; -import mdx from "@mdx-js/rollup"; -import react from "@vitejs/plugin-react"; -import { type Plugin, type ResolvedConfig, defineConfig } from "vite"; -import inspect from "vite-plugin-inspect"; -import { RSC_POSTFIX } from "./src/framework/shared"; - -export default defineConfig((env) => ({ - plugins: [ - mdx(), - react(), - rsc({ - entries: { - client: "./src/framework/entry.browser.tsx", - rsc: "./src/framework/entry.rsc.tsx", - ssr: "./src/framework/entry.ssr.tsx", - }, - serverHandler: env.isPreview ? false : undefined, - }), - rscSsgPlugin(), - inspect(), - ], -})); - -function rscSsgPlugin(): Plugin[] { - return [ - { - name: "rsc-ssg", - config(_config, env) { - if (env.isPreview) { - return { - appType: "mpa", - }; - } - }, - // Use post ssr writeBundle to wait for app is fully built. - // On Vite 7, you can use `buildApp` hook instead. - writeBundle: { - order: "post", - async handler() { - if (this.environment.name === "ssr") { - const config = this.environment.getTopLevelConfig(); - await renderStatic(config); - } - }, - }, - }, - ]; -} - -async function renderStatic(config: ResolvedConfig) { - // import server entry - const entryPath = path.join(config.environments.rsc.build.outDir, "index.js"); - const entry: typeof import("./src/framework/entry.rsc") = await import( - pathToFileURL(entryPath).href - ); - - // entry provides a list of static paths - const staticPaths = await entry.getStaticPaths(); - - // render rsc and html - const baseDir = config.environments.client.build.outDir; - for (const htmlPath of staticPaths) { - config.logger.info("[vite-rsc:ssg] -> " + htmlPath); - const rscPath = htmlPath + RSC_POSTFIX; - const htmlResponse = await entry.default( - new Request(new URL(htmlPath, "http://ssg.local")), - ); - assert.equal(htmlResponse.status, 200); - await fs.promises.writeFile( - path.join(baseDir, normalizeHtmlFilePath(htmlPath)), - Readable.fromWeb(htmlResponse.body as any), - ); - - const rscResponse = await entry.default( - new Request(new URL(rscPath, "http://ssg.local")), - ); - assert.equal(rscResponse.status, 200); - await fs.promises.writeFile( - path.join(baseDir, rscPath), - Readable.fromWeb(rscResponse.body as any), - ); - } -} - -function normalizeHtmlFilePath(p: string) { - if (p.endsWith("/")) { - return p + "index.html"; - } - return p + ".html"; -} diff --git a/packages/rsc/examples/starter-cf-single/README.md b/packages/rsc/examples/starter-cf-single/README.md deleted file mode 100644 index 78d862b66..000000000 --- a/packages/rsc/examples/starter-cf-single/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Vite + RSC + Cloudflare Workers - -https://vite-rsc-starter.hiro18181.workers.dev - -[examples/starter](https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/starter) integrated with [`@cloudflare/vite-plugin`](https://github.com/cloudflare/workers-sdk/tree/main/packages/vite-plugin-cloudflare). - -The difference from [examples/react-router](https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/react-router) is that this doesn't require two workers. - -- RSC environment always runs on Cloudflare Workers. -- During development, SSR environment runs as Vite's deafult Node environment. -- During production, SSR environment build output is directly imported into RSC environment build and both codes run on the same worker. - -Such communication mechanism is enabled via `rsc({ loadModuleDevProxy: true })` plugin option. - -```sh -# run dev server -npm run dev - -# build for production and preview -npm run build -npm run preview -npm run release -``` diff --git a/packages/rsc/examples/starter-cf-single/package.json b/packages/rsc/examples/starter-cf-single/package.json deleted file mode 100644 index e5a89f373..000000000 --- a/packages/rsc/examples/starter-cf-single/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-starter-cf-single", - "version": "0.0.0", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "release": "wrangler deploy" - }, - "dependencies": { - "@hiogawa/vite-rsc": "latest", - "react": "latest", - "react-dom": "latest" - }, - "devDependencies": { - "@cloudflare/vite-plugin": "^1.13.9", - "@types/react": "latest", - "@types/react-dom": "latest", - "@vitejs/plugin-react": "latest", - "vite": "latest" - } -} diff --git a/packages/rsc/examples/starter-cf-single/public/vite.svg b/packages/rsc/examples/starter-cf-single/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/packages/rsc/examples/starter-cf-single/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/rsc/examples/starter-cf-single/src/action.tsx b/packages/rsc/examples/starter-cf-single/src/action.tsx deleted file mode 100644 index 6b5029dcb..000000000 --- a/packages/rsc/examples/starter-cf-single/src/action.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use server"; - -let serverCounter = 0; - -export async function getServerCounter() { - return serverCounter; -} - -export async function updateServerCounter(change: number) { - serverCounter += change; -} diff --git a/packages/rsc/examples/starter-cf-single/src/assets/react.svg b/packages/rsc/examples/starter-cf-single/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/packages/rsc/examples/starter-cf-single/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/rsc/examples/starter-cf-single/src/client.tsx b/packages/rsc/examples/starter-cf-single/src/client.tsx deleted file mode 100644 index f857e6355..000000000 --- a/packages/rsc/examples/starter-cf-single/src/client.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -import React from "react"; - -export function ClientCounter() { - const [count, setCount] = React.useState(0); - - return ( - - ); -} diff --git a/packages/rsc/examples/starter-cf-single/src/framework/entry.browser.tsx b/packages/rsc/examples/starter-cf-single/src/framework/entry.browser.tsx deleted file mode 100644 index 5d255a56d..000000000 --- a/packages/rsc/examples/starter-cf-single/src/framework/entry.browser.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as ReactClient from "@hiogawa/vite-rsc/browser"; -import { getRscStreamFromHtml } from "@hiogawa/vite-rsc/rsc-html-stream/browser"; -import React from "react"; -import * as ReactDOMClient from "react-dom/client"; -import type { RscPayload } from "./entry.rsc"; - -async function main() { - // stash `setPayload` function to trigger re-rendering - // from outside of `BrowserRoot` component (e.g. server function call, navigation, hmr) - let setPayload: (v: RscPayload) => void; - - // deserialize RSC stream back to React VDOM for CSR - const initialPayload = await ReactClient.createFromReadableStream( - // initial RSC stream is injected in SSR stream as - getRscStreamFromHtml(), - ); - - // browser root component to (re-)render RSC payload as state - function BrowserRoot() { - const [payload, setPayload_] = React.useState(initialPayload); - - React.useEffect(() => { - setPayload = (v) => React.startTransition(() => setPayload_(v)); - }, [setPayload_]); - - // re-fetch/render on client side navigation - React.useEffect(() => { - return listenNavigation(() => fetchRscPayload()); - }, []); - - return payload.root; - } - - // re-fetch RSC and trigger re-rendering - async function fetchRscPayload() { - const payload = await ReactClient.createFromFetch( - fetch(window.location.href), - ); - setPayload(payload); - } - - // register a handler which will be internally called by React - // on server function request after hydration. - ReactClient.setServerCallback(async (id, args) => { - const url = new URL(window.location.href); - const temporaryReferences = ReactClient.createTemporaryReferenceSet(); - const payload = await ReactClient.createFromFetch( - fetch(url, { - method: "POST", - body: await ReactClient.encodeReply(args, { temporaryReferences }), - headers: { - "x-rsc-action": id, - }, - }), - { temporaryReferences }, - ); - setPayload(payload); - return payload.returnValue; - }); - - // hydration - const browserRoot = ( - - - - ); - ReactDOMClient.hydrateRoot(document, browserRoot, { - formState: initialPayload.formState, - }); - - // implement server HMR by trigering re-fetch/render of RSC upon server code change - if (import.meta.hot) { - import.meta.hot.on("rsc:update", () => { - fetchRscPayload(); - }); - } -} - -// a little helper to setup events interception for client side navigation -function listenNavigation(onNavigation: () => void) { - window.addEventListener("popstate", onNavigation); - - const oldPushState = window.history.pushState; - window.history.pushState = function (...args) { - const res = oldPushState.apply(this, args); - onNavigation(); - return res; - }; - - const oldReplaceState = window.history.replaceState; - window.history.replaceState = function (...args) { - const res = oldReplaceState.apply(this, args); - onNavigation(); - return res; - }; - - function onClick(e: MouseEvent) { - let link = (e.target as Element).closest("a"); - if ( - link && - link instanceof HTMLAnchorElement && - link.href && - (!link.target || link.target === "_self") && - link.origin === location.origin && - !link.hasAttribute("download") && - e.button === 0 && // left clicks only - !e.metaKey && // open in new tab (mac) - !e.ctrlKey && // open in new tab (windows) - !e.altKey && // download - !e.shiftKey && - !e.defaultPrevented - ) { - e.preventDefault(); - history.pushState(null, "", link.href); - } - } - document.addEventListener("click", onClick); - - return () => { - document.removeEventListener("click", onClick); - window.removeEventListener("popstate", onNavigation); - window.history.pushState = oldPushState; - window.history.replaceState = oldReplaceState; - }; -} - -main(); diff --git a/packages/rsc/examples/starter-cf-single/src/framework/entry.rsc.tsx b/packages/rsc/examples/starter-cf-single/src/framework/entry.rsc.tsx deleted file mode 100644 index 949d6090c..000000000 --- a/packages/rsc/examples/starter-cf-single/src/framework/entry.rsc.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as ReactServer from "@hiogawa/vite-rsc/rsc"; -import type { ReactFormState } from "react-dom/client"; -import { Root } from "../root.tsx"; - -export type RscPayload = { - root: React.ReactNode; - returnValue?: unknown; - formState?: ReactFormState; -}; - -async function handler(request: Request): Promise { - // handle server function request - const isAction = request.method === "POST"; - let returnValue: unknown | undefined; - let formState: ReactFormState | undefined; - let temporaryReferences: unknown | undefined; - if (isAction) { - // x-rsc-action header exists when action is called via `ReactClient.setServerCallback`. - const actionId = request.headers.get("x-rsc-action"); - if (actionId) { - const contentType = request.headers.get("content-type"); - const body = contentType?.startsWith("multipart/form-data") - ? await request.formData() - : await request.text(); - temporaryReferences = ReactServer.createTemporaryReferenceSet(); - const args = await ReactServer.decodeReply(body, { temporaryReferences }); - const action = await ReactServer.loadServerAction(actionId); - returnValue = await action.apply(null, args); - } else { - // otherwise server function is called via `
` - // before hydration (e.g. when javascript is disabled). - // aka progressive enhancement. - const formData = await request.formData(); - const decodedAction = await ReactServer.decodeAction(formData); - const result = await decodedAction(); - formState = await ReactServer.decodeFormState(result, formData); - } - } - - // serialization from React VDOM tree to RSC stream. - // we render RSC stream after handling server function request - // so that new render reflects updated state from server function call - // to achieve single round trip to mutate and fetch from server. - const rscStream = ReactServer.renderToReadableStream({ - // in this example, we always render the same `` - root: , - returnValue, - formState, - }); - - // respond RSC stream without HTML rendering based on framework's convention. - // here we use request header `content-type`. - // additionally we allow `?__rsc` and `?__html` to easily view payload directly. - const url = new URL(request.url); - const isRscRequest = - (!request.headers.get("accept")?.includes("text/html") && - !url.searchParams.has("__html")) || - url.searchParams.has("__rsc"); - - if (isRscRequest) { - return new Response(rscStream, { - headers: { - "content-type": "text/x-component;charset=utf-8", - vary: "accept", - }, - }); - } - - const { renderHTML } = await import.meta.viteRsc.loadModule< - typeof import("./entry.ssr.tsx") - >("ssr", "index"); - const htmlStream = await renderHTML(rscStream, { - formState, - // allow quick simulation of javscript disabled browser - debugNojs: url.searchParams.has("__nojs"), - }); - - // respond html - return new Response(htmlStream, { - headers: { - "Content-type": "text/html", - vary: "accept", - }, - }); -} - -export default { - fetch(request: Request) { - return handler(request); - }, -}; - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc/examples/starter-cf-single/src/framework/entry.ssr.tsx b/packages/rsc/examples/starter-cf-single/src/framework/entry.ssr.tsx deleted file mode 100644 index 3730215a2..000000000 --- a/packages/rsc/examples/starter-cf-single/src/framework/entry.ssr.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { injectRscStreamToHtml } from "@hiogawa/vite-rsc/rsc-html-stream/ssr"; // helper API -import * as ReactClient from "@hiogawa/vite-rsc/ssr"; // RSC API -import React from "react"; -import type { ReactFormState } from "react-dom/client"; -import * as ReactDOMServer from "react-dom/server.edge"; -import type { RscPayload } from "./entry.rsc"; - -export type RenderHTML = typeof renderHTML; - -export async function renderHTML( - rscStream: ReadableStream, - options?: { - formState?: ReactFormState; - nonce?: string; - debugNojs?: boolean; - }, -) { - // duplicate one RSC stream into two. - // - one for SSR (ReactClient.createFromReadableStream below) - // - another for browser hydration payload by injecting . - const [rscStream1, rscStream2] = rscStream.tee(); - - // deserialize RSC stream back to React VDOM - let payload: Promise; - function SsrRoot() { - // deserialization needs to be kicked off inside ReactDOMServer context - // for ReactDomServer preinit/preloading to work - payload ??= ReactClient.createFromReadableStream(rscStream1); - return React.use(payload).root; - } - - // render html (traditional SSR) - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - const htmlStream = await ReactDOMServer.renderToReadableStream(, { - bootstrapScriptContent: options?.debugNojs - ? undefined - : bootstrapScriptContent, - nonce: options?.nonce, - // no types - ...{ formState: options?.formState }, - }); - - let responseStream: ReadableStream = htmlStream; - if (!options?.debugNojs) { - // initial RSC stream is injected in HTML stream as - responseStream = responseStream.pipeThrough( - injectRscStreamToHtml(rscStream2, { - nonce: options?.nonce, - }), - ); - } - - return responseStream; -} diff --git a/packages/rsc/examples/starter-cf-single/src/framework/react.d.ts b/packages/rsc/examples/starter-cf-single/src/framework/react.d.ts deleted file mode 100644 index 10dd886d7..000000000 --- a/packages/rsc/examples/starter-cf-single/src/framework/react.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} diff --git a/packages/rsc/examples/starter-cf-single/src/index.css b/packages/rsc/examples/starter-cf-single/src/index.css deleted file mode 100644 index f4d2128c0..000000000 --- a/packages/rsc/examples/starter-cf-single/src/index.css +++ /dev/null @@ -1,112 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 1rem; -} - -.read-the-docs { - color: #888; - text-align: left; -} diff --git a/packages/rsc/examples/starter-cf-single/src/root.tsx b/packages/rsc/examples/starter-cf-single/src/root.tsx deleted file mode 100644 index 2920ef933..000000000 --- a/packages/rsc/examples/starter-cf-single/src/root.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import "./index.css"; -import viteLogo from "/vite.svg"; -import { getServerCounter, updateServerCounter } from "./action.tsx"; -import reactLogo from "./assets/react.svg"; -import { ClientCounter } from "./client.tsx"; - -export function Root() { - return ( - - - - - - Vite + RSC - - - - - - ); -} - -function App() { - return ( -
- -

Vite + RSC

-
- -
-
- - - -
-
    -
  • - Edit src/client.tsx to test client HMR. -
  • -
  • - Edit src/root.tsx to test server HMR. -
  • -
  • - Visit{" "} - - /?__rsc - {" "} - to view RSC stream payload. -
  • -
  • - Visit{" "} - - /?__nojs - {" "} - to test server action without js enabled. -
  • -
-
- ); -} diff --git a/packages/rsc/examples/starter-cf-single/tsconfig.json b/packages/rsc/examples/starter-cf-single/tsconfig.json deleted file mode 100644 index 20a227f1b..000000000 --- a/packages/rsc/examples/starter-cf-single/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "erasableSyntaxOnly": true, - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["vite/client", "@hiogawa/vite-rsc/types"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/starter-cf-single/vite.config.ts b/packages/rsc/examples/starter-cf-single/vite.config.ts deleted file mode 100644 index 48cafda1a..000000000 --- a/packages/rsc/examples/starter-cf-single/vite.config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { cloudflare } from "@cloudflare/vite-plugin"; -import rsc from "@hiogawa/vite-rsc"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -export default defineConfig((_env) => ({ - clearScreen: false, - build: { - minify: false, - }, - plugins: [ - react(), - rsc({ - entries: { - client: "./src/framework/entry.browser.tsx", - ssr: "./src/framework/entry.ssr.tsx", - }, - serverHandler: false, - loadModuleDevProxy: true, - }), - cloudflare({ - configPath: "./wrangler.jsonc", - viteEnvironment: { - name: "rsc", - }, - }), - ], - environments: { - rsc: { - build: { - rollupOptions: { - // ensure `default` export only in cloudflare entry output - preserveEntrySignatures: "exports-only", - }, - }, - }, - ssr: { - keepProcessEnv: false, - build: { - // build `ssr` inside `rsc` directory so that - // wrangler can deploy self-contained `dist/rsc` - outDir: "./dist/rsc/ssr", - }, - }, - }, -})); diff --git a/packages/rsc/examples/starter-cf-single/wrangler.jsonc b/packages/rsc/examples/starter-cf-single/wrangler.jsonc deleted file mode 100644 index bbd479cbb..000000000 --- a/packages/rsc/examples/starter-cf-single/wrangler.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://www.unpkg.com/wrangler@4.19.1/config-schema.json", - "name": "vite-rsc-starter", - "main": "./src/framework/entry.rsc.tsx", - "workers_dev": true, - "compatibility_date": "2025-04-01", - "compatibility_flags": ["nodejs_als"] -} diff --git a/packages/rsc/examples/starter/README.md b/packages/rsc/examples/starter/README.md deleted file mode 100644 index 36c7e7b08..000000000 --- a/packages/rsc/examples/starter/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Vite + RSC - -This example shows how to setup a React application with [Server Component](https://react.dev/reference/rsc/server-components) features on Vite using [`@hiogawa/vite-rsc`](https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc). - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/hi-ogawa/vite-plugins/tree/main/packages/rsc/examples/starter) - -```sh -# run dev server -npm run dev - -# build for production and preview -npm run build -npm run preview -``` - -## API usages - -See [`@hiogawa/vite-rsc`](https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc) for the documentation. - -- [`vite.config.ts`](./vite.config.ts) - - `@higoawa/vite-rsc/plugin` -- [`./src/framework/entry.rsc.tsx`](./src/framework/entry.rsc.tsx) - - `@hiogawa/vite-rsc/rsc` - - `import.meta.viteRsc.loadModule` -- [`./src/framework/entry.ssr.tsx`](./src/framework/entry.ssr.tsx) - - `@hiogawa/vite-rsc/ssr` - - `@hiogawa/vite-rsc/rsc-html-stream/ssr` - - `import.meta.viteRsc.loadBootstrapScriptContent` -- [`./src/framework/entry.browser.tsx`](./src/framework/entry.browser.tsx) - - `@hiogawa/vite-rsc/browser` - - `@hiogawa/vite-rsc/rsc-html-stream/browser` - -## Notes - -- [`./src/framework/entry.{browser,rsc,ssr}.tsx`](./src/framework) (with inline comments) provides an overview of how low level RSC (React flight) API can be used to build RSC framework. -- You can use [`vite-plugin-inspect`](https://github.com/antfu-collective/vite-plugin-inspect) to understand how `"use client"` and `"use server"` directives are transformed internally. diff --git a/packages/rsc/examples/starter/package.json b/packages/rsc/examples/starter/package.json deleted file mode 100644 index e784f8dcc..000000000 --- a/packages/rsc/examples/starter/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc-examples-starter", - "version": "0.0.0", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@hiogawa/vite-rsc": "latest", - "react": "latest", - "react-dom": "latest" - }, - "devDependencies": { - "@types/react": "latest", - "@types/react-dom": "latest", - "@vitejs/plugin-react": "latest", - "vite": "latest", - "vite-plugin-inspect": "latest" - } -} diff --git a/packages/rsc/examples/starter/public/vite.svg b/packages/rsc/examples/starter/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/packages/rsc/examples/starter/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/rsc/examples/starter/src/action.tsx b/packages/rsc/examples/starter/src/action.tsx deleted file mode 100644 index 6b5029dcb..000000000 --- a/packages/rsc/examples/starter/src/action.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use server"; - -let serverCounter = 0; - -export async function getServerCounter() { - return serverCounter; -} - -export async function updateServerCounter(change: number) { - serverCounter += change; -} diff --git a/packages/rsc/examples/starter/src/assets/react.svg b/packages/rsc/examples/starter/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/packages/rsc/examples/starter/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/rsc/examples/starter/src/client.tsx b/packages/rsc/examples/starter/src/client.tsx deleted file mode 100644 index f857e6355..000000000 --- a/packages/rsc/examples/starter/src/client.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -import React from "react"; - -export function ClientCounter() { - const [count, setCount] = React.useState(0); - - return ( - - ); -} diff --git a/packages/rsc/examples/starter/src/framework/entry.browser.tsx b/packages/rsc/examples/starter/src/framework/entry.browser.tsx deleted file mode 100644 index 5d255a56d..000000000 --- a/packages/rsc/examples/starter/src/framework/entry.browser.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as ReactClient from "@hiogawa/vite-rsc/browser"; -import { getRscStreamFromHtml } from "@hiogawa/vite-rsc/rsc-html-stream/browser"; -import React from "react"; -import * as ReactDOMClient from "react-dom/client"; -import type { RscPayload } from "./entry.rsc"; - -async function main() { - // stash `setPayload` function to trigger re-rendering - // from outside of `BrowserRoot` component (e.g. server function call, navigation, hmr) - let setPayload: (v: RscPayload) => void; - - // deserialize RSC stream back to React VDOM for CSR - const initialPayload = await ReactClient.createFromReadableStream( - // initial RSC stream is injected in SSR stream as - getRscStreamFromHtml(), - ); - - // browser root component to (re-)render RSC payload as state - function BrowserRoot() { - const [payload, setPayload_] = React.useState(initialPayload); - - React.useEffect(() => { - setPayload = (v) => React.startTransition(() => setPayload_(v)); - }, [setPayload_]); - - // re-fetch/render on client side navigation - React.useEffect(() => { - return listenNavigation(() => fetchRscPayload()); - }, []); - - return payload.root; - } - - // re-fetch RSC and trigger re-rendering - async function fetchRscPayload() { - const payload = await ReactClient.createFromFetch( - fetch(window.location.href), - ); - setPayload(payload); - } - - // register a handler which will be internally called by React - // on server function request after hydration. - ReactClient.setServerCallback(async (id, args) => { - const url = new URL(window.location.href); - const temporaryReferences = ReactClient.createTemporaryReferenceSet(); - const payload = await ReactClient.createFromFetch( - fetch(url, { - method: "POST", - body: await ReactClient.encodeReply(args, { temporaryReferences }), - headers: { - "x-rsc-action": id, - }, - }), - { temporaryReferences }, - ); - setPayload(payload); - return payload.returnValue; - }); - - // hydration - const browserRoot = ( - - - - ); - ReactDOMClient.hydrateRoot(document, browserRoot, { - formState: initialPayload.formState, - }); - - // implement server HMR by trigering re-fetch/render of RSC upon server code change - if (import.meta.hot) { - import.meta.hot.on("rsc:update", () => { - fetchRscPayload(); - }); - } -} - -// a little helper to setup events interception for client side navigation -function listenNavigation(onNavigation: () => void) { - window.addEventListener("popstate", onNavigation); - - const oldPushState = window.history.pushState; - window.history.pushState = function (...args) { - const res = oldPushState.apply(this, args); - onNavigation(); - return res; - }; - - const oldReplaceState = window.history.replaceState; - window.history.replaceState = function (...args) { - const res = oldReplaceState.apply(this, args); - onNavigation(); - return res; - }; - - function onClick(e: MouseEvent) { - let link = (e.target as Element).closest("a"); - if ( - link && - link instanceof HTMLAnchorElement && - link.href && - (!link.target || link.target === "_self") && - link.origin === location.origin && - !link.hasAttribute("download") && - e.button === 0 && // left clicks only - !e.metaKey && // open in new tab (mac) - !e.ctrlKey && // open in new tab (windows) - !e.altKey && // download - !e.shiftKey && - !e.defaultPrevented - ) { - e.preventDefault(); - history.pushState(null, "", link.href); - } - } - document.addEventListener("click", onClick); - - return () => { - document.removeEventListener("click", onClick); - window.removeEventListener("popstate", onNavigation); - window.history.pushState = oldPushState; - window.history.replaceState = oldReplaceState; - }; -} - -main(); diff --git a/packages/rsc/examples/starter/src/framework/entry.rsc.tsx b/packages/rsc/examples/starter/src/framework/entry.rsc.tsx deleted file mode 100644 index 4d5e89e63..000000000 --- a/packages/rsc/examples/starter/src/framework/entry.rsc.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import * as ReactServer from "@hiogawa/vite-rsc/rsc"; -import type { ReactFormState } from "react-dom/client"; -import { Root } from "../root.tsx"; - -// The schema of payload which is serialized into RSC stream on rsc environment -// and deserialized on ssr/client environments. -export type RscPayload = { - // this demo renders/serializes/deserizlies entire root html element - // but this mechanism can be changed to render/fetch different parts of components - // based on your own route conventions. - root: React.ReactNode; - // server action return value of non-progressive enhancement case - returnValue?: unknown; - // server action form state (e.g. useActionState) of progressive enhancement case - formState?: ReactFormState; -}; - -// the plugin by default assumes `rsc` entry having default export of request handler. -// however, how server entries are executed can be customized by registering -// own server handler e.g. `@cloudflare/vite-plugin`. -export default async function handler(request: Request): Promise { - // handle server function request - const isAction = request.method === "POST"; - let returnValue: unknown | undefined; - let formState: ReactFormState | undefined; - let temporaryReferences: unknown | undefined; - if (isAction) { - // x-rsc-action header exists when action is called via `ReactClient.setServerCallback`. - const actionId = request.headers.get("x-rsc-action"); - if (actionId) { - const contentType = request.headers.get("content-type"); - const body = contentType?.startsWith("multipart/form-data") - ? await request.formData() - : await request.text(); - temporaryReferences = ReactServer.createTemporaryReferenceSet(); - const args = await ReactServer.decodeReply(body, { temporaryReferences }); - const action = await ReactServer.loadServerAction(actionId); - returnValue = await action.apply(null, args); - } else { - // otherwise server function is called via `
` - // before hydration (e.g. when javascript is disabled). - // aka progressive enhancement. - const formData = await request.formData(); - const decodedAction = await ReactServer.decodeAction(formData); - const result = await decodedAction(); - formState = await ReactServer.decodeFormState(result, formData); - } - } - - // serialization from React VDOM tree to RSC stream. - // we render RSC stream after handling server function request - // so that new render reflects updated state from server function call - // to achieve single round trip to mutate and fetch from server. - const rscStream = ReactServer.renderToReadableStream({ - // in this example, we always render the same `` - root: , - returnValue, - formState, - }); - - // respond RSC stream without HTML rendering based on framework's convention. - // here we use request header `content-type`. - // additionally we allow `?__rsc` and `?__html` to easily view payload directly. - const url = new URL(request.url); - const isRscRequest = - (!request.headers.get("accept")?.includes("text/html") && - !url.searchParams.has("__html")) || - url.searchParams.has("__rsc"); - - if (isRscRequest) { - return new Response(rscStream, { - headers: { - "content-type": "text/x-component;charset=utf-8", - vary: "accept", - }, - }); - } - - // Delegate to SSR environment for html rendering. - // The plugin provides `loadSsrModule` helper to allow loading SSR environment entry module - // in RSC environment. however this can be customized by implementing own runtime communication - // e.g. `@cloudflare/vite-plugin`'s service binding. - const ssrEntryModule = await import.meta.viteRsc.loadModule< - typeof import("./entry.ssr.tsx") - >("ssr", "index"); - const htmlStream = await ssrEntryModule.renderHTML(rscStream, { - formState, - // allow quick simulation of javscript disabled browser - debugNojs: url.searchParams.has("__nojs"), - }); - - // respond html - return new Response(htmlStream, { - headers: { - "Content-type": "text/html", - vary: "accept", - }, - }); -} - -if (import.meta.hot) { - import.meta.hot.accept(); -} diff --git a/packages/rsc/examples/starter/src/framework/entry.ssr.tsx b/packages/rsc/examples/starter/src/framework/entry.ssr.tsx deleted file mode 100644 index 70ff1d33e..000000000 --- a/packages/rsc/examples/starter/src/framework/entry.ssr.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { injectRscStreamToHtml } from "@hiogawa/vite-rsc/rsc-html-stream/ssr"; // helper API -import * as ReactClient from "@hiogawa/vite-rsc/ssr"; // RSC API -import React from "react"; -import type { ReactFormState } from "react-dom/client"; -import * as ReactDOMServer from "react-dom/server.edge"; -import type { RscPayload } from "./entry.rsc"; - -export async function renderHTML( - rscStream: ReadableStream, - options: { - formState?: ReactFormState; - nonce?: string; - debugNojs?: boolean; - }, -) { - // duplicate one RSC stream into two. - // - one for SSR (ReactClient.createFromReadableStream below) - // - another for browser hydration payload by injecting . - const [rscStream1, rscStream2] = rscStream.tee(); - - // deserialize RSC stream back to React VDOM - let payload: Promise; - function SsrRoot() { - // deserialization needs to be kicked off inside ReactDOMServer context - // for ReactDomServer preinit/preloading to work - payload ??= ReactClient.createFromReadableStream(rscStream1); - return React.use(payload).root; - } - - // render html (traditional SSR) - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - const htmlStream = await ReactDOMServer.renderToReadableStream(, { - bootstrapScriptContent: options?.debugNojs - ? undefined - : bootstrapScriptContent, - nonce: options?.nonce, - // no types - ...{ formState: options?.formState }, - }); - - let responseStream: ReadableStream = htmlStream; - if (!options?.debugNojs) { - // initial RSC stream is injected in HTML stream as - responseStream = responseStream.pipeThrough( - injectRscStreamToHtml(rscStream2, { - nonce: options?.nonce, - }), - ); - } - - return responseStream; -} diff --git a/packages/rsc/examples/starter/src/framework/react.d.ts b/packages/rsc/examples/starter/src/framework/react.d.ts deleted file mode 100644 index 10dd886d7..000000000 --- a/packages/rsc/examples/starter/src/framework/react.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} diff --git a/packages/rsc/examples/starter/src/index.css b/packages/rsc/examples/starter/src/index.css deleted file mode 100644 index f4d2128c0..000000000 --- a/packages/rsc/examples/starter/src/index.css +++ /dev/null @@ -1,112 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 1rem; -} - -.read-the-docs { - color: #888; - text-align: left; -} diff --git a/packages/rsc/examples/starter/src/root.tsx b/packages/rsc/examples/starter/src/root.tsx deleted file mode 100644 index 0f84a74c0..000000000 --- a/packages/rsc/examples/starter/src/root.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import "./index.css"; // css import is automatically injected in exported server components -import viteLogo from "/vite.svg"; -import { getServerCounter, updateServerCounter } from "./action.tsx"; -import reactLogo from "./assets/react.svg"; -import { ClientCounter } from "./client.tsx"; - -export function Root() { - return ( - - - - - - Vite + RSC - - - - - - ); -} - -function App() { - return ( -
- -

Vite + RSC

-
- -
-
- - - -
-
    -
  • - Edit src/client.tsx to test client HMR. -
  • -
  • - Edit src/root.tsx to test server HMR. -
  • -
  • - Visit{" "} - - /?__rsc - {" "} - to view RSC stream payload. -
  • -
  • - Visit{" "} - - /?__nojs - {" "} - to test server action without js enabled. -
  • -
-
- ); -} diff --git a/packages/rsc/examples/starter/tsconfig.json b/packages/rsc/examples/starter/tsconfig.json deleted file mode 100644 index 20a227f1b..000000000 --- a/packages/rsc/examples/starter/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "erasableSyntaxOnly": true, - "allowImportingTsExtensions": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "moduleResolution": "Bundler", - "module": "ESNext", - "target": "ESNext", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["vite/client", "@hiogawa/vite-rsc/types"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/examples/starter/vite.config.ts b/packages/rsc/examples/starter/vite.config.ts deleted file mode 100644 index 5e9467749..000000000 --- a/packages/rsc/examples/starter/vite.config.ts +++ /dev/null @@ -1,73 +0,0 @@ -import rsc from "@hiogawa/vite-rsc"; -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; -// import inspect from "vite-plugin-inspect"; - -export default defineConfig({ - plugins: [ - rsc({ - // `entries` option is only a shorthand for specifying each `rollupOptions.input` below - // > entries: { rsc, ssr, client }, - // - // by default, the plugin setup request handler based on `default export` of `rsc` environment `rollupOptions.input.index`. - // This can be disabled when setting up own server handler e.g. `@cloudflare/vite-plugin`. - // > serverHandler: false - }), - - // use any of react plugins https://github.com/vitejs/vite-plugin-react - // to enable client component HMR - react(), - - // use https://github.com/antfu-collective/vite-plugin-inspect - // to understand internal transforms required for RSC. - // inspect(), - ], - - // specify entry point for each environment. - // (currently the plugin assumes `rollupOptions.input.index` for some features.) - environments: { - // `rsc` environment loads modules with `react-server` condition. - // this environment is responsible for: - // - RSC stream serialization (React VDOM -> RSC stream) - // - server functions handling - rsc: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.rsc.tsx", - }, - }, - }, - }, - - // `ssr` environment loads modules without `react-server` condition. - // this environment is responsible for: - // - RSC stream deserialization (RSC stream -> React VDOM) - // - traditional SSR (React VDOM -> HTML string/stream) - ssr: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.ssr.tsx", - }, - }, - }, - }, - - // client environment is used for hydration and client-side rendering - // this environment is responsible for: - // - RSC stream deserialization (RSC stream -> React VDOM) - // - traditional CSR (React VDOM -> Browser DOM tree mount/hydration) - // - refetch and re-render RSC - // - calling server functions - client: { - build: { - rollupOptions: { - input: { - index: "./src/framework/entry.browser.tsx", - }, - }, - }, - }, - }, -}); diff --git a/packages/rsc/package.json b/packages/rsc/package.json deleted file mode 100644 index 24569c047..000000000 --- a/packages/rsc/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@hiogawa/vite-rsc", - "version": "0.4.9", - "homepage": "https://github.com/hi-ogawa/vite-plugins/tree/main/packages/rsc", - "repository": { - "type": "git", - "url": "git+https://github.com/hi-ogawa/vite-plugins.git", - "directory": "packages/rsc" - }, - "license": "MIT", - "type": "module", - "exports": { - "./package.json": "./package.json", - "./types": "./types/index.d.ts", - ".": "./dist/index.js", - "./*": "./dist/*.js" - }, - "files": ["dist", "types"], - "scripts": { - "test-e2e": "playwright test --project=chromium", - "test-e2e-ci": "playwright test", - "test-package": "bash scripts/test-package.sh", - "dev": "tsdown --sourcemap --watch src", - "build": "tsdown", - "prepack": "tsdown --clean" - }, - "dependencies": { - "@hiogawa/transforms": "workspace:*", - "@mjackson/node-fetch-server": "^0.6.1", - "es-module-lexer": "^1.6.0", - "magic-string": "^0.30.17", - "turbo-stream": "^3.1.0", - "vitefu": "^1.0.5" - }, - "devDependencies": { - "@playwright/test": "^1.53.1", - "react-server-dom-webpack": "*", - "rsc-html-stream": "^0.0.6", - "tinyexec": "^1.0.1" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*", - "vite": "*" - } -} diff --git a/packages/rsc/playwright.config.ts b/packages/rsc/playwright.config.ts deleted file mode 100644 index 7de224e24..000000000 --- a/packages/rsc/playwright.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -export default defineConfig({ - testDir: "e2e", - use: { - trace: "on-first-retry", - }, - expect: { - toPass: { timeout: 5000 }, - }, - projects: [ - { - name: "chromium", - use: { - ...devices["Desktop Chrome"], - viewport: null, - deviceScaleFactor: undefined, - }, - }, - { - name: "firefox", - use: devices["Desktop Firefox"], - }, - { - name: "webkit", - use: devices["Desktop Safari"], - }, - ], - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - reporter: ["list", process.env.CI && "github"] - .filter(Boolean) - .map((name) => [name] as any), -}) as any; diff --git a/packages/rsc/src/browser.ts b/packages/rsc/src/browser.ts deleted file mode 100644 index 1f011146d..000000000 --- a/packages/rsc/src/browser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as clientReferences from "virtual:vite-rsc/client-references"; -import { setRequireModule } from "./core/browser"; - -export * from "./react/browser"; - -initialize(); - -function initialize(): void { - setRequireModule({ - load: async (id) => { - if (!import.meta.env.__vite_rsc_build__) { - // @ts-ignore - return __vite_rsc_raw_import__(import.meta.env.BASE_URL + id.slice(1)); - } else { - const import_ = clientReferences.default[id]; - if (!import_) { - throw new Error(`client reference not found '${id}'`); - } - return import_(); - } - }, - }); -} diff --git a/packages/rsc/src/core/browser.ts b/packages/rsc/src/core/browser.ts deleted file mode 100644 index 942ce5376..000000000 --- a/packages/rsc/src/core/browser.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { memoize } from "@hiogawa/utils"; -import { removeReferenceCacheTag, setInternalRequire } from "./shared"; - -let init = false; - -export function setRequireModule(options: { - load: (id: string) => Promise; -}): void { - if (init) return; - init = true; - - const requireModule = memoize((id: string) => { - return options.load(removeReferenceCacheTag(id)); - }); - - (globalThis as any).__vite_rsc_client_require__ = requireModule; - - setInternalRequire(); -} diff --git a/packages/rsc/src/core/plugin.ts b/packages/rsc/src/core/plugin.ts deleted file mode 100644 index f7f54fe45..000000000 --- a/packages/rsc/src/core/plugin.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Plugin } from "vite"; - -export default function vitePluginRscCore(): Plugin[] { - return [ - { - name: "rsc:patch-react-server-dom-webpack", - transform(originalCode, _id, _options) { - let code = originalCode; - if (code.includes("__webpack_require__.u")) { - // avoid accessing `__webpack_require__` on import side effect - // https://github.com/facebook/react/blob/a9bbe34622885ef5667d33236d580fe7321c0d8b/packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js#L16-L17 - code = code.replaceAll("__webpack_require__.u", "({}).u"); - } - - // the existance of `__webpack_require__` global can break some packages - // https://github.com/TooTallNate/node-bindings/blob/c8033dcfc04c34397384e23f7399a30e6c13830d/bindings.js#L90-L94 - if (code.includes("__webpack_require__")) { - code = code.replaceAll("__webpack_require__", "__vite_rsc_require__"); - } - - if (code !== originalCode) { - return { code, map: null }; - } - }, - }, - { - // commonjsOptions needs to be tweaked when this is a linked dep - // since otherwise vendored cjs doesn't work. - name: "rsc:workaround-linked-dep", - apply: () => !import.meta.url.includes("/node_modules/"), - configEnvironment() { - return { - build: { - commonjsOptions: { - include: [/\/node_modules\//, /\/vendor\/react-server-dom\//], - }, - }, - }; - }, - }, - ]; -} diff --git a/packages/rsc/src/core/rsc.ts b/packages/rsc/src/core/rsc.ts deleted file mode 100644 index 701628bf0..000000000 --- a/packages/rsc/src/core/rsc.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { memoize, tinyassert } from "@hiogawa/utils"; -import type { BundlerConfig, ImportManifestEntry, ModuleMap } from "../types"; -import { - SERVER_DECODE_CLIENT_PREFIX, - SERVER_REFERENCE_PREFIX, - createReferenceCacheTag, - removeReferenceCacheTag, - setInternalRequire, -} from "./shared"; - -// @ts-ignore -import * as ReactServer from "@hiogawa/vite-rsc/vendor/react-server-dom/server.edge"; - -let init = false; -let requireModule!: (id: string) => unknown; - -export function setRequireModule(options: { - load: (id: string) => unknown; -}): void { - if (init) return; - init = true; - - requireModule = (id) => { - return options.load(removeReferenceCacheTag(id)); - }; - - // need memoize to return stable promise from __webpack_require__ - (globalThis as any).__vite_rsc_server_require__ = memoize( - async (id: string) => { - if (id.startsWith(SERVER_DECODE_CLIENT_PREFIX)) { - id = id.slice(SERVER_DECODE_CLIENT_PREFIX.length); - id = removeReferenceCacheTag(id); - // create `registerClientReference` on the fly since there's no way to - // grab the original client reference module on ther server. - // cf. https://github.com/lazarv/react-server/blob/79e7acebc6f4a8c930ad8422e2a4a9fdacfcce9b/packages/react-server/server/module-loader.mjs#L19 - // decode client reference on the server - return new Proxy({} as any, { - get(target, name, _receiver) { - if (typeof name !== "string" || name === "then") return; - return (target[name] ??= ReactServer.registerClientReference( - () => { - throw new Error( - `Unexpectedly client reference export '${name}' is called on server`, - ); - }, - id, - name, - )); - }, - }); - } - return requireModule(id); - }, - ); - - setInternalRequire(); -} - -export async function loadServerAction(id: string): Promise { - const [file, name] = id.split("#") as [string, string]; - const mod: any = await requireModule(file); - return mod[name]; -} - -export function createServerManifest(): BundlerConfig { - const cacheTag = import.meta.env.DEV ? createReferenceCacheTag() : ""; - - return new Proxy( - {}, - { - get(_target, $$id, _receiver) { - tinyassert(typeof $$id === "string"); - let [id, name] = $$id.split("#"); - tinyassert(id); - tinyassert(name); - return { - id: SERVER_REFERENCE_PREFIX + id + cacheTag, - name, - chunks: [], - async: true, - } satisfies ImportManifestEntry; - }, - }, - ); -} - -export function createServerDecodeClientManifest(): ModuleMap { - return new Proxy( - {}, - { - get(_target, id: string) { - return new Proxy( - {}, - { - get(_target, name: string) { - return { - id: SERVER_REFERENCE_PREFIX + SERVER_DECODE_CLIENT_PREFIX + id, - name, - chunks: [], - async: true, - }; - }, - }, - ); - }, - }, - ); -} - -export function createClientManifest(): BundlerConfig { - const cacheTag = import.meta.env.DEV ? createReferenceCacheTag() : ""; - - return new Proxy( - {}, - { - get(_target, $$id, _receiver) { - tinyassert(typeof $$id === "string"); - let [id, name] = $$id.split("#"); - tinyassert(id); - tinyassert(name); - return { - id: id + cacheTag, - name, - chunks: [], - async: true, - } satisfies ImportManifestEntry; - }, - }, - ); -} diff --git a/packages/rsc/src/core/shared.ts b/packages/rsc/src/core/shared.ts deleted file mode 100644 index 0780a2b97..000000000 --- a/packages/rsc/src/core/shared.ts +++ /dev/null @@ -1,25 +0,0 @@ -// use special prefix to switch client/server reference loading inside __webpack_require__ -export const SERVER_REFERENCE_PREFIX = "$$server:"; - -export const SERVER_DECODE_CLIENT_PREFIX = "$$decode-client:"; - -// cache bust memoized require promise during dev -export function createReferenceCacheTag(): string { - const cache = Math.random().toString(36).slice(2); - return "$$cache=" + cache; -} - -export function removeReferenceCacheTag(id: string): string { - return id.split("$$cache=")[0]!; -} - -export function setInternalRequire(): void { - // branch client and server require to support the case when ssr and rsc share the same global - (globalThis as any).__vite_rsc_require__ = (id: string) => { - if (id.startsWith(SERVER_REFERENCE_PREFIX)) { - id = id.slice(SERVER_REFERENCE_PREFIX.length); - return (globalThis as any).__vite_rsc_server_require__(id); - } - return (globalThis as any).__vite_rsc_client_require__(id); - }; -} diff --git a/packages/rsc/src/core/ssr.ts b/packages/rsc/src/core/ssr.ts deleted file mode 100644 index 8838f8c8b..000000000 --- a/packages/rsc/src/core/ssr.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { memoize } from "@hiogawa/utils"; -import type { ServerConsumerManifest } from "../types"; -import { removeReferenceCacheTag, setInternalRequire } from "./shared"; - -let init = false; - -export function setRequireModule(options: { - load: (id: string) => unknown; -}): void { - if (init) return; - init = true; - - const requireModule = memoize((id: string) => { - return options.load(removeReferenceCacheTag(id)); - }); - - const clientRequire = (id: string) => { - return requireModule(id); - }; - (globalThis as any).__vite_rsc_client_require__ = clientRequire; - - setInternalRequire(); -} - -export function createServerConsumerManifest(): ServerConsumerManifest { - return {}; -} diff --git a/packages/rsc/src/extra/browser.tsx b/packages/rsc/src/extra/browser.tsx deleted file mode 100644 index 1ffbb22a1..000000000 --- a/packages/rsc/src/extra/browser.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from "react"; -import ReactDomClient from "react-dom/client"; -import { rscStream } from "rsc-html-stream/client"; -import { - type CallServerCallback, - createFromFetch, - createFromReadableStream, - createTemporaryReferenceSet, - encodeReply, - setServerCallback, -} from "../browser"; -import type { RscPayload } from "./rsc"; - -export async function hydrate(): Promise { - const callServer: CallServerCallback = async (id, args) => { - const url = new URL(window.location.href); - const temporaryReferences = createTemporaryReferenceSet(); - const payload = await createFromFetch( - fetch(url, { - method: "POST", - body: await encodeReply(args, { temporaryReferences }), - headers: { - "x-rsc-action": id, - }, - }), - { temporaryReferences }, - ); - setPayload(payload); - return payload.returnValue; - }; - setServerCallback(callServer); - - async function onNavigation() { - const url = new URL(window.location.href); - const payload = await createFromFetch(fetch(url)); - setPayload(payload); - } - - const initialPayload = await createFromReadableStream(rscStream); - - let setPayload: (v: RscPayload) => void; - - function BrowserRoot() { - const [payload, setPayload_] = React.useState(initialPayload); - - React.useEffect(() => { - setPayload = (v) => React.startTransition(() => setPayload_(v)); - }, [setPayload_]); - - React.useEffect(() => { - return listenNavigation(() => onNavigation()); - }, []); - - return payload.root; - } - - const browserRoot = ( - - - - ); - - ReactDomClient.hydrateRoot(document, browserRoot, { - formState: initialPayload.formState, - }); - - if (import.meta.hot) { - import.meta.hot.on("rsc:update", () => { - window.history.replaceState({}, "", window.location.href); - }); - } -} - -export async function fetchRSC( - request: string | URL | Request, -): Promise { - const payload = await createFromFetch(fetch(request)); - return payload.root; -} - -function listenNavigation(onNavigation: () => void): () => void { - window.addEventListener("popstate", onNavigation); - - const oldPushState = window.history.pushState; - window.history.pushState = function (...args) { - const res = oldPushState.apply(this, args); - onNavigation(); - return res; - }; - - const oldReplaceState = window.history.replaceState; - window.history.replaceState = function (...args) { - const res = oldReplaceState.apply(this, args); - onNavigation(); - return res; - }; - - function onClick(e: MouseEvent) { - let link = (e.target as Element).closest("a"); - if ( - link && - link instanceof HTMLAnchorElement && - link.href && - (!link.target || link.target === "_self") && - link.origin === location.origin && - !link.hasAttribute("download") && - e.button === 0 && // left clicks only - !e.metaKey && // open in new tab (mac) - !e.ctrlKey && // open in new tab (windows) - !e.altKey && // download - !e.shiftKey && - !e.defaultPrevented - ) { - e.preventDefault(); - history.pushState(null, "", link.href); - } - } - document.addEventListener("click", onClick); - - return () => { - document.removeEventListener("click", onClick); - window.removeEventListener("popstate", onNavigation); - window.history.pushState = oldPushState; - window.history.replaceState = oldReplaceState; - }; -} diff --git a/packages/rsc/src/extra/rsc.tsx b/packages/rsc/src/extra/rsc.tsx deleted file mode 100644 index 276d36bb1..000000000 --- a/packages/rsc/src/extra/rsc.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type { ReactFormState } from "react-dom/client"; -import { - createTemporaryReferenceSet, - decodeAction, - decodeFormState, - decodeReply, - loadServerAction, - renderToReadableStream, -} from "../rsc"; - -export type RscPayload = { - root: React.ReactNode; - formState?: ReactFormState; - returnValue?: unknown; -}; - -export async function renderRequest( - request: Request, - root: React.ReactNode, - options?: { nonce?: string }, -): Promise { - function RscRoot() { - // https://vite.dev/guide/features.html#content-security-policy-csp - // this isn't needed if `style-src: 'unsafe-inline'` (dev) and `script-src: 'self'` - const nonceMeta = options?.nonce && ( - - ); - return ( - <> - {nonceMeta} - {root} - - ); - } - - const url = new URL(request.url); - const isAction = request.method === "POST"; - - // use ?__rsc and ?__html for quick debugging - const isRscRequest = - (!request.headers.get("accept")?.includes("text/html") && - !url.searchParams.has("__html")) || - url.searchParams.has("__rsc"); - - // TODO: error handling - // callAction - let returnValue: unknown | undefined; - let formState: ReactFormState | undefined; - let temporaryReferences: unknown | undefined; - if (isAction) { - const actionId = request.headers.get("x-rsc-action"); - if (actionId) { - // client stream request - const contentType = request.headers.get("content-type"); - const body = contentType?.startsWith("multipart/form-data") - ? await request.formData() - : await request.text(); - temporaryReferences = createTemporaryReferenceSet(); - const args = await decodeReply(body, { temporaryReferences }); - const action = await loadServerAction(actionId); - returnValue = await action.apply(null, args); - } else { - // progressive enhancement - const formData = await request.formData(); - const decodedAction = await decodeAction(formData); - const result = await decodedAction(); - formState = await decodeFormState(result, formData); - } - } - - const rscPayload: RscPayload = { root: , formState, returnValue }; - const rscOptions = { temporaryReferences }; - const rscStream = renderToReadableStream(rscPayload, rscOptions); - - if (isRscRequest) { - return new Response(rscStream, { - headers: { - "content-type": "text/x-component;charset=utf-8", - vary: "accept", - }, - }); - } - - const ssrEntry = await import.meta.viteRsc.loadModule( - "ssr", - "index", - ); - return ssrEntry.renderHtml(rscStream, { - formState, - nonce: options?.nonce, - debugNoJs: url.searchParams.has("__nojs"), - }); -} diff --git a/packages/rsc/src/extra/ssr.tsx b/packages/rsc/src/extra/ssr.tsx deleted file mode 100644 index 25fb1fc27..000000000 --- a/packages/rsc/src/extra/ssr.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import type { ReactFormState } from "react-dom/client"; -import ReactDomServer from "react-dom/server.edge"; -import { injectRSCPayload } from "rsc-html-stream/server"; -import { createFromReadableStream } from "../ssr"; -import type { RscPayload } from "./rsc"; - -export async function renderHtml( - rscStream: ReadableStream, - options?: { - formState?: ReactFormState; - nonce?: string; - debugNoJs?: boolean; - }, -): Promise { - const [rscStream1, rscStream2] = rscStream.tee(); - - // flight deserialization needs to be kicked off inside SSR context - // for ReactDomServer preinit/preloading to work - let payload: Promise; - function SsrRoot() { - payload ??= createFromReadableStream(rscStream1, { - nonce: options?.nonce, - }); - const root = React.use(payload).root; - return root; - } - - const bootstrapScriptContent = - await import.meta.viteRsc.loadBootstrapScriptContent("index"); - const htmlStream = await ReactDomServer.renderToReadableStream(, { - bootstrapScriptContent: options?.debugNoJs - ? undefined - : bootstrapScriptContent, - nonce: options?.nonce, - // no types - ...{ formState: options?.formState }, - }); - - let responseStream: ReadableStream = htmlStream; - if (!options?.debugNoJs) { - responseStream = responseStream.pipeThrough( - injectRSCPayload(rscStream2, { - nonce: options?.nonce, - }), - ); - } - - return new Response(responseStream, { - headers: { - "content-type": "text/html;charset=utf-8", - vary: "accept", - }, - }); -} diff --git a/packages/rsc/src/index.ts b/packages/rsc/src/index.ts deleted file mode 100644 index 848a7bc7d..000000000 --- a/packages/rsc/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default, type RscPluginOptions } from "./plugin"; -export { transformHoistInlineDirective } from "@hiogawa/transforms"; diff --git a/packages/rsc/src/plugin.ts b/packages/rsc/src/plugin.ts deleted file mode 100644 index a84885a78..000000000 --- a/packages/rsc/src/plugin.ts +++ /dev/null @@ -1,1803 +0,0 @@ -import assert from "node:assert"; -import { createHash } from "node:crypto"; -import fs from "node:fs"; -import { createRequire } from "node:module"; -import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { - type TransformWrapExportFilter, - hasDirective, - transformDirectiveProxyExport, - transformServerActionServer, - transformWrapExport, -} from "@hiogawa/transforms"; -import { createRequestListener } from "@mjackson/node-fetch-server"; -import * as esModuleLexer from "es-module-lexer"; -import MagicString from "magic-string"; -import { - type DevEnvironment, - type EnvironmentModuleNode, - type Plugin, - type ResolvedConfig, - type Rollup, - type RunnableDevEnvironment, - type ViteDevServer, - defaultServerConditions, - isCSSRequest, - normalizePath, - parseAstAsync, -} from "vite"; -import { crawlFrameworkPkgs } from "vitefu"; -import vitePluginRscCore from "./core/plugin"; -import { generateEncryptionKey, toBase64 } from "./utils/encryption-utils"; -import { createRpcServer } from "./utils/rpc"; -import { normalizeViteImportAnalysisUrl } from "./vite-utils"; - -// state for build orchestration -let serverReferences: Record = {}; -let server: ViteDevServer; -let config: ResolvedConfig; -let rscBundle: Rollup.OutputBundle; -let buildAssetsManifest: AssetsManifest | undefined; -let isScanBuild = false; -const BUILD_ASSETS_MANIFEST_NAME = "__vite_rsc_assets_manifest.js"; - -type ClientReferenceMeta = { - importId: string; - // same as `importId` during dev. hashed id during build. - referenceKey: string; - packageSource?: string; - // build only for tree-shaking unused export - exportNames: string[]; - renderedExports: string[]; -}; -let clientReferenceMetaMap: Record = {}; - -let serverResourcesMetaMap: Record = {}; - -const PKG_NAME = "@hiogawa/vite-rsc"; -const REACT_SERVER_DOM_NAME = `${PKG_NAME}/vendor/react-server-dom`; - -// dev-only wrapper virtual module of rollupOptions.input.index -const VIRTUAL_ENTRIES = { - browser: "virtual:vite-rsc/entry-browser", -}; - -const require = createRequire(import.meta.url); - -function resolvePackage(name: string) { - return pathToFileURL(require.resolve(name)).href; -} - -export type RscPluginOptions = { - /** - * shorthand for configuring `environments.(name).build.rollupOptions.input.index` - */ - entries?: Partial>; - - /** @deprecated use `serverHandler: false` */ - disableServerHandler?: boolean; - - /** @default { enviornmentName: "rsc", entryName: "index" } */ - serverHandler?: - | { - environmentName: string; - entryName: string; - } - | false; - - /** @default false */ - loadModuleDevProxy?: boolean; - - rscCssTransform?: false | { filter?: (id: string) => boolean }; - - ignoredPackageWarnings?: (string | RegExp)[]; - - /** - * This option allows customizing how client build copies assets from server build. - * By default, all assets are copied, but frameworks might want to establish some convention - * to tighten security based on this option. - */ - copyServerAssetsToClient?: (fileName: string) => boolean; - - defineEncryptionKey?: string; - - /** - * Allows enabling action closure encryption for debugging purpose. - * @default true - */ - enableActionEncryption?: boolean; - - /** Escape hatch for Waku's `allowServer` */ - keepUseCientProxy?: boolean; -}; - -export default function vitePluginRsc( - rscPluginOptions: RscPluginOptions = {}, -): Plugin[] { - return [ - { - name: "rsc", - async config(config, env) { - await esModuleLexer.init; - - // crawl packages with "react" in "peerDependencies" to bundle react deps on server - // see https://github.com/svitejs/vitefu/blob/d8d82fa121e3b2215ba437107093c77bde51b63b/src/index.js#L95-L101 - const result = await crawlFrameworkPkgs({ - root: process.cwd(), - isBuild: env.command === "build", - isFrameworkPkgByJson(pkgJson) { - if ([PKG_NAME, "react-dom"].includes(pkgJson.name)) { - return; - } - const deps = pkgJson["peerDependencies"]; - return deps && "react" in deps; - }, - }); - const noExternal = [ - "react", - "react-dom", - "server-only", - "client-only", - PKG_NAME, - ...result.ssr.noExternal.sort(), - ]; - - return { - appType: "custom", - define: { - "import.meta.env.__vite_rsc_build__": JSON.stringify( - env.command === "build", - ), - }, - environments: { - client: { - build: { - outDir: - config.environments?.client?.build?.outDir ?? "dist/client", - rollupOptions: { - input: rscPluginOptions.entries?.client && { - index: rscPluginOptions.entries.client, - }, - }, - }, - optimizeDeps: { - include: [ - "react-dom/client", - `${REACT_SERVER_DOM_NAME}/client.browser`, - ], - exclude: [PKG_NAME], - }, - }, - ssr: { - build: { - outDir: config.environments?.ssr?.build?.outDir ?? "dist/ssr", - rollupOptions: { - input: rscPluginOptions.entries?.ssr && { - index: rscPluginOptions.entries.ssr, - }, - }, - }, - resolve: { - noExternal, - }, - optimizeDeps: { - include: [ - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "react-dom/server.edge", - `${REACT_SERVER_DOM_NAME}/client.edge`, - ], - exclude: [PKG_NAME], - }, - }, - rsc: { - build: { - outDir: config.environments?.rsc?.build?.outDir ?? "dist/rsc", - emitAssets: true, - rollupOptions: { - input: rscPluginOptions.entries?.rsc && { - index: rscPluginOptions.entries.rsc, - }, - }, - }, - resolve: { - conditions: ["react-server", ...defaultServerConditions], - noExternal, - }, - optimizeDeps: { - include: [ - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - `${REACT_SERVER_DOM_NAME}/server.edge`, - `${REACT_SERVER_DOM_NAME}/client.edge`, - ], - exclude: [PKG_NAME], - }, - }, - }, - builder: { - sharedPlugins: true, - sharedConfigBuild: true, - async buildApp(builder) { - isScanBuild = true; - builder.environments.rsc!.config.build.write = false; - builder.environments.ssr!.config.build.write = false; - await builder.build(builder.environments.rsc!); - await builder.build(builder.environments.ssr!); - isScanBuild = false; - builder.environments.rsc!.config.build.write = true; - builder.environments.ssr!.config.build.write = true; - await builder.build(builder.environments.rsc!); - // sort for stable build - clientReferenceMetaMap = sortObject(clientReferenceMetaMap); - serverResourcesMetaMap = sortObject(serverResourcesMetaMap); - await builder.build(builder.environments.client!); - await builder.build(builder.environments.ssr!); - }, - }, - }; - }, - configResolved(config_) { - config = config_; - }, - configureServer(server_) { - server = server_; - (globalThis as any).__viteRscDevServer = server; - - if (rscPluginOptions.disableServerHandler) return; - if (rscPluginOptions.serverHandler === false) return; - const options = rscPluginOptions.serverHandler ?? { - environmentName: "rsc", - entryName: "index", - }; - const environment = server.environments[ - options.environmentName - ] as RunnableDevEnvironment; - const source = getEntrySource(environment.config, options.entryName); - - return () => { - server.middlewares.use(async (req, res, next) => { - try { - // resolve before `runner.import` to workaround https://github.com/vitejs/vite/issues/19975 - const resolved = - await environment.pluginContainer.resolveId(source); - assert( - resolved, - `[vite-rsc] failed to resolve server handler '${source}'`, - ); - const mod = await environment.runner.import(resolved.id); - createRequestListener(mod.default)(req, res); - } catch (e) { - next(e); - } - }); - }; - }, - async configurePreviewServer(server) { - if (rscPluginOptions.disableServerHandler) return; - if (rscPluginOptions.serverHandler === false) return; - const options = rscPluginOptions.serverHandler ?? { - environmentName: "rsc", - entryName: "index", - }; - - const entryFile = path.join( - config.environments[options.environmentName]!.build.outDir, - `${options.entryName}.js`, - ); - const entry = pathToFileURL(entryFile).href; - const mod = await import(/* @vite-ignore */ entry); - const handler = createRequestListener(mod.default); - - // disable compressions since it breaks html streaming - // https://github.com/vitejs/vite/blob/9f5c59f07aefb1756a37bcb1c0aff24d54288950/packages/vite/src/node/preview.ts#L178 - server.middlewares.use((req, _res, next) => { - delete req.headers["accept-encoding"]; - next(); - }); - - return () => { - server.middlewares.use(async (req, res, next) => { - try { - handler(req, res); - } catch (e) { - next(e); - } - }); - }; - }, - async hotUpdate(ctx) { - if (isCSSRequest(ctx.file)) { - if (this.environment.name === "client") { - // filter out `.css?direct` (injected by SSR) to avoid browser full reload - // when changing non-self accepting css such as `module.css`. - return ctx.modules.filter((m) => !m.id?.includes("?direct")); - } - } - - const ids = ctx.modules.map((mod) => mod.id).filter((v) => v !== null); - if (ids.length === 0) return; - - // TODO: what if shared component? - function isInsideClientBoundary(mods: EnvironmentModuleNode[]) { - const visited = new Set(); - function recurse(mod: EnvironmentModuleNode): boolean { - if (!mod.id) return false; - if (clientReferenceMetaMap[mod.id]) return true; - if (visited.has(mod.id)) return false; - visited.add(mod.id); - for (const importer of mod.importers) { - if (recurse(importer)) { - return true; - } - } - return false; - } - return mods.some((mod) => recurse(mod)); - } - - if (!isInsideClientBoundary(ctx.modules)) { - if (this.environment.name === "rsc") { - // server hmr - ctx.server.environments.client.hot.send({ - type: "custom", - event: "rsc:update", - data: { - file: ctx.file, - }, - }); - } - - if (this.environment.name === "client") { - // Server files can be included in client module graph, for example, - // when `addWatchFile` is used to track js files as style dependency (e.g. tailwind) - // In this case, reload all importers (for css hmr), and return empty modules to avoid full-reload. - const env = ctx.server.environments.rsc!; - const mod = env.moduleGraph.getModuleById(ctx.file); - if (mod) { - for (const clientMod of ctx.modules) { - for (const importer of clientMod.importers) { - if (importer.id && isCSSRequest(importer.id)) { - await this.environment.reloadModule(importer); - } - } - } - return []; - } - } - } - }, - }, - { - name: "rsc:patch-browser-raw-import", - transform: { - order: "post", - handler(code) { - if (code.includes("__vite_rsc_raw_import__")) { - // inject dynamic import last to avoid Vite adding `?import` query to client references - return code.replace("__vite_rsc_raw_import__", "import"); - } - }, - }, - }, - { - // backward compat: `loadSsrModule(name)` implemented as `loadModule("ssr", name)` - name: "rsc:load-ssr-module", - transform(code) { - if (code.includes("import.meta.viteRsc.loadSsrModule(")) { - return code.replaceAll( - `import.meta.viteRsc.loadSsrModule(`, - `import.meta.viteRsc.loadModule("ssr", `, - ); - } - }, - }, - { - // allow loading entry module in other environment by - // - (dev) rewriting to `server.environments[].runner.import()` - // - (build) rewriting to external `import("..//.js")` - name: "rsc:load-environment-module", - async transform(code) { - if (!code.includes("import.meta.viteRsc.loadModule")) return; - const s = new MagicString(code); - for (const match of code.matchAll( - /import\.meta\.viteRsc\.loadModule\(([\s\S]*?)\)/dg, - )) { - const argCode = match[1]!.trim(); - const [environmentName, entryName] = evalValue(`[${argCode}]`); - let replacement: string; - if ( - this.environment.mode === "dev" && - rscPluginOptions.loadModuleDevProxy - ) { - const origin = server.resolvedUrls?.local[0]; - assert(origin, "[vite-rsc] no server for loadModueleDevProxy"); - const endpoint = - origin + - "__vite_rsc_load_module_dev_proxy?" + - new URLSearchParams({ environmentName, entryName }); - replacement = `__vite_rsc_rpc.createRpcClient(${JSON.stringify({ endpoint })})`; - s.prepend( - `import * as __vite_rsc_rpc from "@hiogawa/vite-rsc/utils/rpc";`, - ); - } else if (this.environment.mode === "dev") { - const environment = server.environments[environmentName]!; - const source = getEntrySource(environment.config, entryName); - const resolved = - await environment.pluginContainer.resolveId(source); - assert(resolved, `[vite-rsc] failed to resolve entry '${source}'`); - replacement = - `globalThis.__viteRscDevServer.environments[${JSON.stringify(environmentName)}]` + - `.runner.import(${JSON.stringify(resolved.id)})`; - } else { - replacement = JSON.stringify( - `__vite_rsc_load_module:${this.environment.name}:${environmentName}:${entryName}`, - ); - } - const [start, end] = match.indices![0]!; - s.overwrite(start, end, replacement); - } - if (s.hasChanged()) { - return { - code: s.toString(), - map: s.generateMap({ hires: "boundary" }), - }; - } - }, - renderChunk(code, chunk) { - if (!code.includes("__vite_rsc_load_module")) return; - const s = new MagicString(code); - for (const match of code.matchAll( - /['"]__vite_rsc_load_module:(\w+):(\w+):(\w+)['"]/dg, - )) { - const [fromEnv, toEnv, entryName] = match.slice(1); - const importPath = normalizeRelativePath( - path.relative( - path.join( - config.environments[fromEnv!]!.build.outDir, - chunk.fileName, - "..", - ), - path.join( - config.environments[toEnv!]!.build.outDir, - // TODO: this breaks when custom entyFileNames - `${entryName}.js`, - ), - ), - ); - const replacement = `(import(${JSON.stringify(importPath)}))`; - const [start, end] = match.indices![0]!; - s.overwrite(start, end, replacement); - } - if (s.hasChanged()) { - return { - code: s.toString(), - map: s.generateMap({ hires: "boundary" }), - }; - } - }, - }, - { - name: "vite-rsc-load-module-dev-proxy", - apply: () => !!rscPluginOptions.loadModuleDevProxy, - configureServer(server) { - async function createHandler(url: URL) { - const { environmentName, entryName } = Object.fromEntries( - url.searchParams, - ); - assert(environmentName); - assert(entryName); - const environment = server.environments[ - environmentName - ] as RunnableDevEnvironment; - const source = getEntrySource(environment.config, entryName); - const resolvedEntry = - await environment.pluginContainer.resolveId(source); - assert( - resolvedEntry, - `[vite-rsc] failed to resolve entry '${source}'`, - ); - const runnerProxy = new Proxy( - {}, - { - get(_target, p, _receiver) { - if (typeof p !== "string" || p === "then") { - return; - } - return async (...args: any[]) => { - const mod = await environment.runner.import(resolvedEntry.id); - return (mod as any)[p](...args); - }; - }, - }, - ); - return createRpcServer(runnerProxy); - } - - server.middlewares.use(async (req, res, next) => { - const url = new URL(req.url ?? "/", `http://localhost`); - if (url.pathname === "/__vite_rsc_load_module_dev_proxy") { - try { - const handler = await createHandler(url); - createRequestListener(handler)(req, res); - } catch (e) { - next(e); - } - return; - } - next(); - }); - }, - }, - { - name: "rsc:virtual:vite-rsc/assets-manifest", - resolveId(source) { - if (source === "virtual:vite-rsc/assets-manifest") { - if (this.environment.mode === "build") { - return { id: source, external: true }; - } - return `\0` + source; - } - }, - load(id) { - if (id === "\0virtual:vite-rsc/assets-manifest") { - assert(this.environment.name !== "client"); - assert(this.environment.mode === "dev"); - const entryUrl = assetsURL("@id/__x00__" + VIRTUAL_ENTRIES.browser); - const manifest: AssetsManifest = { - bootstrapScriptContent: `import(${JSON.stringify(entryUrl)})`, - clientReferenceDeps: {}, - }; - return `export default ${JSON.stringify(manifest, null, 2)}`; - } - }, - // client build - generateBundle(_options, bundle) { - // copy assets from rsc build to client build - if (this.environment.name === "rsc") { - rscBundle = bundle; - } - - if (this.environment.name === "client") { - const filterAssets = - rscPluginOptions.copyServerAssetsToClient ?? (() => true); - const rscBuildOptions = config.environments.rsc!.build; - const rscViteManifest = - typeof rscBuildOptions.manifest === "string" - ? rscBuildOptions.manifest - : rscBuildOptions.manifest && ".vite/manifest.json"; - for (const asset of Object.values(rscBundle)) { - if (asset.fileName === rscViteManifest) continue; - if (asset.type === "asset" && filterAssets(asset.fileName)) { - this.emitFile({ - type: "asset", - fileName: asset.fileName, - source: asset.source, - }); - } - } - - const serverResources: Record = {}; - const rscAssetDeps = collectAssetDeps(rscBundle); - for (const [id, meta] of Object.entries(serverResourcesMetaMap)) { - serverResources[meta.key] = assetsURLOfDeps({ - js: [], - css: rscAssetDeps[id]?.deps.css ?? [], - }); - } - - const assetDeps = collectAssetDeps(bundle); - const entry = Object.values(assetDeps).find( - (v) => v.chunk.name === "index", - ); - assert(entry); - const entryUrl = assetsURL(entry.chunk.fileName); - const clientReferenceDeps: Record = {}; - for (const [id, meta] of Object.entries(clientReferenceMetaMap)) { - const deps: AssetDeps = assetDeps[id]?.deps ?? { js: [], css: [] }; - clientReferenceDeps[meta.referenceKey] = assetsURLOfDeps( - mergeAssetDeps(deps, entry.deps), - ); - } - buildAssetsManifest = { - bootstrapScriptContent: `import(${JSON.stringify(entryUrl)})`, - clientReferenceDeps, - serverResources, - }; - } - }, - // non-client builds can load assets manifest as external - renderChunk(code, chunk) { - if (code.includes("virtual:vite-rsc/assets-manifest")) { - assert(this.environment.name !== "client"); - const replacement = normalizeRelativePath( - path.relative( - path.join(chunk.fileName, ".."), - BUILD_ASSETS_MANIFEST_NAME, - ), - ); - code = code.replaceAll( - "virtual:vite-rsc/assets-manifest", - () => replacement, - ); - return { code }; - } - return; - }, - writeBundle() { - if (this.environment.name === "ssr") { - // output client manifest to non-client build directly. - // this makes server build to be self-contained and deploy-able for cloudflare. - const assetsManifestCode = `export default ${JSON.stringify(buildAssetsManifest, null, 2)}`; - for (const name of ["ssr", "rsc"]) { - const manifestPath = path.join( - config.environments[name]!.build.outDir, - BUILD_ASSETS_MANIFEST_NAME, - ); - fs.writeFileSync(manifestPath, assetsManifestCode); - } - } - }, - }, - createVirtualPlugin("vite-rsc/bootstrap-script-content", function () { - assert(this.environment.name !== "client"); - return `\ -import assetsManifest from "virtual:vite-rsc/assets-manifest"; -export default assetsManifest.bootstrapScriptContent; -`; - }), - { - name: "rsc:bootstrap-script-content", - async transform(code) { - if ( - !code.includes("loadBootstrapScriptContent") || - !/import\s*\.\s*meta\s*\.\s*viteRsc\s*\.\s*loadBootstrapScriptContent/.test( - code, - ) - ) { - return; - } - - assert(this.environment.name !== "client"); - const output = new MagicString(code); - - for (const match of code.matchAll( - /import\s*\.\s*meta\s*\.\s*viteRsc\s*\.\s*loadBootstrapScriptContent\(([\s\S]*?)\)/dg, - )) { - const argCode = match[1]!.trim(); - const entryName = evalValue(argCode); - assert( - entryName, - `[vite-rsc] expected 'loadBootstrapScriptContent("index")' but got ${argCode}`, - ); - let replacement: string = `Promise.resolve(__vite_rsc_assets_manifest.bootstrapScriptContent)`; - const [start, end] = match.indices![0]!; - output.overwrite(start, end, replacement); - } - if (output.hasChanged()) { - if (!code.includes("__vite_rsc_assets_manifest")) { - output.prepend( - `import __vite_rsc_assets_manifest from "virtual:vite-rsc/assets-manifest";`, - ); - } - return { - code: output.toString(), - map: output.generateMap({ hires: "boundary" }), - }; - } - }, - }, - createVirtualPlugin( - VIRTUAL_ENTRIES.browser.slice("virtual:".length), - async function () { - assert(this.environment.mode === "dev"); - let code = ""; - // enable hmr only when react plugin is available - const resolved = await this.resolve("/@react-refresh"); - if (resolved) { - code += ` -import RefreshRuntime from "/@react-refresh"; -RefreshRuntime.injectIntoGlobalHook(window); -window.$RefreshReg$ = () => {}; -window.$RefreshSig$ = () => (type) => type; -window.__vite_plugin_react_preamble_installed__ = true; -`; - } - const source = getEntrySource(this.environment.config, "index"); - const resolvedEntry = await this.resolve(source); - assert(resolvedEntry, `[vite-rsc] failed to resolve entry '${source}'`); - code += `await import(${JSON.stringify(resolvedEntry.id)});`; - // TODO - // should remove only the ones we injected during ssr, which are duplicated by browser imports for HMR. - // technically this doesn't have to wait for "vite:beforeUpdate" and should do it right after browser css import. - // TODO: there migth be a clever way to let Vite deduplicate itself. - // cf. https://github.com/withastro/astro/blob/acb9b302f56e38833a1ab01147f7fde0bf967889/packages/astro/src/vite-plugin-astro-server/pipeline.ts#L133-L135 - code += ` -const ssrCss = document.querySelectorAll("link[rel='stylesheet']"); -import.meta.hot.on("vite:beforeUpdate", () => ssrCss.forEach(node => node.remove())); -`; - return code; - }, - ), - { - // make `AsyncLocalStorage` available globally for React request context on edge build (e.g. React.cache, ssr preload) - // https://github.com/facebook/react/blob/f14d7f0d2597ea25da12bcf97772e8803f2a394c/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge.js#L16-L19 - name: "rsc:inject-async-local-storage", - async configureServer() { - const __viteRscAyncHooks = await import("node:async_hooks"); - (globalThis as any).AsyncLocalStorage = - __viteRscAyncHooks.AsyncLocalStorage; - }, - banner(chunk) { - if ( - (this.environment.name === "ssr" || - this.environment.name === "rsc") && - this.environment.mode === "build" && - chunk.isEntry - ) { - return `\ -import * as __viteRscAyncHooks from "node:async_hooks"; -globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage; -`; - } - return ""; - }, - }, - ...vitePluginRscCore(), - ...vitePluginUseClient(rscPluginOptions), - ...vitePluginUseServer(rscPluginOptions), - ...vitePluginDefineEncryptionKey(rscPluginOptions), - ...vitePluginFindSourceMapURL(), - ...vitePluginRscCss({ rscCssTransform: rscPluginOptions.rscCssTransform }), - scanBuildStripPlugin(), - ]; -} - -function scanBuildStripPlugin(): Plugin { - return { - name: "rsc:scan-strip", - apply: "build", - enforce: "post", - transform(code, _id, _options) { - if (!isScanBuild) return; - // During server scan, we strip all code but imports to only discover client/server references. - const [imports] = esModuleLexer.parse(code); - const output = imports - .map((e) => e.n && `import ${JSON.stringify(e.n)};\n`) - .filter(Boolean) - .join(""); - return { code: output, map: { mappings: "" } }; - }, - }; -} - -function normalizeRelativePath(s: string) { - s = normalizePath(s); - return s[0] === "." ? s : "./" + s; -} - -function getEntrySource( - config: Pick, - name: string = "index", -) { - const input = config.build.rollupOptions.input; - assert(input); - assert( - typeof input === "object" && - !Array.isArray(input) && - name in input && - typeof input[name] === "string", - ); - return input[name]; -} - -function hashString(v: string) { - return createHash("sha256").update(v).digest().toString("hex").slice(0, 12); -} - -function normalizeReferenceId(id: string, name: "client" | "rsc") { - if (!server) { - return hashString(path.relative(config.root, id)); - } - - // align with how Vite import analysis would rewrite id - // to avoid double modules on browser and ssr. - const environment = server.environments[name]!; - return normalizeViteImportAnalysisUrl(environment, id); -} - -function vitePluginUseClient( - useClientPluginOptions: Pick< - RscPluginOptions, - "ignoredPackageWarnings" | "keepUseCientProxy" - >, -): Plugin[] { - const packageSources = new Map(); - - // https://github.com/vitejs/vite/blob/4bcf45863b5f46aa2b41f261283d08f12d3e8675/packages/vite/src/node/utils.ts#L175 - const bareImportRE = /^(?![a-zA-Z]:)[\w@](?!.*:\/\/)/; - - return [ - { - name: "rsc:use-client", - async transform(code, id) { - if (this.environment.name !== "rsc") return; - if (!code.includes("use client")) return; - - const ast = await parseAstAsync(code); - if (!hasDirective(ast.body, "use client")) return; - - let importId: string; - let referenceKey: string; - const packageSource = packageSources.get(id); - if (!packageSource && id.includes("?v=")) { - assert(this.environment.mode === "dev"); - // If non package source `?v=` reached here, this is a client boundary - // created by a package imported on server environment, which breaks the - // expectation on dependency optimizer on browser. Directly copying over - // "?v=" from client optimizer in client reference can make a hashed - // module stale, so we use another virtual module wrapper to delay such process. - // TODO: suggest `optimizeDeps.exclude` and skip warning if that's already the case. - const ignored = useClientPluginOptions.ignoredPackageWarnings?.some( - (pattern) => - pattern instanceof RegExp - ? pattern.test(id) - : id.includes(`/node_modules/${pattern}/`), - ); - if (!ignored) { - this.warn( - `[vite-rsc] detected an internal client boundary created by a package imported on rsc environment`, - ); - } - importId = `/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/${encodeURIComponent(id.split("?v=")[0]!)}`; - referenceKey = importId; - } else if (packageSource) { - if (this.environment.mode === "dev") { - importId = `/@id/__x00__virtual:vite-rsc/client-package-proxy/${packageSource}`; - referenceKey = importId; - } else { - importId = packageSource; - referenceKey = hashString(packageSource); - } - } else { - if (this.environment.mode === "dev") { - importId = normalizeViteImportAnalysisUrl( - server.environments.client, - id, - ); - referenceKey = importId; - } else { - importId = id; - referenceKey = hashString( - normalizePath(path.relative(config.root, id)), - ); - } - } - - const transformDirectiveProxyExport_ = withRollupError( - this, - transformDirectiveProxyExport, - ); - const result = transformDirectiveProxyExport_(ast, { - directive: "use client", - code, - keep: !!useClientPluginOptions.keepUseCientProxy, - runtime: (name, meta) => { - let proxyValue = - `() => { throw new Error("Unexpectedly client reference export '" + ` + - JSON.stringify(name) + - ` + "' is called on server") }`; - if (meta?.value) { - proxyValue = `(${meta.value})`; - } - return ( - `$$ReactServer.registerClientReference(` + - ` ${proxyValue},` + - ` ${JSON.stringify(referenceKey)},` + - ` ${JSON.stringify(name)})` - ); - }, - }); - if (!result) return; - const { output, exportNames } = result; - clientReferenceMetaMap[id] = { - importId, - referenceKey, - packageSource, - exportNames, - renderedExports: [], - }; - const importSource = resolvePackage(`${PKG_NAME}/react/rsc`); - output.prepend(`import * as $$ReactServer from "${importSource}";\n`); - return { code: output.toString(), map: { mappings: "" } }; - }, - }, - createVirtualPlugin("vite-rsc/client-references", function () { - if (this.environment.mode === "dev") { - return { code: `export default {}`, map: null }; - } - let code = ""; - for (const meta of Object.values(clientReferenceMetaMap)) { - // vite/rollup can apply tree-shaking to dynamic import of this form - const key = JSON.stringify(meta.referenceKey); - const id = JSON.stringify(meta.importId); - const exports = meta.renderedExports - .map((name) => (name === "default" ? "default: _default" : name)) - .sort(); - code += ` - ${key}: async () => { - const {${exports}} = await import(${id}); - return {${exports}}; - }, - `; - } - code = `export default {${code}};\n`; - return { code, map: null }; - }), - { - name: "rsc:virtual-client-in-server-package", - async load(id) { - if ( - id.startsWith("\0virtual:vite-rsc/client-in-server-package-proxy/") - ) { - assert.equal(this.environment.mode, "dev"); - assert.notEqual(this.environment.name, "rsc"); - id = decodeURIComponent( - id.slice( - "\0virtual:vite-rsc/client-in-server-package-proxy/".length, - ), - ); - // TODO: avoid `export default undefined` - return ` - export * from ${JSON.stringify(id)}; - import * as __all__ from ${JSON.stringify(id)}; - export default __all__.default; - `; - } - }, - }, - { - name: "rsc:virtual-client-package", - resolveId: { - order: "pre", - async handler(source, importer, options) { - if (this.environment.name === "rsc" && bareImportRE.test(source)) { - const resolved = await this.resolve(source, importer, options); - if (resolved && resolved.id.includes("/node_modules/")) { - packageSources.set(resolved.id, source); - return resolved; - } - } - }, - }, - async load(id) { - if (id.startsWith("\0virtual:vite-rsc/client-package-proxy/")) { - assert(this.environment.mode === "dev"); - const source = id.slice( - "\0virtual:vite-rsc/client-package-proxy/".length, - ); - const meta = Object.values(clientReferenceMetaMap).find( - (v) => v.packageSource === source, - )!; - const exportNames = meta.exportNames; - return `export {${exportNames.join(",")}} from ${JSON.stringify(source)};\n`; - } - }, - generateBundle(_options, bundle) { - if (this.environment.name !== "rsc") return; - - // track used exports of client references in rsc build - // to tree shake unused exports in browser and ssr build - for (const chunk of Object.values(bundle)) { - if (chunk.type === "chunk") { - for (const [id, mod] of Object.entries(chunk.modules)) { - const meta = clientReferenceMetaMap[id]; - if (meta) { - meta.renderedExports = mod.renderedExports; - } - } - } - } - }, - }, - ]; -} - -function vitePluginDefineEncryptionKey( - useServerPluginOptions: Pick, -): Plugin[] { - let defineEncryptionKey: string; - let emitEncryptionKey = false; - const KEY_PLACEHOLDER = "__vite_rsc_define_encryption_key"; - const KEY_FILE = "__vite_rsc_encryption_key.js"; - - return [ - { - name: "rsc:encryption-key", - async configEnvironment(name, _config, env) { - if (name === "rsc" && !env.isPreview) { - defineEncryptionKey = - useServerPluginOptions.defineEncryptionKey ?? - JSON.stringify(toBase64(await generateEncryptionKey())); - } - }, - resolveId(source) { - if (source === "virtual:vite-rsc/encryption-key") { - // encryption logic can be tree-shaken if action bind is not used. - return { id: "\0" + source, moduleSideEffects: false }; - } - }, - load(id) { - if (id === "\0virtual:vite-rsc/encryption-key") { - if (this.environment.mode === "build") { - // during build, load key from an external file to make chunks stable. - return `export default () => ${KEY_PLACEHOLDER}`; - } - return `export default () => (${defineEncryptionKey})`; - } - }, - renderChunk(code, chunk) { - if (code.includes(KEY_PLACEHOLDER)) { - assert.equal(this.environment.name, "rsc"); - emitEncryptionKey = true; - const normalizedPath = normalizeRelativePath( - path.relative(path.join(chunk.fileName, ".."), KEY_FILE), - ); - const replacement = `import(${JSON.stringify(normalizedPath)}).then(__m => __m.default)`; - code = code.replaceAll(KEY_PLACEHOLDER, () => replacement); - return { code }; - } - }, - writeBundle() { - if (this.environment.name === "rsc" && emitEncryptionKey) { - fs.writeFileSync( - path.join(this.environment.config.build.outDir, KEY_FILE), - `export default ${defineEncryptionKey};\n`, - ); - } - }, - }, - ]; -} - -function vitePluginUseServer( - useServerPluginOptions: Pick< - RscPluginOptions, - "ignoredPackageWarnings" | "enableActionEncryption" - >, -): Plugin[] { - return [ - { - name: "rsc:use-server", - async transform(code, id) { - if (!code.includes("use server")) return; - const ast = await parseAstAsync(code); - - let normalizedId_: string | undefined; - const getNormalizedId = () => { - if (!normalizedId_) { - if (id.includes("?v=")) { - assert(this.environment.mode === "dev"); - const ignored = - useServerPluginOptions.ignoredPackageWarnings?.some( - (pattern) => - pattern instanceof RegExp - ? pattern.test(id) - : id.includes(`/node_modules/${pattern}/`), - ); - if (!ignored) { - this.warn( - `[vite-rsc] detected an internal server function created by a package imported on ${this.environment.name} environment`, - ); - } - // module runner has additional resolution step and it's not strict about - // module identity of `import(id)` like browser, so we simply strip it off. - id = id.split("?v=")[0]!; - } - normalizedId_ = normalizeReferenceId(id, "rsc"); - } - return normalizedId_; - }; - - if (this.environment.name === "rsc") { - const transformServerActionServer_ = withRollupError( - this, - transformServerActionServer, - ); - const enableEncryption = - useServerPluginOptions.enableActionEncryption ?? true; - const { output } = transformServerActionServer_(code, ast, { - runtime: (value, name) => - `$$ReactServer.registerServerReference(${value}, ${JSON.stringify(getNormalizedId())}, ${JSON.stringify(name)})`, - rejectNonAsyncFunction: true, - encode: enableEncryption - ? (value) => `$$ReactServer.encryptActionBoundArgs(${value})` - : undefined, - decode: enableEncryption - ? (value) => - `await $$ReactServer.decryptActionBoundArgs(${value})` - : undefined, - }); - if (!output.hasChanged()) return; - serverReferences[getNormalizedId()] = id; - const importSource = resolvePackage(`${PKG_NAME}/rsc`); - output.prepend(`import * as $$ReactServer from "${importSource}";\n`); - return { - code: output.toString(), - map: output.generateMap({ hires: "boundary" }), - }; - } else { - if (!hasDirective(ast.body, "use server")) return; - const transformDirectiveProxyExport_ = withRollupError( - this, - transformDirectiveProxyExport, - ); - const result = transformDirectiveProxyExport_(ast, { - code, - runtime: (name) => - `$$ReactClient.createServerReference(` + - `${JSON.stringify(getNormalizedId() + "#" + name)},` + - `$$ReactClient.callServer, ` + - `undefined, ` + - (this.environment.mode === "dev" - ? `$$ReactClient.findSourceMapURL,` - : "undefined,") + - `${JSON.stringify(name)})`, - directive: "use server", - rejectNonAsyncFunction: true, - }); - const output = result?.output; - if (!output?.hasChanged()) return; - serverReferences[getNormalizedId()] = id; - const name = this.environment.name === "client" ? "browser" : "ssr"; - const importSource = resolvePackage(`${PKG_NAME}/react/${name}`); - output.prepend(`import * as $$ReactClient from "${importSource}";\n`); - return { - code: output.toString(), - map: output.generateMap({ hires: "boundary" }), - }; - } - }, - }, - createVirtualPlugin("vite-rsc/server-references", function () { - if (this.environment.mode === "dev") { - return { code: `export {}`, map: null }; - } - const code = generateDynamicImportCode(serverReferences); - return { code, map: null }; - }), - ]; -} - -// Rethrow transform error through `this.error` with `error.pos` which is injected by `@hiogawa/transforms` -function withRollupError any>( - ctx: Rollup.TransformPluginContext, - f: F, -): F { - function processError(e: any): never { - if (e && typeof e === "object" && typeof e.pos === "number") { - return ctx.error(e, e.pos); - } - throw e; - } - return function (this: any, ...args: any[]) { - try { - const result = f.apply(this, args); - if (result instanceof Promise) { - return result.catch((e: any) => processError(e)); - } - return result; - } catch (e: any) { - processError(e); - } - } as F; -} - -function createVirtualPlugin(name: string, load: Plugin["load"]) { - name = "virtual:" + name; - return { - name: `rsc:virtual-${name}`, - resolveId(source, _importer, _options) { - return source === name ? "\0" + name : undefined; - }, - load(id, options) { - if (id === "\0" + name) { - return (load as Function).apply(this, [id, options]); - } - }, - } satisfies Plugin; -} - -function generateDynamicImportCode(map: Record) { - let code = Object.entries(map) - .map( - ([key, id]) => - `${JSON.stringify(key)}: () => import(${JSON.stringify(id)}),`, - ) - .join("\n"); - return `export default {${code}};\n`; -} - -// // https://github.com/vitejs/vite/blob/2a7473cfed96237711cda9f736465c84d442ddef/packages/vite/src/node/plugins/importAnalysisBuild.ts#L222-L230 -function assetsURL(url: string) { - return config.base + url; -} - -function assetsURLOfDeps(deps: AssetDeps) { - return { - js: deps.js.map((href) => assetsURL(href)), - css: deps.css.map((href) => assetsURL(href)), - }; -} - -// -// collect client reference dependency chunk for modulepreload -// - -export type AssetsManifest = { - bootstrapScriptContent: string; - clientReferenceDeps: Record; - serverResources?: Record; -}; - -export type AssetDeps = { - js: string[]; - css: string[]; -}; - -function mergeAssetDeps(a: AssetDeps, b: AssetDeps): AssetDeps { - return { - js: [...new Set([...a.js, ...b.js])], - css: [...new Set([...a.css, ...b.css])], - }; -} - -function collectAssetDeps(bundle: Rollup.OutputBundle) { - const chunkToDeps = new Map(); - for (const chunk of Object.values(bundle)) { - if (chunk.type === "chunk") { - chunkToDeps.set(chunk, collectAssetDepsInner(chunk.fileName, bundle)); - } - } - const idToDeps: Record< - string, - { chunk: Rollup.OutputChunk; deps: AssetDeps } - > = {}; - for (const [chunk, deps] of chunkToDeps.entries()) { - for (const id of chunk.moduleIds) { - idToDeps[id] = { chunk, deps }; - } - } - return idToDeps; -} - -function collectAssetDepsInner( - fileName: string, - bundle: Rollup.OutputBundle, -): AssetDeps { - const visited = new Set(); - const css: string[] = []; - - function recurse(k: string) { - if (visited.has(k)) return; - visited.add(k); - const v = bundle[k]; - assert(v, `Not found '${k}' in the bundle`); - if (v.type === "chunk") { - css.push(...(v.viteMetadata?.importedCss ?? [])); - for (const k2 of v.imports) { - // server external imports is not in bundle - if (k2 in bundle) { - recurse(k2); - } - } - } - } - - recurse(fileName); - return { - js: [...visited], - css: [...new Set(css)], - }; -} - -// -// support findSourceMapURL -// https://github.com/facebook/react/pull/29708 -// https://github.com/facebook/react/pull/30741 -// - -export function vitePluginFindSourceMapURL(): Plugin[] { - return [ - { - name: "rsc:findSourceMapURL", - apply: "serve", - configureServer(server) { - server.middlewares.use(async (req, res, next) => { - const url = new URL(req.url!, `http://localhost`); - if (url.pathname === "/__vite_rsc_findSourceMapURL") { - let filename = url.searchParams.get("filename")!; - let environmentName = url.searchParams.get("environmentName")!; - try { - const map = await findSourceMapURL( - server, - filename, - environmentName, - ); - res.setHeader("content-type", "application/json"); - if (!map) res.statusCode = 404; - res.end(JSON.stringify(map ?? {})); - } catch (e) { - next(e); - } - return; - } - next(); - }); - }, - }, - ]; -} - -export async function findSourceMapURL( - server: ViteDevServer, - filename: string, - environmentName: string, -): Promise { - // this is likely server external (i.e. outside of Vite processing) - if (filename.startsWith("file://")) { - filename = fileURLToPath(filename); - if (fs.existsSync(filename)) { - // line-by-line identity source map - const content = fs.readFileSync(filename, "utf-8"); - return { - version: 3, - sources: [filename], - sourcesContent: [content], - mappings: "AAAA" + ";AACA".repeat(content.split("\n").length), - }; - } - return; - } - - // server component stack, replace log, `registerServerReference`, etc... - let mod: EnvironmentModuleNode | undefined; - let map: - | NonNullable["map"] - | undefined; - if (environmentName === "Server") { - mod = server.environments.rsc!.moduleGraph.getModuleById(filename); - // React extracts stacktrace via resetting `prepareStackTrace` on the server - // and let browser devtools handle the mapping. - // https://github.com/facebook/react/blob/4a36d3eab7d9bbbfae62699989aa95e5a0297c16/packages/react-server/src/ReactFlightStackConfigV8.js#L15-L20 - // This means it has additional +2 line offset due to Vite's module runner - // function wrapper. We need to correct it just like Vite module runner. - // https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/shared/utils.ts#L58-L69 - // https://github.com/vitejs/vite/blob/d94e7b25564abb81ab7b921d4cd44d0f0d22fec4/packages/vite/src/node/ssr/fetchModule.ts#L142-L146 - map = mod?.transformResult?.map; - if (map && map.mappings) { - map = { ...map, mappings: (";;" + map.mappings) as any }; - } - } - - const base = server.config.base.slice(0, -1); - - // `createServerReference(... findSourceMapURL ...)` called on browser - if (environmentName === "Client") { - try { - const url = new URL(filename).pathname.slice(base.length); - mod = server.environments.client.moduleGraph.urlToModuleMap.get(url); - map = mod?.transformResult?.map; - } catch (e) {} - } - - if (mod && map) { - // fix sources to match Vite's module url on browser - return { ...map, sources: [base + mod.url] }; - } -} - -// -// css support -// - -export function vitePluginRscCss( - rscCssOptions?: Pick, -): Plugin[] { - function collectCss(environment: DevEnvironment, entryId: string) { - const visited = new Set(); - const cssIds = new Set(); - const visitedFiles = new Set(); - - function recurse(id: string) { - if (visited.has(id)) { - return; - } - visited.add(id); - const mod = environment.moduleGraph.getModuleById(id); - if (mod?.file) { - visitedFiles.add(mod.file); - } - for (const next of mod?.importedModules ?? []) { - if (next.id) { - if (isCSSRequest(next.id)) { - cssIds.add(next.id); - } else { - recurse(next.id); - } - } - } - } - - recurse(entryId); - - // this doesn't include ?t= query so that RSC won't keep adding styles. - const hrefs = [...cssIds].map((id) => - normalizeViteImportAnalysisUrl(environment, id), - ); - return { ids: [...cssIds], hrefs, visitedFiles: [...visitedFiles] }; - } - - function getRscCssTransformFilter({ - id, - code, - }: { id: string; code: string }): false | TransformWrapExportFilter { - const { query } = parseIdQuery(id); - if ("vite-rsc-css-export" in query) { - const value = query["vite-rsc-css-export"]; - if (value) { - const names = value.split(","); - return (name: string) => names.includes(name); - } - return (name: string) => /^[A-Z]/.test(name); - } - - const options = rscCssOptions?.rscCssTransform; - if (options === false) return false; - if (options?.filter && !options.filter(id)) return false; - if (id.includes("/node_modules/") || !/\.[tj]sx$/.test(id)) return false; - - // skip transform if no css imports - const result = esModuleLexer.parse(code); - if (!result[0].some((i) => i.t === 1 && i.n && isCSSRequest(i.n))) { - return false; - } - // transform only function exports with capital names, e.g. - // export default function Page() {} - // export function Page() {} - // export const Page = () => {} - return (_name: string, meta) => - !!( - (meta.isFunction && meta.declName && /^[A-Z]/.test(meta.declName)) || - (meta.defaultExportIdentifierName && - /^[A-Z]/.test(meta.defaultExportIdentifierName)) - ); - } - - return [ - { - name: "rsc:rsc-css-export-transform", - async transform(code, id) { - if (this.environment.name !== "rsc") return; - const filter = getRscCssTransformFilter({ id, code }); - if (!filter) return; - const ast = await parseAstAsync(code); - const result = await transformRscCssExport({ - ast, - code, - filter, - }); - if (result) { - return { - code: result.output.toString(), - map: result.output.generateMap({ hires: "boundary" }), - }; - } - }, - }, - { - name: "rsc:css/dev-ssr-virtual", - resolveId(source) { - if (source.startsWith("virtual:vite-rsc/css/dev-ssr/")) { - return "\0" + source; - } - }, - async load(id) { - if (id.startsWith("\0virtual:vite-rsc/css/dev-ssr/")) { - id = id.slice("\0virtual:vite-rsc/css/dev-ssr/".length); - const mod = - await server.environments.ssr.moduleGraph.getModuleByUrl(id); - if (!mod?.id || !mod?.file) { - return `export default []`; - } - const result = collectCss(server.environments.ssr, mod.id); - // invalidate virtual module on js file changes to reflect added/deleted css import - for (const file of [mod.file, ...result.visitedFiles]) { - this.addWatchFile(file); - } - const hrefs = result.hrefs.map((href) => assetsURL(href.slice(1))); - return `export default ${JSON.stringify(hrefs)}`; - } - }, - }, - { - name: "rsc:importer-resources", - async transform(code, id) { - if (!code.includes("import.meta.viteRsc.loadCss")) return; - - assert(this.environment.name === "rsc"); - const output = new MagicString(code); - - for (const match of code.matchAll( - /import\.meta\.viteRsc\.loadCss\(([\s\S]*?)\)/dg, - )) { - const [start, end] = match.indices![0]!; - const argCode = match[1]!.trim(); - let importer = id; - if (argCode) { - const argValue = evalValue(argCode); - const resolved = await this.resolve(argValue, id); - if (resolved) { - importer = resolved.id; - } else { - this.warn( - `[vite-rsc] failed to transform 'import.meta.viteRsc.loadCss(${argCode})'`, - ); - output.update(start, end, `null`); - continue; - } - } - const importId = `virtual:vite-rsc/importer-resources?importer=${encodeURIComponent(importer)}`; - - // use dynamic import during dev to delay crawling and discover css correctly. - let replacement: string; - if (this.environment.mode === "dev") { - replacement = `__vite_rsc_react__.createElement(async () => { - const __m = await import(${JSON.stringify(importId)}); - return __vite_rsc_react__.createElement(__m.Resources); - })`; - } else { - const hash = hashString(importId); - if (!code.includes(`__vite_rsc_importer_resources_${hash}`)) { - output.prepend( - `import * as __vite_rsc_importer_resources_${hash} from ${JSON.stringify(importId)};`, - ); - } - replacement = `__vite_rsc_react__.createElement(__vite_rsc_importer_resources_${hash}.Resources)`; - } - output.update(start, end, replacement); - } - - if (output.hasChanged()) { - if (!code.includes("__vite_rsc_react__")) { - output.prepend(`import __vite_rsc_react__ from "react";`); - } - return { - code: output.toString(), - map: output.generateMap({ hires: "boundary" }), - }; - } - }, - resolveId(source) { - if ( - source.startsWith("virtual:vite-rsc/importer-resources?importer=") - ) { - assert(this.environment.name === "rsc"); - return "\0" + source; - } - }, - load(id) { - if (id.startsWith("\0virtual:vite-rsc/importer-resources?importer=")) { - const importer = decodeURIComponent( - parseIdQuery(id).query["importer"]!, - ); - if (this.environment.mode === "dev") { - const result = collectCss(server.environments.rsc!, importer); - const cssHrefs = result.hrefs.map((href) => href.slice(1)); - const jsHrefs = [ - "@id/__x00__virtual:vite-rsc/importer-resources-browser?importer=" + - encodeURIComponent(importer), - ]; - const deps = assetsURLOfDeps({ css: cssHrefs, js: jsHrefs }); - return generateResourcesCode(JSON.stringify(deps, null, 2)); - } else { - const key = normalizePath(path.relative(config.root, importer)); - serverResourcesMetaMap[importer] = { key }; - return ` - import __vite_rsc_assets_manifest__ from "virtual:vite-rsc/assets-manifest"; - ${generateResourcesCode(`__vite_rsc_assets_manifest__.serverResources[${JSON.stringify(key)}]`)} - `; - } - } - if ( - id.startsWith( - "\0virtual:vite-rsc/importer-resources-browser?importer=", - ) - ) { - assert(this.environment.name === "client"); - assert(this.environment.mode === "dev"); - const importer = decodeURIComponent( - parseIdQuery(id).query["importer"]!, - ); - const result = collectCss(server.environments.rsc!, importer); - let code = result.ids - .map((id) => id.replace(/^\0/, "")) - .map((id) => `import ${JSON.stringify(id)};\n`) - .join(""); - // ensure hmr boundary at this virtual since otherwise non-self accepting css - // (e.g. css module) causes full reload - code += `if (import.meta.hot) { import.meta.hot.accept() }\n`; - return code; - } - }, - hotUpdate(ctx) { - if (this.environment.name === "rsc") { - const mods = collectModuleDependents(ctx.modules); - for (const mod of mods) { - if (mod.id) { - const importer = encodeURIComponent(mod.id); - invalidteModuleById( - server.environments.rsc!, - `\0virtual:vite-rsc/importer-resources?importer=${importer}`, - ); - invalidteModuleById( - server.environments.client, - `\0virtual:vite-rsc/importer-resources-browser?importer=${importer}`, - ); - } - } - } - }, - }, - ]; -} - -function invalidteModuleById(environment: DevEnvironment, id: string) { - const mod = environment.moduleGraph.getModuleById(id); - if (mod) { - environment.moduleGraph.invalidateModule(mod); - } - return mod; -} - -function collectModuleDependents(mods: EnvironmentModuleNode[]) { - const visited = new Set(); - function recurse(mod: EnvironmentModuleNode) { - if (visited.has(mod)) return; - visited.add(mod); - for (const importer of mod.importers) { - recurse(importer); - } - } - for (const mod of mods) { - recurse(mod); - } - return [...visited]; -} - -function generateResourcesCode(depsCode: string) { - const ResourcesFn = (React: typeof import("react"), deps: AssetDeps) => { - return function Resources() { - return React.createElement(React.Fragment, null, [ - ...deps.css.map((href: string) => - React.createElement("link", { - key: "css:" + href, - rel: "stylesheet", - precedence: "vite-rsc/importer-resources", - href: href, - }), - ), - // js is only for dev to forward css import on browser to have hmr - ...deps.js.map((href: string) => - React.createElement("script", { - key: "js:" + href, - type: "module", - async: true, - src: href, - }), - ), - ]); - }; - }; - - return ` - import __vite_rsc_react__ from "react"; - export const Resources = (${ResourcesFn.toString()})(__vite_rsc_react__, ${depsCode}); - `; -} - -// https://github.com/vitejs/vite/blob/ea9aed7ebcb7f4be542bd2a384cbcb5a1e7b31bd/packages/vite/src/node/utils.ts#L1469-L1475 -function evalValue(rawValue: string): T { - const fn = new Function(` - var console, exports, global, module, process, require - return (\n${rawValue}\n) - `); - return fn(); -} - -// https://github.com/vitejs/vite-plugin-vue/blob/06931b1ea2b9299267374cb8eb4db27c0626774a/packages/plugin-vue/src/utils/query.ts#L13 -function parseIdQuery(id: string) { - if (!id.includes("?")) return { filename: id, query: {} }; - const [filename, rawQuery] = id.split(`?`, 2); - const query = Object.fromEntries(new URLSearchParams(rawQuery)); - return { filename, query }; -} - -export async function transformRscCssExport(options: { - ast: Awaited>; - code: string; - id?: string; - filter: TransformWrapExportFilter; -}): Promise<{ output: MagicString } | undefined> { - if (hasDirective(options.ast.body, "use client")) { - return; - } - - const result = transformWrapExport(options.code, options.ast, { - runtime: (value, name, meta) => - `__vite_rsc_wrap_css__(${value}, ${JSON.stringify(meta.defaultExportIdentifierName ?? name)})`, - filter: options.filter, - ignoreExportAllDeclaration: true, - }); - if (result.output.hasChanged()) { - if (!options.code.includes("__vite_rsc_react__")) { - result.output.prepend(`import __vite_rsc_react__ from "react";`); - } - result.output.append(` -function __vite_rsc_wrap_css__(value, name) { - if (typeof value !== 'function') return value; - - function __wrapper(props) { - return __vite_rsc_react__.createElement( - __vite_rsc_react__.Fragment, - null, - import.meta.viteRsc.loadCss(${options.id ? JSON.stringify(options.id) : ""}), - __vite_rsc_react__.createElement(value, props), - ); - } - Object.defineProperty(__wrapper, "name", { value: name }); - return __wrapper; -} -`); - return { output: result.output }; - } -} - -/** - * temporary workaround for - * - https://github.com/cloudflare/workers-sdk/issues/9538 (fixed in @cloudflare/vite-plugin@1.8.0) - * - https://github.com/vitejs/vite/pull/20077 (fixed in vite@7.0.0) - */ -export function __fix_cloudflare(): Plugin { - return { - name: "rsc:workaround-cloudflare", - enforce: "post", - config(config) { - // https://github.com/cloudflare/workers-sdk/issues/9538 - const plugin = config - .plugins!.flat() - .find((p) => p && "name" in p && p.name === "vite-plugin-cloudflare"); - const original = (plugin as any).configResolved; - (plugin as any).configResolved = function (this: any, ...args: any[]) { - try { - return original.apply(this, args); - } catch (e) {} - }; - - // workaround (fixed in Vite 7) https://github.com/vitejs/vite/pull/20077 - (config.environments as any).ssr.resolve.noExternal = true; - (config.environments as any).rsc.resolve.noExternal = true; - }, - }; -} - -function sortObject(o: T) { - return Object.fromEntries( - Object.entries(o).sort(([a], [b]) => a.localeCompare(b)), - ) as T; -} diff --git a/packages/rsc/src/react/browser.ts b/packages/rsc/src/react/browser.ts deleted file mode 100644 index 8ade2f024..000000000 --- a/packages/rsc/src/react/browser.ts +++ /dev/null @@ -1,62 +0,0 @@ -// @ts-ignore -import * as ReactClient from "@hiogawa/vite-rsc/vendor/react-server-dom/client.browser"; -import type { CallServerCallback } from "../types"; - -export { setRequireModule } from "../core/browser"; - -export function createFromReadableStream( - stream: ReadableStream, - options: object = {}, -): Promise { - return ReactClient.createFromReadableStream(stream, { - callServer, - findSourceMapURL, - ...options, - }); -} - -export function createFromFetch( - promiseForResponse: Promise, - options: object = {}, -): Promise { - return ReactClient.createFromFetch(promiseForResponse, { - callServer, - findSourceMapURL, - ...options, - }); -} - -export const encodeReply: ( - v: unknown[], - options?: unknown, -) => Promise = ReactClient.encodeReply; - -export const createServerReference: (...args: any[]) => unknown = - ReactClient.createServerReference; - -// use global instead of local variable to tolerate duplicate modules -// e.g. when `setServerCallback` is pre-bundled but `createServerReference` is not - -export function callServer(...args: any[]): any { - return (globalThis as any).__viteRscCallServer(...args); -} - -export function setServerCallback(fn: CallServerCallback): void { - (globalThis as any).__viteRscCallServer = fn; -} - -export type { CallServerCallback }; - -export const createTemporaryReferenceSet: () => unknown = - ReactClient.createTemporaryReferenceSet; - -export function findSourceMapURL( - filename: string, - environmentName: string, -): string | null { - // TODO: respect config.server.origin and config.base? - const url = new URL("/__vite_rsc_findSourceMapURL", window.location.origin); - url.searchParams.set("filename", filename); - url.searchParams.set("environmentName", environmentName); - return url.toString(); -} diff --git a/packages/rsc/src/react/rsc.ts b/packages/rsc/src/react/rsc.ts deleted file mode 100644 index c8e0c935c..000000000 --- a/packages/rsc/src/react/rsc.ts +++ /dev/null @@ -1,85 +0,0 @@ -// @ts-ignore -import * as ReactClient from "@hiogawa/vite-rsc/vendor/react-server-dom/client.edge"; -// @ts-ignore -import * as ReactServer from "@hiogawa/vite-rsc/vendor/react-server-dom/server.edge"; -import type { ReactFormState } from "react-dom/client"; -import { - createClientManifest, - createServerDecodeClientManifest, - createServerManifest, -} from "../core/rsc"; - -export { loadServerAction, setRequireModule } from "../core/rsc"; - -export function renderToReadableStream( - data: T, - options?: object, -): ReadableStream { - return ReactServer.renderToReadableStream( - data, - createClientManifest(), - options, - ); -} - -export function createFromReadableStream( - stream: ReadableStream, - options: object = {}, -): Promise { - return ReactClient.createFromReadableStream(stream, { - serverConsumerManifest: { - // https://github.com/facebook/react/pull/31300 - // https://github.com/vercel/next.js/pull/71527 - serverModuleMap: createServerManifest(), - moduleMap: createServerDecodeClientManifest(), - }, - ...options, - }); -} - -export function registerClientReference( - proxy: T, - id: string, - name: string, -): T { - return ReactServer.registerClientReference(proxy, id, name); -} - -export const registerServerReference: ( - ref: T, - id: string, - name: string, -) => T = ReactServer.registerServerReference; - -export function decodeReply( - body: string | FormData, - options?: unknown, -): Promise { - return ReactServer.decodeReply(body, createServerManifest(), options); -} - -export function decodeAction(body: FormData): Promise<() => Promise> { - return ReactServer.decodeAction(body, createServerManifest()); -} - -export function decodeFormState( - actionResult: unknown, - body: FormData, -): Promise { - return ReactServer.decodeFormState( - actionResult, - body, - createServerManifest(), - ); -} - -export const createTemporaryReferenceSet: () => unknown = - ReactServer.createTemporaryReferenceSet; - -export const encodeReply: ( - v: unknown[], - options?: unknown, -) => Promise = ReactClient.encodeReply; - -export const createClientTemporaryReferenceSet: () => unknown = - ReactClient.createTemporaryReferenceSet; diff --git a/packages/rsc/src/react/ssr.ts b/packages/rsc/src/react/ssr.ts deleted file mode 100644 index 30bb5e043..000000000 --- a/packages/rsc/src/react/ssr.ts +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-ignore -import * as ReactClient from "@hiogawa/vite-rsc/vendor/react-server-dom/client.edge"; -import { createServerConsumerManifest } from "../core/ssr"; - -export { setRequireModule } from "../core/ssr"; - -export function createFromReadableStream( - stream: ReadableStream, - options: object = {}, -): Promise { - return ReactClient.createFromReadableStream(stream, { - serverConsumerManifest: createServerConsumerManifest(), - ...options, - }); -} - -export function createServerReference(id: string): unknown { - return ReactClient.createServerReference(id); -} - -export const callServer = null; -export const findSourceMapURL = null; diff --git a/packages/rsc/src/rsc-html-stream/browser.ts b/packages/rsc/src/rsc-html-stream/browser.ts deleted file mode 100644 index 69cc83374..000000000 --- a/packages/rsc/src/rsc-html-stream/browser.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as rscHtmlStreamClient from "rsc-html-stream/client"; - -export const getRscStreamFromHtml = (): ReadableStream => - rscHtmlStreamClient.rscStream; diff --git a/packages/rsc/src/rsc-html-stream/ssr.ts b/packages/rsc/src/rsc-html-stream/ssr.ts deleted file mode 100644 index 8b2b80665..000000000 --- a/packages/rsc/src/rsc-html-stream/ssr.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as rscHtmlStreamServer from "rsc-html-stream/server"; - -export const injectRscStreamToHtml = ( - stream: ReadableStream, - options?: { nonce?: string }, -): TransformStream => - rscHtmlStreamServer.injectRSCPayload(stream, options); diff --git a/packages/rsc/src/rsc.tsx b/packages/rsc/src/rsc.tsx deleted file mode 100644 index 2d20cbeaf..000000000 --- a/packages/rsc/src/rsc.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import serverReferences from "virtual:vite-rsc/server-references"; -import { setRequireModule } from "./core/rsc"; - -export { - createClientManifest, - createServerManifest, - loadServerAction, -} from "./core/rsc"; - -export { - encryptActionBoundArgs, - decryptActionBoundArgs, -} from "./utils/encryption-runtime"; - -export * from "./react/rsc"; - -initialize(); - -function initialize(): void { - setRequireModule({ - load: async (id) => { - if (!import.meta.env.__vite_rsc_build__) { - return import(/* @vite-ignore */ id); - } else { - const import_ = serverReferences[id]; - if (!import_) { - throw new Error(`server reference not found '${id}'`); - } - return import_(); - } - }, - }); -} diff --git a/packages/rsc/src/ssr.tsx b/packages/rsc/src/ssr.tsx deleted file mode 100644 index e7c4d93c2..000000000 --- a/packages/rsc/src/ssr.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import assetsManifest from "virtual:vite-rsc/assets-manifest"; -import * as clientReferences from "virtual:vite-rsc/client-references"; -import * as ReactDOM from "react-dom"; -import { setRequireModule } from "./core/ssr"; -import type { AssetDeps } from "./plugin"; - -export { createServerConsumerManifest } from "./core/ssr"; - -export * from "./react/ssr"; - -initialize(); - -function initialize(): void { - setRequireModule({ - load: async (id) => { - if (!import.meta.env.__vite_rsc_build__) { - const mod = await import(/* @vite-ignore */ id); - const modCss = await import( - /* @vite-ignore */ "/@id/__x00__virtual:vite-rsc/css/dev-ssr/" + id - ); - return wrapResourceProxy(mod, { js: [], css: modCss.default }); - } else { - const import_ = clientReferences.default[id]; - if (!import_) { - throw new Error(`client reference not found '${id}'`); - } - const deps = assetsManifest.clientReferenceDeps[id]; - // kick off preload before initial async import, which is not sync-cached - if (deps) { - preloadDeps(deps); - } - const mod: any = await import_(); - return wrapResourceProxy(mod, deps); - } - }, - }); -} - -// preload/preinit during getter access since `load` is cached on production -function wrapResourceProxy(mod: any, deps?: AssetDeps) { - return new Proxy(mod, { - get(target, p, receiver) { - if (p in mod) { - if (deps) { - preloadDeps(deps); - } - } - return Reflect.get(target, p, receiver); - }, - }); -} - -function preloadDeps(deps: AssetDeps) { - for (const href of deps.js) { - ReactDOM.preloadModule(href, { - as: "script", - // vite doesn't allow configuring crossorigin at the moment, so we can hard code it as well. - // https://github.com/vitejs/vite/issues/6648 - crossOrigin: "", - }); - } - for (const href of deps.css) { - ReactDOM.preinit(href, { as: "style" }); - } -} diff --git a/packages/rsc/src/types/index.ts b/packages/rsc/src/types/index.ts deleted file mode 100644 index d34078a69..000000000 --- a/packages/rsc/src/types/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface ImportManifestEntry { - id: string; - name: string; - chunks: string[]; - async?: boolean; -} - -export interface BundlerConfig { - [bundlerId: string]: ImportManifestEntry; -} - -export type ModuleMap = { - [id: string]: { - [exportName: string]: ImportManifestEntry; - }; -}; - -export interface ServerConsumerManifest { - moduleMap?: ModuleMap; - serverModuleMap?: BundlerConfig; - moduleLoading?: { - prefix: string; - crossOriign?: string; - }; -} - -export type CallServerCallback = (id: string, args: unknown[]) => unknown; diff --git a/packages/rsc/src/types/react.ts b/packages/rsc/src/types/react.ts deleted file mode 100644 index 10dd886d7..000000000 --- a/packages/rsc/src/types/react.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "react-dom/server.edge" { - export * from "react-dom/server"; -} diff --git a/packages/rsc/src/types/virtual.d.ts b/packages/rsc/src/types/virtual.d.ts deleted file mode 100644 index 715d4cd0f..000000000 --- a/packages/rsc/src/types/virtual.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -declare module "virtual:vite-rsc/assets-manifest" { - const assetsManifest: import("../plugin").AssetsManifest; - export default assetsManifest; -} - -declare module "virtual:vite-rsc/client-references" { - const default_: Record Promise>; - export default default_; - export const assetDeps: - | Record - | undefined; -} - -declare module "virtual:vite-rsc/server-references" { - const default_: Record Promise>; - export default default_; -} - -declare module "virtual:vite-rsc/encryption-key" { - const default_: () => string | Promise; - export default default_; -} diff --git a/packages/rsc/src/utils/encryption-runtime.ts b/packages/rsc/src/utils/encryption-runtime.ts deleted file mode 100644 index 39c6ba656..000000000 --- a/packages/rsc/src/utils/encryption-runtime.ts +++ /dev/null @@ -1,48 +0,0 @@ -import encryptionKeySource from "virtual:vite-rsc/encryption-key"; -import { once } from "@hiogawa/utils"; -import { createFromReadableStream, renderToReadableStream } from "../react/rsc"; -import { - arrayToStream, - concatArrayStream, - decryptBuffer, - encryptBuffer, - fromBase64, -} from "./encryption-utils"; - -// based on -// https://github.com/parcel-bundler/parcel/blob/9855f558a69edde843b1464f39a6010f6b421efe/packages/transformers/js/src/rsc-utils.js -// https://github.com/vercel/next.js/blob/c10c10daf9e95346c31c24dc49d6b7cda48b5bc8/packages/next/src/server/app-render/encryption.ts -// https://github.com/vercel/next.js/pull/56377 - -export async function encryptActionBoundArgs( - originalValue: unknown, -): Promise { - const serialized = renderToReadableStream(originalValue); - const serializedBuffer = await concatArrayStream(serialized); - return encryptBuffer(serializedBuffer, await getEncryptionKey()); -} - -export async function decryptActionBoundArgs( - encrypted: ReturnType, -): Promise { - const serializedBuffer = await decryptBuffer( - await encrypted, - await getEncryptionKey(), - ); - const serialized = arrayToStream(new Uint8Array(serializedBuffer)); - return createFromReadableStream(serialized); -} - -const getEncryptionKey = /* #__PURE__ */ once(async () => { - const resolved = await encryptionKeySource(); - const key = await crypto.subtle.importKey( - "raw", - fromBase64(resolved), - { - name: "AES-GCM", - }, - true, - ["encrypt", "decrypt"], - ); - return key; -}); diff --git a/packages/rsc/src/utils/encryption-utils.ts b/packages/rsc/src/utils/encryption-utils.ts deleted file mode 100644 index d14c3b159..000000000 --- a/packages/rsc/src/utils/encryption-utils.ts +++ /dev/null @@ -1,115 +0,0 @@ -// based on -// https://github.com/vercel/next.js/blob/a0993d90c280690e83a2a1bc7c292e1187429fe8/packages/next/src/server/app-render/encryption-utils.ts - -function arrayBufferToString(buffer: ArrayBuffer | Uint8Array): string { - const bytes = new Uint8Array(buffer); - const len = bytes.byteLength; - if (len < 65535) { - return String.fromCharCode.apply(null, bytes as unknown as number[]); - } - let binary = ""; - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]!); - } - return binary; -} - -function stringToUint8Array(binary: string): Uint8Array { - const len = binary.length; - const arr = new Uint8Array(len); - for (let i = 0; i < len; i++) { - arr[i] = binary.charCodeAt(i); - } - return arr; -} - -function concatArray(chunks: Uint8Array[]): Uint8Array { - let total = 0; - for (const chunk of chunks) { - total += chunk.length; - } - const result = new Uint8Array(total); - let offset = 0; - for (const chunk of chunks) { - result.set(chunk, offset); - offset += chunk.length; - } - return result; -} - -export async function concatArrayStream( - stream: ReadableStream, -): Promise { - const chunks: Uint8Array[] = []; - await stream.pipeTo( - new WritableStream({ - write(chunk) { - chunks.push(chunk); - }, - }), - ); - return concatArray(chunks); -} - -export function arrayToStream(data: Uint8Array): ReadableStream { - return new ReadableStream({ - start(controller) { - controller.enqueue(data); - controller.close(); - }, - }); -} - -export function toBase64(buffer: Uint8Array): string { - return btoa(arrayBufferToString(buffer)); -} - -export function fromBase64(data: string): Uint8Array { - return stringToUint8Array(atob(data)); -} - -export async function generateEncryptionKey(): Promise { - const key = await crypto.subtle.generateKey( - { - name: "AES-GCM", - length: 256, - }, - true, - ["encrypt", "decrypt"], - ); - const exported = await crypto.subtle.exportKey("raw", key); - return new Uint8Array(exported); -} - -export async function encryptBuffer( - data: BufferSource, - key: CryptoKey, -): Promise { - const iv = crypto.getRandomValues(new Uint8Array(16)); - const encrypted = await crypto.subtle.encrypt( - { - name: "AES-GCM", - iv, - }, - key, - data, - ); - return toBase64(concatArray([iv, new Uint8Array(encrypted)])); -} - -export async function decryptBuffer( - encryptedString: string, - key: CryptoKey, -): Promise { - const concatenated = fromBase64(encryptedString); - const iv = concatenated.slice(0, 16); - const encrypted = concatenated.slice(16); - return crypto.subtle.decrypt( - { - name: "AES-GCM", - iv, - }, - key, - encrypted, - ); -} diff --git a/packages/rsc/src/utils/rpc.ts b/packages/rsc/src/utils/rpc.ts deleted file mode 100644 index 74cc955dc..000000000 --- a/packages/rsc/src/utils/rpc.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { decode, encode } from "turbo-stream"; - -type RequestPayload = { - method: string; - args: any[]; -}; - -type ResponsePayload = { - ok: boolean; - data: any; -}; - -export function createRpcServer(handlers: T) { - return async (request: Request): Promise => { - if (!request.body) { - throw new Error(`loadModuleDevProxy error: missing request body`); - } - const reqPayload = await decode( - request.body.pipeThrough(new TextDecoderStream()), - ); - const handler = (handlers as any)[reqPayload.method]; - if (!handler) { - throw new Error( - `loadModuleDevProxy error: unknown method ${reqPayload.method}`, - ); - } - const resPayload: ResponsePayload = { ok: true, data: undefined }; - try { - resPayload.data = await handler(...reqPayload.args); - } catch (e) { - resPayload.ok = false; - resPayload.data = e; - } - return new Response(encode(resPayload)); - }; -} - -export function createRpcClient(options: { endpoint: string }): T { - async function callRpc(method: string, args: any[]) { - const reqPayload: RequestPayload = { - method, - args, - }; - const body = encode(reqPayload).pipeThrough(new TextEncoderStream()); - const res = await fetch(options.endpoint, { - method: "POST", - body, - // @ts-ignore undici compat - duplex: "half", - }); - if (!res.ok || !res.body) { - throw new Error( - `loadModuleDevProxy error: ${res.status} ${res.statusText}`, - ); - } - const resPayload = await decode( - res.body.pipeThrough(new TextDecoderStream()), - ); - if (!resPayload.ok) { - throw resPayload.data; - } - return resPayload.data; - } - - return new Proxy( - {}, - { - get(_target, p, _receiver) { - if (typeof p !== "string" || p === "then") { - return; - } - return (...args: any[]) => callRpc(p, args); - }, - }, - ) as any; -} diff --git a/packages/rsc/src/vite-utils.ts b/packages/rsc/src/vite-utils.ts deleted file mode 100644 index 7ee18381c..000000000 --- a/packages/rsc/src/vite-utils.ts +++ /dev/null @@ -1,126 +0,0 @@ -// import analysis logic copied from vite - -import fs from "node:fs"; -import path from "node:path"; -import type { DevEnvironment, Rollup } from "vite"; - -export const VALID_ID_PREFIX = `/@id/`; - -export const NULL_BYTE_PLACEHOLDER = `__x00__`; - -export const FS_PREFIX = `/@fs/`; - -export function wrapId(id: string): string { - return id.startsWith(VALID_ID_PREFIX) - ? id - : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER); -} - -export function unwrapId(id: string): string { - return id.startsWith(VALID_ID_PREFIX) - ? id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, "\0") - : id; -} - -export function withTrailingSlash(path: string): string { - if (path[path.length - 1] !== "/") { - return `${path}/`; - } - return path; -} - -const postfixRE = /[?#].*$/; -export function cleanUrl(url: string): string { - return url.replace(postfixRE, ""); -} - -export function splitFileAndPostfix(path: string): { - file: string; - postfix: string; -} { - const file = cleanUrl(path); - return { file, postfix: path.slice(file.length) }; -} - -const windowsSlashRE = /\\/g; -export function slash(p: string): string { - return p.replace(windowsSlashRE, "/"); -} - -const isWindows = - typeof process !== "undefined" && process.platform === "win32"; - -export function injectQuery(url: string, queryToInject: string): string { - const { file, postfix } = splitFileAndPostfix(url); - const normalizedFile = isWindows ? slash(file) : file; - return `${normalizedFile}?${queryToInject}${postfix[0] === "?" ? `&${postfix.slice(1)}` : /* hash only */ postfix}`; -} - -export function joinUrlSegments(a: string, b: string): string { - if (!a || !b) { - return a || b || ""; - } - if (a.endsWith("/")) { - a = a.substring(0, a.length - 1); - } - if (b[0] !== "/") { - b = "/" + b; - } - return a + b; -} - -export function normalizeResolvedIdToUrl( - environment: DevEnvironment, - url: string, - resolved: Rollup.PartialResolvedId, -): string { - const root = environment.config.root; - const depsOptimizer = environment.depsOptimizer; - - // normalize all imports into resolved URLs - // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'` - if (resolved.id.startsWith(withTrailingSlash(root))) { - // in root: infer short absolute path from root - url = resolved.id.slice(root.length); - } else if ( - depsOptimizer?.isOptimizedDepFile(resolved.id) || - // vite-plugin-react isn't following the leading \0 virtual module convention. - // This is a temporary hack to avoid expensive fs checks for React apps. - // We'll remove this as soon we're able to fix the react plugins. - (resolved.id !== "/@react-refresh" && - path.isAbsolute(resolved.id) && - fs.existsSync(cleanUrl(resolved.id))) - ) { - // an optimized deps may not yet exists in the filesystem, or - // a regular file exists but is out of root: rewrite to absolute /@fs/ paths - url = path.posix.join(FS_PREFIX, resolved.id); - } else { - url = resolved.id; - } - - // if the resolved id is not a valid browser import specifier, - // prefix it to make it valid. We will strip this before feeding it - // back into the transform pipeline - if (url[0] !== "." && url[0] !== "/") { - url = wrapId(resolved.id); - } - - return url; -} - -export function normalizeViteImportAnalysisUrl( - environment: DevEnvironment, - id: string, -): string { - let url = normalizeResolvedIdToUrl(environment, id, { id }); - - // https://github.com/vitejs/vite/blob/c18ce868c4d70873406e9f7d1b2d0a03264d2168/packages/vite/src/node/plugins/importAnalysis.ts#L416 - if (environment.config.consumer === "client") { - const mod = environment.moduleGraph.getModuleById(id); - if (mod && mod.lastHMRTimestamp > 0) { - url = injectQuery(url, `t=${mod.lastHMRTimestamp}`); - } - } - - return url; -} diff --git a/packages/rsc/tsconfig.json b/packages/rsc/tsconfig.json deleted file mode 100644 index 70388ec04..000000000 --- a/packages/rsc/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["src", "*.ts", "types"], - "compilerOptions": { - "noPropertyAccessFromIndexSignature": false, - "noImplicitReturns": false, - "checkJs": false, - "declaration": true, - "isolatedDeclarations": true, - "types": ["vite/client"], - "jsx": "react-jsx" - } -} diff --git a/packages/rsc/tsdown.config.ts b/packages/rsc/tsdown.config.ts deleted file mode 100644 index 3562ba398..000000000 --- a/packages/rsc/tsdown.config.ts +++ /dev/null @@ -1,49 +0,0 @@ -import fs from "node:fs"; -import { defineConfig } from "tsdown"; - -export default defineConfig({ - entry: [ - "src/index.ts", - "src/plugin.ts", - "src/browser.ts", - "src/ssr.tsx", - "src/rsc.tsx", - "src/vite-utils.ts", - "src/core/browser.ts", - "src/core/ssr.ts", - "src/core/rsc.ts", - "src/core/plugin.ts", - "src/react/browser.ts", - "src/react/ssr.ts", - "src/react/rsc.ts", - "src/extra/browser.tsx", - "src/extra/ssr.tsx", - "src/extra/rsc.tsx", - "src/rsc-html-stream/ssr.ts", - "src/rsc-html-stream/browser.ts", - "src/utils/rpc.ts", - ], - format: ["esm"], - external: [/^virtual:/, /^@hiogawa\/vite-rsc\//], - dts: { - sourcemap: process.argv.slice(2).includes("--sourcemap"), - }, - plugins: [ - { - name: "vendor-react-server-dom", - buildStart() { - fs.rmSync("./dist/vendor/", { recursive: true, force: true }); - fs.mkdirSync("./dist/vendor", { recursive: true }); - fs.cpSync( - "./node_modules/react-server-dom-webpack", - "./dist/vendor/react-server-dom", - { recursive: true, dereference: true }, - ); - fs.rmSync("./dist/vendor/react-server-dom/node_modules", { - recursive: true, - force: true, - }); - }, - }, - ], -}) as any; diff --git a/packages/rsc/types/index.d.ts b/packages/rsc/types/index.d.ts deleted file mode 100644 index 1f5e30151..000000000 --- a/packages/rsc/types/index.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import "./virtual"; - -declare global { - interface ImportMeta { - readonly viteRsc: { - loadCss: (importer?: string) => import("react").ReactNode; - /** @deprecated use `loadModule("ssr", entry)` instead */ - loadSsrModule: (entry: string) => Promise; - loadModule: (environmentName: string, entryName: string) => Promise; - loadBootstrapScriptContent: (entryName: string) => Promise; - }; - } - - interface ImportMetaEnv { - readonly __vite_rsc_build__: boolean; - } -} - -export {}; diff --git a/packages/rsc/types/virtual.d.ts b/packages/rsc/types/virtual.d.ts deleted file mode 100644 index 984f937f5..000000000 --- a/packages/rsc/types/virtual.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "virtual:vite-rsc/bootstrap-script-content" { - /** @deprecated use `import.meta.viteRsc.loadBootstrapScriptContent("index")` instead */ - const bootstrapScriptContent: string; - export default bootstrapScriptContent; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3735ca494..f7f5ba485 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,6 @@ overrides: react-dom: ^19.1.0 react-server-dom-webpack: ^19.1.0 '@hiogawa/react-server': workspace:* - '@hiogawa/vite-rsc': workspace:* '@playwright/test': ^1.53.1 '@types/react': ^19.1.8 '@types/react-dom': ^19.1.6 @@ -223,8 +222,8 @@ importers: specifier: workspace:* version: link:../transforms '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../rsc + specifier: ^0.4.9 + version: 0.4.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) es-module-lexer: specifier: ^1.6.0 version: 1.7.0 @@ -584,262 +583,6 @@ importers: specifier: ^7.1.5 version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - packages/rsc: - dependencies: - '@hiogawa/transforms': - specifier: workspace:* - version: link:../transforms - '@mjackson/node-fetch-server': - specifier: ^0.6.1 - version: 0.6.1 - es-module-lexer: - specifier: ^1.6.0 - version: 1.7.0 - magic-string: - specifier: ^0.30.17 - version: 0.30.17 - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - turbo-stream: - specifier: ^3.1.0 - version: 3.1.0 - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vitefu: - specifier: ^1.0.5 - version: 1.0.5(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - devDependencies: - '@playwright/test': - specifier: ^1.53.1 - version: 1.53.1 - react-server-dom-webpack: - specifier: ^19.1.0 - version: 19.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.93.0(esbuild@0.24.2)) - rsc-html-stream: - specifier: ^0.0.6 - version: 0.0.6 - tinyexec: - specifier: ^1.0.1 - version: 1.0.1 - - packages/rsc-react-router: - dependencies: - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - devDependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../rsc - '@react-router/dev': - specifier: 0.0.0-experimental-23decd7bc - version: 0.0.0-experimental-23decd7bc(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0))(yaml@2.7.0) - react-router: - specifier: 0.0.0-experimental-23decd7bc - version: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - - packages/rsc/examples/basic: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - devDependencies: - '@tailwindcss/vite': - specifier: ^4.1.4 - version: 4.1.4(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - '@vitejs/test-dep-client-in-server': - specifier: file:./test-dep/client-in-server - version: file:packages/rsc/examples/basic/test-dep/client-in-server(react@19.1.0) - '@vitejs/test-dep-client-in-server2': - specifier: file:./test-dep/client-in-server2 - version: file:packages/rsc/examples/basic/test-dep/client-in-server2(react@19.1.0) - '@vitejs/test-dep-server-in-client': - specifier: file:./test-dep/server-in-client - version: file:packages/rsc/examples/basic/test-dep/server-in-client(react@19.1.0) - '@vitejs/test-dep-server-in-server': - specifier: file:./test-dep/server-in-server - version: file:packages/rsc/examples/basic/test-dep/server-in-server(react@19.1.0) - tailwindcss: - specifier: ^4.1.4 - version: 4.1.4 - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-inspect: - specifier: ^11.2.0 - version: 11.3.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - - packages/rsc/examples/hono: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - hono: - specifier: ^4.7.5 - version: 4.7.5 - - packages/rsc/examples/react-router: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - react-router: - specifier: 0.0.0-experimental-23decd7bc - version: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - devDependencies: - '@cloudflare/vite-plugin': - specifier: ^1.13.9 - version: 1.13.9(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workerd@1.20251011.0)(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0)) - '@react-router/dev': - specifier: 0.0.0-experimental-23decd7bc - version: 0.0.0-experimental-23decd7bc(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0))(yaml@2.7.0) - '@tailwindcss/typography': - specifier: ^0.5.16 - version: 0.5.16(tailwindcss@4.1.4) - '@tailwindcss/vite': - specifier: ^4.1.4 - version: 4.1.4(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - tailwindcss: - specifier: ^4.1.4 - version: 4.1.4 - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-inspect: - specifier: ^11.2.0 - version: 11.3.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - wrangler: - specifier: ^4.41.0 - version: 4.41.0(@cloudflare/workers-types@4.20251003.0) - - packages/rsc/examples/ssg: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - devDependencies: - '@mdx-js/rollup': - specifier: ^3.1.0 - version: 3.1.0(acorn@8.15.0)(rollup@4.46.2) - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-inspect: - specifier: latest - version: 11.3.3(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - - packages/rsc/examples/starter: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - devDependencies: - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-inspect: - specifier: latest - version: 11.3.3(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - - packages/rsc/examples/starter-cf-single: - dependencies: - '@hiogawa/vite-rsc': - specifier: workspace:* - version: link:../.. - react: - specifier: ^19.1.0 - version: 19.1.0 - react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) - devDependencies: - '@cloudflare/vite-plugin': - specifier: ^1.13.9 - version: 1.13.9(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workerd@1.20251011.0)(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0)) - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^5.1.0 - version: 5.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - vite: - specifier: ^7.1.5 - version: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - packages/server-asset: dependencies: magic-string: @@ -958,10 +701,6 @@ packages: resolution: {integrity: sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==} engines: {node: '>=6.9.0'} - '@babel/core@7.27.7': - resolution: {integrity: sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} @@ -974,62 +713,28 @@ packages: resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.25.9': - resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.26.9': - resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.25.9': - resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.25.9': - resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.26.5': - resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.25.9': - resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -1046,10 +751,6 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} @@ -1069,30 +770,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-decorators@7.25.9': - resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.26.3': - resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -1105,18 +782,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.26.8': - resolution: {integrity: sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-typescript@7.26.0': - resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1211,12 +876,6 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.13.9': - resolution: {integrity: sha512-XvrHcml/jL8OtN0+61TLeP6DRgwxoR/MC2kuNoNKZ+wpnowAJ1D5BKVE4J8VzX8P4MclS592BoXuRlYboxuuIg==} - peerDependencies: - vite: ^7.1.5 - wrangler: ^4.41.0 - '@cloudflare/workerd-darwin-64@1.20251001.0': resolution: {integrity: sha512-y1ST/cCscaRewWRnsHZdWbgiLJbki5UMGd0hMo/FLqjlztwPeDgQ5CGm5jMiCDdw/IBCpWxEukftPYR34rWNog==} engines: {node: '>=16'} @@ -1947,6 +1606,9 @@ packages: peerDependencies: react: ^19.1.0 + '@hiogawa/transforms@0.1.6': + resolution: {integrity: sha512-IWt/CCZ5+ZxOmpm/X7Eyt5t2byqFEnnwNJK1AliIKnN+6Lq8j3fdYB/4goWcTH7WJCelGQoKpvx86eO//IN48w==} + '@hiogawa/unocss-preset-antd@2.2.1-pre.7': resolution: {integrity: sha512-gwY0T8FpFzvHE3I2bShDFKRFAlf1dxgAeaLkfGy7VpNkVr7NkrS3hIUb3An95zAjQWSvgP90t+p1e8AaFW8oXw==} peerDependencies: @@ -1963,6 +1625,14 @@ packages: peerDependencies: vite: ^7.1.5 + '@hiogawa/vite-rsc@0.4.9': + resolution: {integrity: sha512-aV+GTKctMfOw+0dyrzK/K4yn0dy3bQxsZXXkhBS60Q5tFSTbQ671UYOLLBrj2LmJ+t7lJkZtFn/dsgvuFfiZng==} + deprecated: Use @vitejs/plugin-rsc instead + peerDependencies: + react: ^19.1.0 + react-dom: ^19.1.0 + vite: ^7.1.5 + '@iconify-json/ri@1.2.5': resolution: {integrity: sha512-kWGimOXMZrlYusjBKKXYOWcKhbOHusFsmrmRGmjS7rH0BpML5A9/fy8KHZqFOwZfC4M6amObQYbh8BqO5cMC3w==} @@ -2137,9 +1807,6 @@ packages: peerDependencies: rollup: '>=2' - '@mjackson/node-fetch-server@0.2.0': - resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} - '@mjackson/node-fetch-server@0.6.1': resolution: {integrity: sha512-9ZJnk/DJjt805uv5PPv11haJIW+HHf3YEEyVXv+8iLQxLD/iXA68FH220XoiTPBC4gCg5q+IMadDw8qPqlA5wg==} @@ -2194,18 +1861,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@npmcli/git@4.1.0': - resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/package-json@4.0.1': - resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/promise-spawn@6.0.2': - resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@oxc-parser/wasm@0.23.1': resolution: {integrity: sha512-POmbpSciGWxpNZeYvdVJhSXSkWzqPymuYwEtR8n2aYlV0jVi4vX6QcQFH4LJ2IK4sQ85TLtVXFC7O6z/vFaPQQ==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -2330,37 +1985,6 @@ packages: resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==} engines: {node: '>=20.0.0'} - '@react-router/dev@0.0.0-experimental-23decd7bc': - resolution: {integrity: sha512-iY4WgHNv/7mDbExXQA35u7H54ihPJTrm20Z42Ni2G+Hgz3X4A0ZmeT7CtpfuBzC3UIrqdmNZT3nQOSoKNwJlWA==} - engines: {node: '>=20.0.0'} - hasBin: true - peerDependencies: - '@react-router/serve': ^0.0.0-experimental-23decd7bc - react-router: ^0.0.0-experimental-23decd7bc - typescript: ^5.1.0 - vite: ^7.1.5 - wrangler: ^3.28.2 || ^4.0.0 - peerDependenciesMeta: - '@react-router/serve': - optional: true - typescript: - optional: true - wrangler: - optional: true - - '@react-router/node@0.0.0-experimental-23decd7bc': - resolution: {integrity: sha512-y9tOT+jEzBGXrwBjCq2obqKe+N6znsT+I02R/SDFTqTcXrdTJ7aK0iRnWGHxUw/FrOxoPeGkA0v5dWoJI0jBew==} - engines: {node: '>=20.0.0'} - peerDependencies: - react-router: 0.0.0-experimental-23decd7bc - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/node-fetch-server@0.8.1': - resolution: {integrity: sha512-J1dev372wtJqmqn9U/qbpbZxbJSQrogNN2+Qv1lKlpATpe/WQ9aCZfl/xSb9d2Rgh1IyLSvNxZAXPZxruO6Xig==} - '@resvg/resvg-wasm@2.4.0': resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} @@ -2799,101 +2423,6 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tailwindcss/node@4.1.4': - resolution: {integrity: sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==} - - '@tailwindcss/oxide-android-arm64@4.1.4': - resolution: {integrity: sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.4': - resolution: {integrity: sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.4': - resolution: {integrity: sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.4': - resolution: {integrity: sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4': - resolution: {integrity: sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.4': - resolution: {integrity: sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.4': - resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.4': - resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.4': - resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.4': - resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.4': - resolution: {integrity: sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.4': - resolution: {integrity: sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.4': - resolution: {integrity: sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==} - engines: {node: '>= 10'} - - '@tailwindcss/typography@0.5.16': - resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - - '@tailwindcss/vite@4.1.4': - resolution: {integrity: sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A==} - peerDependencies: - vite: ^7.1.5 - '@tanstack/history@1.99.13': resolution: {integrity: sha512-JMd7USmnp8zV8BRGIjALqzPxazvKtQ7PGXQC7n39HpbqdsmfV2ePCzieO84IvN+mwsTrXErpbjI4BfKCa+ZNCg==} engines: {node: '>=12'} @@ -3125,26 +2654,6 @@ packages: peerDependencies: vite: ^7.1.5 - '@vitejs/test-dep-client-in-server2@file:packages/rsc/examples/basic/test-dep/client-in-server2': - resolution: {directory: packages/rsc/examples/basic/test-dep/client-in-server2, type: directory} - peerDependencies: - react: ^19.1.0 - - '@vitejs/test-dep-client-in-server@file:packages/rsc/examples/basic/test-dep/client-in-server': - resolution: {directory: packages/rsc/examples/basic/test-dep/client-in-server, type: directory} - peerDependencies: - react: ^19.1.0 - - '@vitejs/test-dep-server-in-client@file:packages/rsc/examples/basic/test-dep/server-in-client': - resolution: {directory: packages/rsc/examples/basic/test-dep/server-in-client, type: directory} - peerDependencies: - react: ^19.1.0 - - '@vitejs/test-dep-server-in-server@file:packages/rsc/examples/basic/test-dep/server-in-server': - resolution: {directory: packages/rsc/examples/basic/test-dep/server-in-server, type: directory} - peerDependencies: - react: ^19.1.0 - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -3421,9 +2930,6 @@ packages: b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} - babel-dead-code-elimination@1.0.10: - resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -3768,14 +3274,6 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - dedent@1.5.3: - resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3961,9 +3459,6 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} @@ -4227,10 +3722,6 @@ packages: get-port-please@3.2.0: resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} - get-port@7.1.0: - resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} - engines: {node: '>=16'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -4338,10 +3829,6 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - hosted-git-info@6.1.3: - resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -4545,10 +4032,6 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-parse-even-better-errors@3.0.2: - resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -4691,9 +4174,6 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash.castarray@4.4.0: - resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -4703,12 +4183,6 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -4731,10 +4205,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - luxon@3.7.1: resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} engines: {node: '>=12'} @@ -5047,10 +4517,6 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - normalize-package-data@5.0.0: - resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - normalize-package-data@6.0.2: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} @@ -5063,22 +4529,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-install-checks@6.3.0: - resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-package-arg@10.1.0: - resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-pick-manifest@8.0.2: - resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5127,10 +4577,6 @@ packages: resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==} engines: {node: '>=18'} - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -5228,9 +4674,6 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - perfect-debounce@2.0.0: - resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} - periscopic@4.0.2: resolution: {integrity: sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==} @@ -5303,10 +4746,6 @@ packages: peerDependencies: postcss: ^8.2.14 - postcss-selector-parser@6.0.10: - resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} - engines: {node: '>=4'} - postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -5333,19 +4772,10 @@ packages: engines: {node: '>=18'} hasBin: true - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - pretty-bytes@6.1.1: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} - proc-log@3.0.0: - resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -5353,18 +4783,6 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} @@ -5406,24 +4824,10 @@ packages: peerDependencies: react: ^19.1.0 - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} - react-router@0.0.0-experimental-23decd7bc: - resolution: {integrity: sha512-oTDa74rdP6WACxX8wihI71TiwQa+3aAXNjGGm20OAyA4hGdfe0VBEbJvuIT0vxR+LKsJisI4rpaq0boBGY3m+g==} - engines: {node: '>=20.0.0'} - peerDependencies: - react: ^19.1.0 - react-dom: ^19.1.0 - peerDependenciesMeta: - react-dom: - optional: true - react-server-dom-webpack@19.1.0: resolution: {integrity: sha512-GUbawkNSN0oj8GnuNhMzsvyIHpXqqpAmyOY5NRqNNQ/M8wvUUN8YBoGjDUj9lbmBrmAHS65BByp6325CcWA0eg==} engines: {node: '>=0.10.0'} @@ -5552,10 +4956,6 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5659,9 +5059,6 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -5869,9 +5266,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.1.4: - resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==} - tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -6145,10 +5539,6 @@ packages: resolution: {integrity: sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==} engines: {node: '>=18.12.0'} - unplugin-utils@0.3.0: - resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} - engines: {node: '>=20.19.0'} - unplugin@1.16.1: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} @@ -6257,21 +5647,9 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - valibot@0.41.0: - resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -6303,16 +5681,6 @@ packages: '@nuxt/kit': optional: true - vite-plugin-inspect@11.3.3: - resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} - engines: {node: '>=14'} - peerDependencies: - '@nuxt/kit': '*' - vite: ^7.1.5 - peerDependenciesMeta: - '@nuxt/kit': - optional: true - vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -6446,11 +5814,6 @@ packages: engines: {node: '>= 8'} hasBin: true - which@3.0.1: - resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -6511,10 +5874,6 @@ packages: utf-8-validate: optional: true - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6599,26 +5958,6 @@ snapshots: '@babel/compat-data@7.27.7': {} - '@babel/core@7.27.7': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) - '@babel/helpers': 7.27.6 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/traverse': 7.27.7 - '@babel/types': 7.28.4 - convert-source-map: 2.0.0 - debug: 4.4.1 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 @@ -6655,10 +5994,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.0.2 - '@babel/helper-annotate-as-pure@7.25.9': - dependencies: - '@babel/types': 7.28.4 - '@babel/helper-compilation-targets@7.27.2': dependencies: '@babel/compat-data': 7.27.7 @@ -6667,28 +6002,8 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.27.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.27.7 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.25.9': - dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.27.7 @@ -6696,15 +6011,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -6714,28 +6020,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.25.9': - dependencies: - '@babel/types': 7.28.4 - '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-replace-supers@7.26.5(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.27.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.25.9': - dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} @@ -6744,11 +6030,6 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.6': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 @@ -6766,29 +6047,6 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -6799,28 +6057,6 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.27.7) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.27.7) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.26.0(@babel/core@7.27.7)': - dependencies: - '@babel/core': 7.27.7 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.27.7) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.27.7) - '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.27.7) - transitivePeerDependencies: - - supports-color - '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -6916,29 +6152,6 @@ snapshots: optionalDependencies: workerd: 1.20251001.0 - '@cloudflare/unenv-preset@2.7.5(unenv@2.0.0-rc.21)(workerd@1.20251011.0)': - dependencies: - unenv: 2.0.0-rc.21 - optionalDependencies: - workerd: 1.20251011.0 - - '@cloudflare/vite-plugin@1.13.9(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workerd@1.20251011.0)(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0))': - dependencies: - '@cloudflare/unenv-preset': 2.7.5(unenv@2.0.0-rc.21)(workerd@1.20251011.0) - '@remix-run/node-fetch-server': 0.8.1 - get-port: 7.1.0 - miniflare: 4.20251001.0 - picocolors: 1.1.1 - tinyglobby: 0.2.15 - unenv: 2.0.0-rc.21 - vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - wrangler: 4.41.0(@cloudflare/workers-types@4.20251003.0) - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - workerd - '@cloudflare/workerd-darwin-64@1.20251001.0': optional: true @@ -7335,6 +6548,12 @@ snapshots: dependencies: react: 19.1.0 + '@hiogawa/transforms@0.1.6': + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + periscopic: 4.0.2 + '@hiogawa/unocss-preset-antd@2.2.1-pre.7(unocss@66.2.1(postcss@8.5.6)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.22(typescript@5.8.3)))': dependencies: unocss: 66.2.1(postcss@8.5.6)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.22(typescript@5.8.3)) @@ -7351,6 +6570,18 @@ snapshots: strip-literal: 3.1.0 vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + '@hiogawa/vite-rsc@0.4.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': + dependencies: + '@hiogawa/transforms': 0.1.6 + '@mjackson/node-fetch-server': 0.6.1 + es-module-lexer: 1.7.0 + magic-string: 0.30.21 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + turbo-stream: 3.1.0 + vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) + vitefu: 1.0.5(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) + '@iconify-json/ri@1.2.5': dependencies: '@iconify/types': 2.0.0 @@ -7558,8 +6789,6 @@ snapshots: - acorn - supports-color - '@mjackson/node-fetch-server@0.2.0': {} - '@mjackson/node-fetch-server@0.6.1': {} '@napi-rs/wasm-runtime@0.2.11': @@ -7669,35 +6898,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.0 - '@npmcli/git@4.1.0': - dependencies: - '@npmcli/promise-spawn': 6.0.2 - lru-cache: 7.18.3 - npm-pick-manifest: 8.0.2 - proc-log: 3.0.0 - promise-inflight: 1.0.1 - promise-retry: 2.0.1 - semver: 7.7.2 - which: 3.0.1 - transitivePeerDependencies: - - bluebird - - '@npmcli/package-json@4.0.1': - dependencies: - '@npmcli/git': 4.1.0 - glob: 10.4.5 - hosted-git-info: 6.1.3 - json-parse-even-better-errors: 3.0.2 - normalize-package-data: 5.0.0 - proc-log: 3.0.0 - semver: 7.7.2 - transitivePeerDependencies: - - bluebird - - '@npmcli/promise-spawn@6.0.2': - dependencies: - which: 3.0.1 - '@oxc-parser/wasm@0.23.1': {} '@oxc-project/runtime@0.75.0': {} @@ -7794,65 +6994,6 @@ snapshots: dependencies: quansync: 0.2.10 - '@react-router/dev@0.0.0-experimental-23decd7bc(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.3)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(wrangler@4.41.0(@cloudflare/workers-types@4.20251003.0))(yaml@2.7.0)': - dependencies: - '@babel/core': 7.27.7 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.7 - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.27.7) - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.27.7) - '@babel/preset-typescript': 7.26.0(@babel/core@7.27.7) - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 - '@npmcli/package-json': 4.0.1 - '@react-router/node': 0.0.0-experimental-23decd7bc(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) - arg: 5.0.2 - babel-dead-code-elimination: 1.0.10 - chokidar: 4.0.3 - dedent: 1.5.3 - es-module-lexer: 1.7.0 - exit-hook: 2.2.1 - jsesc: 3.0.2 - lodash: 4.17.21 - pathe: 1.1.2 - picocolors: 1.1.1 - prettier: 2.8.8 - react-refresh: 0.14.2 - react-router: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - semver: 7.7.2 - set-cookie-parser: 2.7.1 - tinyglobby: 0.2.14 - valibot: 0.41.0(typescript@5.8.3) - vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-node: 3.2.4(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - optionalDependencies: - typescript: 5.8.3 - wrangler: 4.41.0(@cloudflare/workers-types@4.20251003.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - bluebird - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@react-router/node@0.0.0-experimental-23decd7bc(react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3)': - dependencies: - '@mjackson/node-fetch-server': 0.2.0 - react-router: 0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - typescript: 5.8.3 - - '@remix-run/node-fetch-server@0.8.1': {} - '@resvg/resvg-wasm@2.4.0': {} '@rolldown/binding-darwin-arm64@1.0.0-beta.21': @@ -8176,79 +7317,6 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tailwindcss/node@4.1.4': - dependencies: - enhanced-resolve: 5.18.1 - jiti: 2.5.1 - lightningcss: 1.29.2 - tailwindcss: 4.1.4 - - '@tailwindcss/oxide-android-arm64@4.1.4': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.4': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.4': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.4': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.4': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.4': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.4': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.4': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.4': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.4': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.4': - optional: true - - '@tailwindcss/oxide@4.1.4': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.4 - '@tailwindcss/oxide-darwin-arm64': 4.1.4 - '@tailwindcss/oxide-darwin-x64': 4.1.4 - '@tailwindcss/oxide-freebsd-x64': 4.1.4 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.4 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.4 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.4 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.4 - '@tailwindcss/oxide-linux-x64-musl': 4.1.4 - '@tailwindcss/oxide-wasm32-wasi': 4.1.4 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.4 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.4 - - '@tailwindcss/typography@0.5.16(tailwindcss@4.1.4)': - dependencies: - lodash.castarray: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - postcss-selector-parser: 6.0.10 - tailwindcss: 4.1.4 - - '@tailwindcss/vite@4.1.4(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))': - dependencies: - '@tailwindcss/node': 4.1.4 - '@tailwindcss/oxide': 4.1.4 - tailwindcss: 4.1.4 - vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - '@tanstack/history@1.99.13': {} '@tsconfig/strictest@2.0.5': {} @@ -8593,22 +7661,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/test-dep-client-in-server2@file:packages/rsc/examples/basic/test-dep/client-in-server2(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@vitejs/test-dep-client-in-server@file:packages/rsc/examples/basic/test-dep/client-in-server(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@vitejs/test-dep-server-in-client@file:packages/rsc/examples/basic/test-dep/server-in-client(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@vitejs/test-dep-server-in-server@file:packages/rsc/examples/basic/test-dep/server-in-server(react@19.1.0)': - dependencies: - react: 19.1.0 - '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -8697,7 +7749,7 @@ snapshots: '@vue/compiler-ssr': 3.5.22 '@vue/shared': 3.5.22 estree-walker: 2.0.2 - magic-string: 0.30.19 + magic-string: 0.30.21 postcss: 8.5.6 source-map-js: 1.2.1 @@ -8962,15 +8014,6 @@ snapshots: b4a@1.6.7: {} - babel-dead-code-elimination@1.0.10: - dependencies: - '@babel/core': 7.27.7 - '@babel/parser': 7.27.7 - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 - transitivePeerDependencies: - - supports-color - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -9266,8 +8309,6 @@ snapshots: dependencies: character-entities: 2.0.2 - dedent@1.5.3: {} - deep-eql@5.0.2: {} deepmerge@4.3.1: {} @@ -9416,8 +8457,6 @@ snapshots: env-paths@3.0.0: {} - err-code@2.0.3: {} - error-stack-parser-es@1.0.5: {} es-define-property@1.0.1: {} @@ -9763,8 +8802,6 @@ snapshots: get-port-please@3.2.0: {} - get-port@7.1.0: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -9924,10 +8961,6 @@ snapshots: hookable@5.5.3: {} - hosted-git-info@6.1.3: - dependencies: - lru-cache: 7.18.3 - hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -10094,8 +9127,6 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-parse-even-better-errors@3.0.2: {} - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -10170,6 +9201,7 @@ snapshots: lightningcss-linux-x64-musl: 1.29.2 lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 + optional: true lilconfig@3.1.3: {} @@ -10220,18 +9252,12 @@ snapshots: lodash-es@4.17.21: {} - lodash.castarray@4.4.0: {} - lodash.debounce@4.0.8: {} lodash.defaults@4.2.0: {} lodash.isarguments@3.1.0: {} - lodash.isplainobject@4.0.6: {} - - lodash.merge@4.6.2: {} - lodash@4.17.21: {} logform@2.7.0: @@ -10255,8 +9281,6 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@7.18.3: {} - luxon@3.7.1: {} magic-string@0.30.17: @@ -10846,13 +9870,6 @@ snapshots: dependencies: abbrev: 3.0.1 - normalize-package-data@5.0.0: - dependencies: - hosted-git-info: 6.1.3 - is-core-module: 2.16.1 - semver: 7.7.2 - validate-npm-package-license: 3.0.4 - normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 @@ -10865,26 +9882,6 @@ snapshots: normalize-path@3.0.0: {} - npm-install-checks@6.3.0: - dependencies: - semver: 7.7.2 - - npm-normalize-package-bin@3.0.1: {} - - npm-package-arg@10.1.0: - dependencies: - hosted-git-info: 6.1.3 - proc-log: 3.0.0 - semver: 7.7.2 - validate-npm-package-name: 5.0.1 - - npm-pick-manifest@8.0.2: - dependencies: - npm-install-checks: 6.3.0 - npm-normalize-package-bin: 3.0.1 - npm-package-arg: 10.1.0 - semver: 7.7.2 - npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -10940,13 +9937,6 @@ snapshots: is-inside-container: 1.0.0 is-wsl: 3.1.0 - open@10.2.0: - dependencies: - default-browser: 5.2.1 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - open@8.4.2: dependencies: define-lazy-prop: 2.0.0 @@ -11031,8 +10021,6 @@ snapshots: perfect-debounce@1.0.0: {} - perfect-debounce@2.0.0: {} - periscopic@4.0.2: dependencies: '@types/estree': 1.0.8 @@ -11095,11 +10083,6 @@ snapshots: postcss: 8.5.3 postcss-selector-parser: 6.1.2 - postcss-selector-parser@6.0.10: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 @@ -11146,23 +10129,12 @@ snapshots: transitivePeerDependencies: - supports-color - prettier@2.8.8: {} - pretty-bytes@6.1.1: {} - proc-log@3.0.0: {} - process-nextick-args@2.0.1: {} process@0.11.10: {} - promise-inflight@1.0.1: {} - - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - property-information@7.0.0: {} pump@3.0.3: @@ -11200,18 +10172,8 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 - react-refresh@0.14.2: {} - react-refresh@0.18.0: {} - react-router@0.0.0-experimental-23decd7bc(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - cookie: 1.0.2 - react: 19.1.0 - set-cookie-parser: 2.7.1 - optionalDependencies: - react-dom: 19.1.0(react@19.1.0) - react-server-dom-webpack@19.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.93.0(esbuild@0.24.2)): dependencies: acorn-loose: 8.4.0 @@ -11389,8 +10351,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - retry@0.12.0: {} - reusify@1.0.4: {} rolldown-plugin-dts@0.13.13(rolldown@1.0.0-beta.21)(typescript@5.8.3): @@ -11548,8 +10508,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@2.7.1: {} - setprototypeof@1.2.0: {} sharp@0.33.5: @@ -11804,8 +10762,6 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@4.1.4: {} - tapable@2.2.1: {} tar-stream@3.1.7: @@ -12104,11 +11060,6 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.2 - unplugin-utils@0.3.0: - dependencies: - pathe: 2.0.3 - picomatch: 4.0.3 - unplugin@1.16.1: dependencies: acorn: 8.15.0 @@ -12182,17 +11133,11 @@ snapshots: uuid@11.1.0: {} - valibot@0.41.0(typescript@5.8.3): - optionalDependencies: - typescript: 5.8.3 - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validate-npm-package-name@5.0.1: {} - vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -12249,21 +11194,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-inspect@11.3.3(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)): - dependencies: - ansis: 4.1.0 - debug: 4.4.1 - error-stack-parser-es: 1.0.5 - ohash: 2.0.11 - open: 10.2.0 - perfect-debounce: 2.0.0 - sirv: 3.0.1 - unplugin-utils: 0.3.0 - vite: 7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) - vite-dev-rpc: 1.1.0(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)) - transitivePeerDependencies: - - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.1.5(@types/node@22.16.0)(jiti@2.5.1)(lightningcss@1.29.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)): dependencies: debug: 4.4.1 @@ -12405,10 +11335,6 @@ snapshots: dependencies: isexe: 2.0.0 - which@3.0.1: - dependencies: - isexe: 2.0.0 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -12488,10 +11414,6 @@ snapshots: ws@8.18.0: {} - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.0 - y18n@5.0.8: {} yallist@3.1.1: {}