From eb39b14bcaaa8d00abc31c45dd52ba60fb11e8a5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 3 Sep 2025 19:08:17 +0800 Subject: [PATCH 01/11] chore(rsc): add middleware mode example --- .../plugin-rsc/e2e/middleware-mode.test.ts | 60 +++++++++++++++++++ .../examples/starter/src/framework/dev.ts | 21 +++++++ 2 files changed, 81 insertions(+) create mode 100644 packages/plugin-rsc/e2e/middleware-mode.test.ts create mode 100644 packages/plugin-rsc/examples/starter/src/framework/dev.ts diff --git a/packages/plugin-rsc/e2e/middleware-mode.test.ts b/packages/plugin-rsc/e2e/middleware-mode.test.ts new file mode 100644 index 000000000..03b816ef7 --- /dev/null +++ b/packages/plugin-rsc/e2e/middleware-mode.test.ts @@ -0,0 +1,60 @@ +import { test } from '@playwright/test' +import { setupInlineFixture, useFixture } from './fixture' +import { defineStarterTest } from './starter' + +test.describe(() => { + const root = 'examples/e2e/temp/module-runner-hmr-false' + + test.beforeAll(async () => { + await setupInlineFixture({ + src: 'examples/starter', + dest: root, + files: { + 'vite.config.base.ts': { cp: 'vite.config.ts' }, + 'vite.config.ts': /* js */ ` + import { defineConfig, mergeConfig, createRunnableDevEnvironment } from 'vite' + import baseConfig from './vite.config.base.ts' + + const overrideConfig = defineConfig({ + environments: { + ssr: { + dev: { + createEnvironment(name, config) { + return createRunnableDevEnvironment(name, config, { + runnerOptions: { + hmr: false, + }, + }) + }, + }, + }, + rsc: { + dev: { + createEnvironment(name, config) { + return createRunnableDevEnvironment(name, config, { + runnerOptions: { + hmr: false, + }, + }) + }, + }, + }, + }, + }) + + export default mergeConfig(baseConfig, overrideConfig) + `, + }, + }) + }) + + test.describe('dev-middleware-mode', () => { + const f = useFixture({ root, mode: 'dev' }) + defineStarterTest(f) + }) + + test.describe('build-middleware-mode', () => { + const f = useFixture({ root, mode: 'build' }) + defineStarterTest(f) + }) +}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/dev.ts b/packages/plugin-rsc/examples/starter/src/framework/dev.ts new file mode 100644 index 000000000..092268883 --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/dev.ts @@ -0,0 +1,21 @@ +import assert from 'node:assert' +import { createServer, isRunnableDevEnvironment } from 'vite' + +async function main() { + const viteServer = await createServer({ + server: { middlewareMode: true }, + rsc: { serverHandler: false }, + }) + assert(isRunnableDevEnvironment(viteServer.environments.rsc)) + const entry = await viteServer.environments.rsc.runner.import< + typeof import('./entry.rsc') + >('/src/framework/entry.rsc.tsx') + entry.default + + viteServer +} + +main().catch((e) => { + console.error(e) + process.exitCode = 1 +}) From a08a3ccb9e84561650578f1314353c1cc1696363 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 3 Sep 2025 23:43:25 +0800 Subject: [PATCH 02/11] wip: dev --- .../plugin-rsc/examples/starter/package.json | 2 + .../examples/starter/src/framework/dev.ts | 33 +++++-- .../examples/starter/src/framework/types.d.ts | 4 + pnpm-lock.yaml | 97 +++++++++++++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 packages/plugin-rsc/examples/starter/src/framework/types.d.ts diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index d8584de0d..2393b1fae 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -5,6 +5,7 @@ "license": "MIT", "type": "module", "scripts": { + "dev-custom": "node ./src/framework/dev.ts", "dev": "vite", "build": "vite build", "preview": "vite preview" @@ -18,6 +19,7 @@ "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "latest", "@vitejs/plugin-rsc": "latest", + "connect": "^3.7.0", "rsc-html-stream": "^0.0.7", "vite": "^7.1.3" } diff --git a/packages/plugin-rsc/examples/starter/src/framework/dev.ts b/packages/plugin-rsc/examples/starter/src/framework/dev.ts index 092268883..adc4085d0 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/dev.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/dev.ts @@ -1,18 +1,39 @@ import assert from 'node:assert' -import { createServer, isRunnableDevEnvironment } from 'vite' +import { createServer, isRunnableDevEnvironment, type Connect } from 'vite' +// @ts-ignore +import connect from 'connect' +import { createRequestListener } from '@remix-run/node-fetch-server' async function main() { const viteServer = await createServer({ server: { middlewareMode: true }, rsc: { serverHandler: false }, }) + assert(isRunnableDevEnvironment(viteServer.environments.rsc)) - const entry = await viteServer.environments.rsc.runner.import< - typeof import('./entry.rsc') - >('/src/framework/entry.rsc.tsx') - entry.default + const runner = viteServer.environments.rsc.runner + + const app = connect() as Connect.Server - viteServer + app.use(viteServer.middlewares) + + app.use(async (req, res, next) => { + try { + const entry = await runner.import( + '/src/framework/entry.rsc.tsx', + ) + await createRequestListener(entry.default)(req, res) + } catch (e) { + next(e) + } + }) + + app.listen(3000, () => { + console.log('listening on http://localhost:3000') + }) + app.on('error', (err) => { + console.error(err) + }) } main().catch((e) => { diff --git a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts new file mode 100644 index 000000000..05a099446 --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts @@ -0,0 +1,4 @@ +declare module 'connect' { + const default_: () => import('vite').Connect.Server + export default default_ +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79342728d..cf2a1c062 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -712,6 +712,9 @@ importers: '@vitejs/plugin-rsc': specifier: latest version: link:../.. + connect: + specifier: ^3.7.0 + version: 3.7.0 rsc-html-stream: specifier: ^0.0.7 version: 0.0.7 @@ -2778,6 +2781,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + conventional-changelog-conventionalcommits@9.0.0: resolution: {integrity: sha512-5e48V0+DsWvQBEnnbBFhYQwYDzFPXVrakGPP1uSxekDkr5d7YWrmaWsgJpKFR0SkXmxK6qQr9O42uuLb9wpKxA==} engines: {node: '>=18'} @@ -2842,6 +2849,14 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -2904,6 +2919,9 @@ packages: oxc-resolver: optional: true + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.129: resolution: {integrity: sha512-JlXUemX4s0+9f8mLqib/bHH8gOHf5elKS6KeWG3sk3xozb/JTq/RLXIv8OKUWiK4Ah00Wm88EFj5PYkFr4RUPA==} @@ -2914,6 +2932,10 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2945,6 +2967,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -3127,6 +3152,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -3712,6 +3741,9 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3758,6 +3790,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -3796,6 +3832,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4203,6 +4243,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -4431,6 +4475,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-utils@0.3.0: resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} engines: {node: '>=20.19.0'} @@ -4455,6 +4503,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -6235,6 +6287,15 @@ snapshots: concat-map@0.0.1: {} + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + conventional-changelog-conventionalcommits@9.0.0: dependencies: compare-func: 2.0.0 @@ -6301,6 +6362,10 @@ snapshots: data-uri-to-buffer@4.0.1: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.1: dependencies: ms: 2.1.3 @@ -6340,12 +6405,16 @@ snapshots: dts-resolver@2.1.2: {} + ee-first@1.1.1: {} + electron-to-chromium@1.5.129: {} emoji-regex@10.3.0: {} empathic@2.0.0: {} + encodeurl@1.0.2: {} + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -6419,6 +6488,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@9.34.0(jiti@2.5.1)): @@ -6647,6 +6718,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-root@1.1.0: {} find-up@5.0.0: @@ -7385,6 +7468,8 @@ snapshots: mrmime@2.0.1: {} + ms@2.0.0: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -7419,6 +7504,10 @@ snapshots: ohash@2.0.11: {} + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -7475,6 +7564,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -7881,6 +7972,8 @@ snapshots: stackback@0.0.2: {} + statuses@1.5.0: {} + std-env@3.9.0: {} stoppable@1.1.0: {} @@ -8114,6 +8207,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + unplugin-utils@0.3.0: dependencies: pathe: 2.0.3 @@ -8159,6 +8254,8 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 From 096e72c37439628884f9b3899daf9290ef4ae6a1 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 3 Sep 2025 23:54:06 +0800 Subject: [PATCH 03/11] chore: comment --- packages/plugin-rsc/examples/starter/src/framework/dev.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/plugin-rsc/examples/starter/src/framework/dev.ts b/packages/plugin-rsc/examples/starter/src/framework/dev.ts index adc4085d0..7304a8c5c 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/dev.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/dev.ts @@ -7,6 +7,7 @@ import { createRequestListener } from '@remix-run/node-fetch-server' async function main() { const viteServer = await createServer({ server: { middlewareMode: true }, + // TODO: disable `serverHandler` on `middlewareMode` automatically? rsc: { serverHandler: false }, }) From 3337f01afc9e1bdcc39bbaa98de558bb59f27921 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 00:21:42 +0800 Subject: [PATCH 04/11] wip: prod --- .../plugin-rsc/examples/starter/package.json | 4 +- .../starter/src/framework/main-build.ts | 46 +++++++++++++++++++ .../src/framework/{dev.ts => main-dev.ts} | 1 - pnpm-lock.yaml | 3 ++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-rsc/examples/starter/src/framework/main-build.ts rename packages/plugin-rsc/examples/starter/src/framework/{dev.ts => main-dev.ts} (93%) diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index 2393b1fae..a773dad86 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -5,7 +5,8 @@ "license": "MIT", "type": "module", "scripts": { - "dev-custom": "node ./src/framework/dev.ts", + "custom-dev": "node ./src/framework/main-dev.ts", + "custom-start": "NODE_ENV=production node ./src/framework/main-build.ts", "dev": "vite", "build": "vite build", "preview": "vite preview" @@ -21,6 +22,7 @@ "@vitejs/plugin-rsc": "latest", "connect": "^3.7.0", "rsc-html-stream": "^0.0.7", + "sirv": "^3.0.1", "vite": "^7.1.3" } } diff --git a/packages/plugin-rsc/examples/starter/src/framework/main-build.ts b/packages/plugin-rsc/examples/starter/src/framework/main-build.ts new file mode 100644 index 000000000..ba83d74ca --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/main-build.ts @@ -0,0 +1,46 @@ +// @ts-ignore +import connect from 'connect' +import { createRequestListener } from '@remix-run/node-fetch-server' +import type { Connect } from 'vite' +import sirv from 'sirv' +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +async function main() { + const app = connect() as Connect.Server + + const entry = (await import( + pathToFileURL(path.resolve('dist/rsc/index.js')).href + )) as typeof import('./entry.rsc.js') + const entryHandler = createRequestListener(entry.default) + + // https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/preview.ts#L237 + app.use( + sirv('./dist/client', { + etag: true, + dev: true, + extensions: [], + ignores: false, + }), + ) + + app.use(async (req, res, next) => { + try { + await entryHandler(req, res) + } catch (e) { + next(e) + } + }) + + app.listen(3000, () => { + console.log('listening on http://localhost:3000') + }) + app.on('error', (err) => { + console.error(err) + }) +} + +main().catch((e) => { + console.error(e) + process.exitCode = 1 +}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/dev.ts b/packages/plugin-rsc/examples/starter/src/framework/main-dev.ts similarity index 93% rename from packages/plugin-rsc/examples/starter/src/framework/dev.ts rename to packages/plugin-rsc/examples/starter/src/framework/main-dev.ts index 7304a8c5c..adc4085d0 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/dev.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/main-dev.ts @@ -7,7 +7,6 @@ import { createRequestListener } from '@remix-run/node-fetch-server' async function main() { const viteServer = await createServer({ server: { middlewareMode: true }, - // TODO: disable `serverHandler` on `middlewareMode` automatically? rsc: { serverHandler: false }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf2a1c062..89d815381 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -718,6 +718,9 @@ importers: rsc-html-stream: specifier: ^0.0.7 version: 0.0.7 + sirv: + specifier: ^3.0.1 + version: 3.0.1 vite: specifier: ^7.1.3 version: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1) From 3b078ea264d5a83b9d8506f4664a2068694a9025 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 10:42:41 +0800 Subject: [PATCH 05/11] wip --- .../plugin-rsc/examples/starter/package.json | 4 +- .../examples/starter/src/framework/cli.ts | 33 ++++++++++++++ .../examples/starter/src/framework/main.ts | 44 +++++++++++++++++++ .../examples/starter/src/framework/types.d.ts | 5 +++ .../examples/starter/vite.config.ts | 39 ++++++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-rsc/examples/starter/src/framework/cli.ts create mode 100644 packages/plugin-rsc/examples/starter/src/framework/main.ts diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index a773dad86..6657748a2 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -5,8 +5,8 @@ "license": "MIT", "type": "module", "scripts": { - "custom-dev": "node ./src/framework/main-dev.ts", - "custom-start": "NODE_ENV=production node ./src/framework/main-build.ts", + "custom-dev": "node ./src/framework/cli.ts dev", + "custom-start": "NODE_ENV=production node ./src/framework/cli.ts start", "dev": "vite", "build": "vite build", "preview": "vite preview" diff --git a/packages/plugin-rsc/examples/starter/src/framework/cli.ts b/packages/plugin-rsc/examples/starter/src/framework/cli.ts new file mode 100644 index 000000000..19deda3a5 --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/cli.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert' +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +async function main() { + const command = process.argv[2] + + if (command === 'dev') { + const { createServer, isRunnableDevEnvironment } = await import('vite') + const server = await createServer({ + server: { middlewareMode: true }, + }) + assert(isRunnableDevEnvironment(server.environments.rsc)) + const runner = server.environments.rsc.runner + const entry = (await runner.import( + '/src/framework/main.ts', + )) as typeof import('./main') + await entry.default(server) + } else if (command === 'start') { + const entry = (await import( + pathToFileURL(path.resolve('dist/rsc/main.js')).href + )) as typeof import('./main') + await entry.default() + } else { + console.error(`Unknown command: ${command}`) + process.exitCode = 1 + } +} + +main().catch((e) => { + console.error(e) + process.exitCode = 1 +}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/main.ts b/packages/plugin-rsc/examples/starter/src/framework/main.ts new file mode 100644 index 000000000..fcbff665a --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/main.ts @@ -0,0 +1,44 @@ +// @ts-ignore +import connect from 'connect' +import type { Connect, ViteDevServer } from 'vite' +import sirv from 'sirv' +import handler from './entry.rsc.tsx' +import { createRequestListener } from '@remix-run/node-fetch-server' + +export default async function start(viteDevServer?: ViteDevServer) { + const app = connect() as Connect.Server + + if (viteDevServer) { + app.use(viteDevServer.middlewares) + } else { + // https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/preview.ts#L237 + app.use( + sirv('./dist/client', { + etag: true, + dev: true, + extensions: [], + ignores: false, + }), + ) + } + + const handlerListener = createRequestListener(handler) + app.use(async (req, res, next) => { + try { + await handlerListener(req, res) + } catch (e) { + next(e) + } + }) + app.listen(3000, () => { + console.log('listening on http://localhost:3000') + }) + app.on('error', (err) => { + console.error(err) + }) +} + +// TODO: hold off `rsc:update` util next server listen +// if (import.meta.hot) { +// import.meta.hot.accept() +// } diff --git a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts index 05a099446..d34626767 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts @@ -2,3 +2,8 @@ declare module 'connect' { const default_: () => import('vite').Connect.Server export default default_ } + +declare module 'virtual:middleware-mode/handler' { + const default_: import('vite').Connect.NextHandleFunction + export default default_ +} diff --git a/packages/plugin-rsc/examples/starter/vite.config.ts b/packages/plugin-rsc/examples/starter/vite.config.ts index 99837202c..342e1faba 100644 --- a/packages/plugin-rsc/examples/starter/vite.config.ts +++ b/packages/plugin-rsc/examples/starter/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ // 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 + serverHandler: false, }), // use any of react plugins https://github.com/vitejs/vite-plugin-react @@ -21,6 +22,43 @@ export default defineConfig({ // use https://github.com/antfu-collective/vite-plugin-inspect // to understand internal transforms required for RSC. // inspect(), + + { + name: 'middleware-mode-helper', + configureServer(server) { + ;(globalThis as any).__viteDevServer = server + }, + resolveId(source) { + if (source.startsWith('virtual:middleware-mode/')) { + return '\0' + source + } + }, + load(id) { + if (id === '\0virtual:middleware-mode/handler') { + this.environment.mode === 'dev' + return `\ +import connect from 'connect' +import { createRequestListener } from '@remix-run/node-fetch-server' +import handler from "/src/framework/entry.rsc"; + +const app = connect(); + +const listener = createRequestListener(handler); + +app.use +app.use(async (req, res, next) => { + try { + await listner(req, res); + } catch (e) { + next(e); + } +}); + +export default app; +` + } + }, + }, ], // specify entry point for each environment. @@ -35,6 +73,7 @@ export default defineConfig({ rollupOptions: { input: { index: './src/framework/entry.rsc.tsx', + main: './src/framework/main.ts', }, }, }, From cacd48b074f68310a54eadb39de8d3f1c2e8ce96 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 10:53:49 +0800 Subject: [PATCH 06/11] more thoughts --- .../examples/starter/src/framework/cli.ts | 14 ++++++- .../starter/src/framework/entry.rsc.tsx | 4 -- .../examples/starter/src/framework/main.ts | 17 +++------ .../examples/starter/src/framework/types.d.ts | 5 --- .../examples/starter/vite.config.ts | 38 ------------------- 5 files changed, 18 insertions(+), 60 deletions(-) diff --git a/packages/plugin-rsc/examples/starter/src/framework/cli.ts b/packages/plugin-rsc/examples/starter/src/framework/cli.ts index 19deda3a5..ba86c2e89 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/cli.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/cli.ts @@ -8,19 +8,29 @@ async function main() { if (command === 'dev') { const { createServer, isRunnableDevEnvironment } = await import('vite') const server = await createServer({ + clearScreen: false, server: { middlewareMode: true }, + rsc: { serverHandler: false }, }) assert(isRunnableDevEnvironment(server.environments.rsc)) const runner = server.environments.rsc.runner const entry = (await runner.import( '/src/framework/main.ts', )) as typeof import('./main') - await entry.default(server) + // TODO: how to restart? + const app = await entry.createApp(server) + app.listen(3000, () => { + console.log('listening on http://localhost:3000') + }) + app.on('error', (err) => { + console.error(err) + }) } else if (command === 'start') { const entry = (await import( pathToFileURL(path.resolve('dist/rsc/main.js')).href )) as typeof import('./main') - await entry.default() + const app = await entry.createApp() + app } else { console.error(`Unknown command: ${command}`) process.exitCode = 1 diff --git a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx index fa1c27845..287e08dbd 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx +++ b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx @@ -105,7 +105,3 @@ export default async function handler(request: Request): Promise { }, }) } - -if (import.meta.hot) { - import.meta.hot.accept() -} diff --git a/packages/plugin-rsc/examples/starter/src/framework/main.ts b/packages/plugin-rsc/examples/starter/src/framework/main.ts index fcbff665a..90ecf7b0a 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/main.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/main.ts @@ -5,7 +5,7 @@ import sirv from 'sirv' import handler from './entry.rsc.tsx' import { createRequestListener } from '@remix-run/node-fetch-server' -export default async function start(viteDevServer?: ViteDevServer) { +export async function createApp(viteDevServer?: ViteDevServer) { const app = connect() as Connect.Server if (viteDevServer) { @@ -30,15 +30,10 @@ export default async function start(viteDevServer?: ViteDevServer) { next(e) } }) - app.listen(3000, () => { - console.log('listening on http://localhost:3000') - }) - app.on('error', (err) => { - console.error(err) - }) + + return app } -// TODO: hold off `rsc:update` util next server listen -// if (import.meta.hot) { -// import.meta.hot.accept() -// } +if (import.meta.hot) { + import.meta.hot.accept() +} diff --git a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts index d34626767..05a099446 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts +++ b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts @@ -2,8 +2,3 @@ declare module 'connect' { const default_: () => import('vite').Connect.Server export default default_ } - -declare module 'virtual:middleware-mode/handler' { - const default_: import('vite').Connect.NextHandleFunction - export default default_ -} diff --git a/packages/plugin-rsc/examples/starter/vite.config.ts b/packages/plugin-rsc/examples/starter/vite.config.ts index 342e1faba..84c6410b4 100644 --- a/packages/plugin-rsc/examples/starter/vite.config.ts +++ b/packages/plugin-rsc/examples/starter/vite.config.ts @@ -12,7 +12,6 @@ export default defineConfig({ // 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 - serverHandler: false, }), // use any of react plugins https://github.com/vitejs/vite-plugin-react @@ -22,43 +21,6 @@ export default defineConfig({ // use https://github.com/antfu-collective/vite-plugin-inspect // to understand internal transforms required for RSC. // inspect(), - - { - name: 'middleware-mode-helper', - configureServer(server) { - ;(globalThis as any).__viteDevServer = server - }, - resolveId(source) { - if (source.startsWith('virtual:middleware-mode/')) { - return '\0' + source - } - }, - load(id) { - if (id === '\0virtual:middleware-mode/handler') { - this.environment.mode === 'dev' - return `\ -import connect from 'connect' -import { createRequestListener } from '@remix-run/node-fetch-server' -import handler from "/src/framework/entry.rsc"; - -const app = connect(); - -const listener = createRequestListener(handler); - -app.use -app.use(async (req, res, next) => { - try { - await listner(req, res); - } catch (e) { - next(e); - } -}); - -export default app; -` - } - }, - }, ], // specify entry point for each environment. From 3d3fb3d0d6d5c3124b71f2b27ccff3ddc461faa4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 11:01:49 +0800 Subject: [PATCH 07/11] simplify --- .../plugin-rsc/examples/starter/package.json | 4 +- .../examples/starter/src/framework/cli.ts | 43 ----------------- .../examples/starter/src/framework/custom.ts | 45 ++++++++++++++++++ .../starter/src/framework/main-build.ts | 46 ------------------- .../starter/src/framework/main-dev.ts | 42 ----------------- .../examples/starter/src/framework/main.ts | 39 ---------------- .../examples/starter/vite.config.ts | 1 - 7 files changed, 47 insertions(+), 173 deletions(-) delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/cli.ts create mode 100644 packages/plugin-rsc/examples/starter/src/framework/custom.ts delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/main-build.ts delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/main-dev.ts delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/main.ts diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index 6657748a2..1f01c22f7 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -5,8 +5,8 @@ "license": "MIT", "type": "module", "scripts": { - "custom-dev": "node ./src/framework/cli.ts dev", - "custom-start": "NODE_ENV=production node ./src/framework/cli.ts start", + "custom-dev": "node ./src/framework/custom.ts dev", + "custom-start": "NODE_ENV=production node ./src/framework/custom.ts start", "dev": "vite", "build": "vite build", "preview": "vite preview" diff --git a/packages/plugin-rsc/examples/starter/src/framework/cli.ts b/packages/plugin-rsc/examples/starter/src/framework/cli.ts deleted file mode 100644 index ba86c2e89..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/cli.ts +++ /dev/null @@ -1,43 +0,0 @@ -import assert from 'node:assert' -import path from 'node:path' -import { pathToFileURL } from 'node:url' - -async function main() { - const command = process.argv[2] - - if (command === 'dev') { - const { createServer, isRunnableDevEnvironment } = await import('vite') - const server = await createServer({ - clearScreen: false, - server: { middlewareMode: true }, - rsc: { serverHandler: false }, - }) - assert(isRunnableDevEnvironment(server.environments.rsc)) - const runner = server.environments.rsc.runner - const entry = (await runner.import( - '/src/framework/main.ts', - )) as typeof import('./main') - // TODO: how to restart? - const app = await entry.createApp(server) - app.listen(3000, () => { - console.log('listening on http://localhost:3000') - }) - app.on('error', (err) => { - console.error(err) - }) - } else if (command === 'start') { - const entry = (await import( - pathToFileURL(path.resolve('dist/rsc/main.js')).href - )) as typeof import('./main') - const app = await entry.createApp() - app - } else { - console.error(`Unknown command: ${command}`) - process.exitCode = 1 - } -} - -main().catch((e) => { - console.error(e) - process.exitCode = 1 -}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/custom.ts b/packages/plugin-rsc/examples/starter/src/framework/custom.ts new file mode 100644 index 000000000..4dc3af232 --- /dev/null +++ b/packages/plugin-rsc/examples/starter/src/framework/custom.ts @@ -0,0 +1,45 @@ +import path from 'node:path' +import { pathToFileURL } from 'node:url' +// @ts-ignore +import connect from 'connect' +import { createRequestListener } from '@remix-run/node-fetch-server' +import sirv from 'sirv' + +async function main() { + const app = connect() + const command = process.argv[2] + if (command === 'dev') { + const { createServer } = await import('vite') + const server = await createServer({ + clearScreen: false, + server: { middlewareMode: true }, + }) + app.use(server.middlewares) + } else if (command === 'start') { + app.use( + sirv('./dist/client', { + etag: true, + dev: true, + extensions: [], + ignores: false, + }), + ) + const entry = (await import( + pathToFileURL(path.resolve('dist/rsc/index.js')).href + )) as typeof import('./entry.rsc') + app.use(createRequestListener(entry.default)) + } else { + console.error(`Unknown command: ${command}`) + process.exitCode = 1 + return + } + + const port = process.env.PORT || 3000 + app.listen(port) + console.log(`Server listening on port ${port} (http://localhost:${port})`) +} + +main().catch((e) => { + console.error(e) + process.exitCode = 1 +}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/main-build.ts b/packages/plugin-rsc/examples/starter/src/framework/main-build.ts deleted file mode 100644 index ba83d74ca..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/main-build.ts +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-ignore -import connect from 'connect' -import { createRequestListener } from '@remix-run/node-fetch-server' -import type { Connect } from 'vite' -import sirv from 'sirv' -import path from 'node:path' -import { pathToFileURL } from 'node:url' - -async function main() { - const app = connect() as Connect.Server - - const entry = (await import( - pathToFileURL(path.resolve('dist/rsc/index.js')).href - )) as typeof import('./entry.rsc.js') - const entryHandler = createRequestListener(entry.default) - - // https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/preview.ts#L237 - app.use( - sirv('./dist/client', { - etag: true, - dev: true, - extensions: [], - ignores: false, - }), - ) - - app.use(async (req, res, next) => { - try { - await entryHandler(req, res) - } catch (e) { - next(e) - } - }) - - app.listen(3000, () => { - console.log('listening on http://localhost:3000') - }) - app.on('error', (err) => { - console.error(err) - }) -} - -main().catch((e) => { - console.error(e) - process.exitCode = 1 -}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/main-dev.ts b/packages/plugin-rsc/examples/starter/src/framework/main-dev.ts deleted file mode 100644 index adc4085d0..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/main-dev.ts +++ /dev/null @@ -1,42 +0,0 @@ -import assert from 'node:assert' -import { createServer, isRunnableDevEnvironment, type Connect } from 'vite' -// @ts-ignore -import connect from 'connect' -import { createRequestListener } from '@remix-run/node-fetch-server' - -async function main() { - const viteServer = await createServer({ - server: { middlewareMode: true }, - rsc: { serverHandler: false }, - }) - - assert(isRunnableDevEnvironment(viteServer.environments.rsc)) - const runner = viteServer.environments.rsc.runner - - const app = connect() as Connect.Server - - app.use(viteServer.middlewares) - - app.use(async (req, res, next) => { - try { - const entry = await runner.import( - '/src/framework/entry.rsc.tsx', - ) - await createRequestListener(entry.default)(req, res) - } catch (e) { - next(e) - } - }) - - app.listen(3000, () => { - console.log('listening on http://localhost:3000') - }) - app.on('error', (err) => { - console.error(err) - }) -} - -main().catch((e) => { - console.error(e) - process.exitCode = 1 -}) diff --git a/packages/plugin-rsc/examples/starter/src/framework/main.ts b/packages/plugin-rsc/examples/starter/src/framework/main.ts deleted file mode 100644 index 90ecf7b0a..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/main.ts +++ /dev/null @@ -1,39 +0,0 @@ -// @ts-ignore -import connect from 'connect' -import type { Connect, ViteDevServer } from 'vite' -import sirv from 'sirv' -import handler from './entry.rsc.tsx' -import { createRequestListener } from '@remix-run/node-fetch-server' - -export async function createApp(viteDevServer?: ViteDevServer) { - const app = connect() as Connect.Server - - if (viteDevServer) { - app.use(viteDevServer.middlewares) - } else { - // https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/preview.ts#L237 - app.use( - sirv('./dist/client', { - etag: true, - dev: true, - extensions: [], - ignores: false, - }), - ) - } - - const handlerListener = createRequestListener(handler) - app.use(async (req, res, next) => { - try { - await handlerListener(req, res) - } catch (e) { - next(e) - } - }) - - return app -} - -if (import.meta.hot) { - import.meta.hot.accept() -} diff --git a/packages/plugin-rsc/examples/starter/vite.config.ts b/packages/plugin-rsc/examples/starter/vite.config.ts index 84c6410b4..99837202c 100644 --- a/packages/plugin-rsc/examples/starter/vite.config.ts +++ b/packages/plugin-rsc/examples/starter/vite.config.ts @@ -35,7 +35,6 @@ export default defineConfig({ rollupOptions: { input: { index: './src/framework/entry.rsc.tsx', - main: './src/framework/main.ts', }, }, }, From ba98b4566457c58d4dc26289b804730e37f8b7bc Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 11:24:38 +0800 Subject: [PATCH 08/11] test: add e2e --- .../plugin-rsc/e2e/middleware-mode.test.ts | 54 ++++++------------- .../examples/e2e/middleware-mode.ts | 46 ++++++++++++++++ packages/plugin-rsc/examples/e2e/package.json | 4 +- .../plugin-rsc/examples/e2e/tsconfig.json | 12 +++++ pnpm-lock.yaml | 6 +++ 5 files changed, 84 insertions(+), 38 deletions(-) create mode 100644 packages/plugin-rsc/examples/e2e/middleware-mode.ts create mode 100644 packages/plugin-rsc/examples/e2e/tsconfig.json diff --git a/packages/plugin-rsc/e2e/middleware-mode.test.ts b/packages/plugin-rsc/e2e/middleware-mode.test.ts index 03b816ef7..7b9224796 100644 --- a/packages/plugin-rsc/e2e/middleware-mode.test.ts +++ b/packages/plugin-rsc/e2e/middleware-mode.test.ts @@ -3,58 +3,38 @@ import { setupInlineFixture, useFixture } from './fixture' import { defineStarterTest } from './starter' test.describe(() => { - const root = 'examples/e2e/temp/module-runner-hmr-false' + const root = 'examples/e2e/temp/middleware-mode' test.beforeAll(async () => { await setupInlineFixture({ src: 'examples/starter', dest: root, files: { - 'vite.config.base.ts': { cp: 'vite.config.ts' }, - 'vite.config.ts': /* js */ ` - import { defineConfig, mergeConfig, createRunnableDevEnvironment } from 'vite' - import baseConfig from './vite.config.base.ts' - - const overrideConfig = defineConfig({ - environments: { - ssr: { - dev: { - createEnvironment(name, config) { - return createRunnableDevEnvironment(name, config, { - runnerOptions: { - hmr: false, - }, - }) - }, - }, - }, - rsc: { - dev: { - createEnvironment(name, config) { - return createRunnableDevEnvironment(name, config, { - runnerOptions: { - hmr: false, - }, - }) - }, - }, - }, - }, - }) - - export default mergeConfig(baseConfig, overrideConfig) - `, + 'middleware-mode.ts': { cp: '../../middleware-mode.ts' }, }, }) }) test.describe('dev-middleware-mode', () => { - const f = useFixture({ root, mode: 'dev' }) + const f = useFixture({ + root, + mode: 'dev', + command: 'node ./middleware-mode.ts dev', + }) defineStarterTest(f) }) test.describe('build-middleware-mode', () => { - const f = useFixture({ root, mode: 'build' }) + const f = useFixture({ + root, + mode: 'build', + command: 'node ./middleware-mode.ts start', + cliOptions: { + env: { + NODE_ENV: 'production', + }, + }, + }) defineStarterTest(f) }) }) diff --git a/packages/plugin-rsc/examples/e2e/middleware-mode.ts b/packages/plugin-rsc/examples/e2e/middleware-mode.ts new file mode 100644 index 000000000..2b0c38cd4 --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/middleware-mode.ts @@ -0,0 +1,46 @@ +import path from 'node:path' +import { pathToFileURL } from 'node:url' +// @ts-ignore +import connect from 'connect' +import { createRequestListener } from '@remix-run/node-fetch-server' +import sirv from 'sirv' +import type { Connect } from 'vite' + +async function main() { + const app = connect() as Connect.Server + const command = process.argv[2] + if (command === 'dev') { + const { createServer } = await import('vite') + const server = await createServer({ + clearScreen: false, + server: { middlewareMode: true }, + }) + app.use(server.middlewares) + } else if (command === 'start') { + app.use( + sirv('./dist/client', { + etag: true, + dev: true, + extensions: [], + ignores: false, + }), + ) + const entry = await import( + pathToFileURL(path.resolve('dist/rsc/index.js')).href + ) + app.use(createRequestListener(entry.default)) + } else { + console.error(`Unknown command: ${command}`) + process.exitCode = 1 + return + } + + const port = process.env.PORT || 3000 + app.listen(port) + console.log(`Server started at http://localhost:${port}`) +} + +main().catch((e) => { + console.error(e) + process.exitCode = 1 +}) diff --git a/packages/plugin-rsc/examples/e2e/package.json b/packages/plugin-rsc/examples/e2e/package.json index d6a1c36fe..182c23908 100644 --- a/packages/plugin-rsc/examples/e2e/package.json +++ b/packages/plugin-rsc/examples/e2e/package.json @@ -5,6 +5,8 @@ "devDependencies": { "@vitejs/plugin-react": "latest", "@vitejs/plugin-rsc": "latest", - "babel-plugin-react-compiler": "19.1.0-rc.3" + "babel-plugin-react-compiler": "19.1.0-rc.3", + "connect": "^3.7.0", + "sirv": "^3.0.1" } } diff --git a/packages/plugin-rsc/examples/e2e/tsconfig.json b/packages/plugin-rsc/examples/e2e/tsconfig.json new file mode 100644 index 000000000..49a0459e3 --- /dev/null +++ b/packages/plugin-rsc/examples/e2e/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["*.ts"], + "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": false, + "checkJs": false, + "declaration": true, + "isolatedDeclarations": true, + "jsx": "react-jsx" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89d815381..400ec64f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -597,6 +597,12 @@ importers: babel-plugin-react-compiler: specifier: 19.1.0-rc.3 version: 19.1.0-rc.3 + connect: + specifier: ^3.7.0 + version: 3.7.0 + sirv: + specifier: ^3.0.1 + version: 3.0.1 packages/plugin-rsc/examples/no-ssr: dependencies: From 000b4b93b3e2dc27e865267410f3c3a4605fd1e6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 11:26:25 +0800 Subject: [PATCH 09/11] chore: cleanup --- .../plugin-rsc/e2e/middleware-mode.test.ts | 7 +-- .../plugin-rsc/examples/starter/package.json | 2 - .../examples/starter/src/framework/custom.ts | 45 ------------------- 3 files changed, 2 insertions(+), 52 deletions(-) delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/custom.ts diff --git a/packages/plugin-rsc/e2e/middleware-mode.test.ts b/packages/plugin-rsc/e2e/middleware-mode.test.ts index 7b9224796..12b75e0ef 100644 --- a/packages/plugin-rsc/e2e/middleware-mode.test.ts +++ b/packages/plugin-rsc/e2e/middleware-mode.test.ts @@ -9,9 +9,6 @@ test.describe(() => { await setupInlineFixture({ src: 'examples/starter', dest: root, - files: { - 'middleware-mode.ts': { cp: '../../middleware-mode.ts' }, - }, }) }) @@ -19,7 +16,7 @@ test.describe(() => { const f = useFixture({ root, mode: 'dev', - command: 'node ./middleware-mode.ts dev', + command: 'node ../../middleware-mode.ts dev', }) defineStarterTest(f) }) @@ -28,7 +25,7 @@ test.describe(() => { const f = useFixture({ root, mode: 'build', - command: 'node ./middleware-mode.ts start', + command: 'node ../../middleware-mode.ts start', cliOptions: { env: { NODE_ENV: 'production', diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index 1f01c22f7..edfe46067 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -5,8 +5,6 @@ "license": "MIT", "type": "module", "scripts": { - "custom-dev": "node ./src/framework/custom.ts dev", - "custom-start": "NODE_ENV=production node ./src/framework/custom.ts start", "dev": "vite", "build": "vite build", "preview": "vite preview" diff --git a/packages/plugin-rsc/examples/starter/src/framework/custom.ts b/packages/plugin-rsc/examples/starter/src/framework/custom.ts deleted file mode 100644 index 4dc3af232..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/custom.ts +++ /dev/null @@ -1,45 +0,0 @@ -import path from 'node:path' -import { pathToFileURL } from 'node:url' -// @ts-ignore -import connect from 'connect' -import { createRequestListener } from '@remix-run/node-fetch-server' -import sirv from 'sirv' - -async function main() { - const app = connect() - const command = process.argv[2] - if (command === 'dev') { - const { createServer } = await import('vite') - const server = await createServer({ - clearScreen: false, - server: { middlewareMode: true }, - }) - app.use(server.middlewares) - } else if (command === 'start') { - app.use( - sirv('./dist/client', { - etag: true, - dev: true, - extensions: [], - ignores: false, - }), - ) - const entry = (await import( - pathToFileURL(path.resolve('dist/rsc/index.js')).href - )) as typeof import('./entry.rsc') - app.use(createRequestListener(entry.default)) - } else { - console.error(`Unknown command: ${command}`) - process.exitCode = 1 - return - } - - const port = process.env.PORT || 3000 - app.listen(port) - console.log(`Server listening on port ${port} (http://localhost:${port})`) -} - -main().catch((e) => { - console.error(e) - process.exitCode = 1 -}) From 8b854410578af6202a3c255cd2310f1d805c8f63 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 11:27:03 +0800 Subject: [PATCH 10/11] chore: cleanup --- .../plugin-rsc/examples/starter/src/framework/entry.rsc.tsx | 4 ++++ packages/plugin-rsc/examples/starter/src/framework/types.d.ts | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 packages/plugin-rsc/examples/starter/src/framework/types.d.ts diff --git a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx index 287e08dbd..fa1c27845 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx +++ b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx @@ -105,3 +105,7 @@ export default async function handler(request: Request): Promise { }, }) } + +if (import.meta.hot) { + import.meta.hot.accept() +} diff --git a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts b/packages/plugin-rsc/examples/starter/src/framework/types.d.ts deleted file mode 100644 index 05a099446..000000000 --- a/packages/plugin-rsc/examples/starter/src/framework/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'connect' { - const default_: () => import('vite').Connect.Server - export default default_ -} From c277101d89922a7ddd1a8948bf2ea4ecd81fd3e5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 4 Sep 2025 11:27:28 +0800 Subject: [PATCH 11/11] chore: cleanup --- packages/plugin-rsc/examples/starter/package.json | 2 -- pnpm-lock.yaml | 6 ------ 2 files changed, 8 deletions(-) diff --git a/packages/plugin-rsc/examples/starter/package.json b/packages/plugin-rsc/examples/starter/package.json index edfe46067..d8584de0d 100644 --- a/packages/plugin-rsc/examples/starter/package.json +++ b/packages/plugin-rsc/examples/starter/package.json @@ -18,9 +18,7 @@ "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "latest", "@vitejs/plugin-rsc": "latest", - "connect": "^3.7.0", "rsc-html-stream": "^0.0.7", - "sirv": "^3.0.1", "vite": "^7.1.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 400ec64f8..4e0929a5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -718,15 +718,9 @@ importers: '@vitejs/plugin-rsc': specifier: latest version: link:../.. - connect: - specifier: ^3.7.0 - version: 3.7.0 rsc-html-stream: specifier: ^0.0.7 version: 0.0.7 - sirv: - specifier: ^3.0.1 - version: 3.0.1 vite: specifier: ^7.1.3 version: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.7.1)