diff --git a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.function-scope.test.ts b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.function-scope.test.ts index a154b3347981..f7d26f380a47 100644 --- a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.function-scope.test.ts +++ b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.function-scope.test.ts @@ -19,8 +19,8 @@ afterAll(() => { process.env.RWJS_CWD = RWJS_CWD }) -function getPluginTransform() { - const plugin = rscTransformUseServerPlugin() +function getPluginTransform(serverEntryFiles: Record) { + const plugin = rscTransformUseServerPlugin('some/dist/path', serverEntryFiles) if (typeof plugin.transform !== 'function') { throw new Error('Plugin does not have a transform function') @@ -33,12 +33,14 @@ function getPluginTransform() { return plugin.transform.bind({} as TransformPluginContext) } -const pluginTransform = getPluginTransform() +const id = 'rw-app/web/src/some/path/to/actions.ts' +const pluginTransform = getPluginTransform({ + 'rsa-actions.ts-0': id, +}) describe('rscTransformUseServerPlugin function scoped "use server"', () => { describe('top-level exports', () => { it('should handle named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -65,12 +67,11 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); } import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "formAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction");" `) }) it('should handle arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -97,12 +98,11 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "formAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction");" `) }) it('should handle default exported named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -129,12 +129,11 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); } import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "default");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "default");" `) }) it('should handle exports with two consts', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -162,13 +161,12 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(fortyTwo, "some/path/to/actions.ts", "fortyTwo"); - registerServerReference(formAction, "some/path/to/actions.ts", "formAction");" + registerServerReference(fortyTwo, "some/dist/path/assets/rsa-actions.ts-0.mjs", "fortyTwo"); + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction");" `) }) it('should handle named function and arrow function with separate export', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -213,15 +211,14 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "formAction"); - registerServerReference(arrowAction, "some/path/to/actions.ts", "arrowAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction"); + registerServerReference(arrowAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "arrowAction");" `) }) it.todo( "should handle named function and 'let' arrow function with separate export", async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -280,14 +277,13 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { // Not 'use server' anymore }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "formAction"); - if (typeof letArrowFunction === "function") registerServerReference(letArrowAction, "some/path/to/actions.ts", "letArrowAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction"); + if (typeof letArrowFunction === "function") registerServerReference(letArrowAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "letArrowAction");" `) }, ) it('should handle separate renamed export', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -327,13 +323,12 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }; export { formAction as fA, arrowAction }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "fA"); - registerServerReference(arrowAction, "some/path/to/actions.ts", "arrowAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "fA"); + registerServerReference(arrowAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "arrowAction");" `) }) it.todo('should handle default exported arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -365,14 +360,13 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","formAction"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); " `) }) describe('without "use server"', () => { it('should not register named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -401,7 +395,6 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }) it('should not register arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -430,7 +423,6 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }) it('should not register default exported named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -459,7 +451,6 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }) it('should not register exports with two consts', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -483,12 +474,11 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); }; import { registerServerReference } from "react-server-dom-webpack/server"; - registerServerReference(formAction, "some/path/to/actions.ts", "formAction");" + registerServerReference(formAction, "some/dist/path/assets/rsa-actions.ts-0.mjs", "formAction");" `) }) it('should not register named function and arrow function with separate export', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -525,7 +515,6 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }) it('should not register separate renamed export', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -564,7 +553,6 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { }) it('should not register default exported arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` import fs from 'node:fs' @@ -597,7 +585,11 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { describe('actions inside components', async () => { it('should handle self-contained named function inside default exported component', async () => { - const id = 'some/path/to/Component.tsx' + const id = 'rw-app/web/src/some/path/to/Component.tsx' + const pluginTransform = getPluginTransform({ + 'rsa-Component.tsx-0': id, + }) + const input = ` import fs from 'node:fs' @@ -640,7 +632,7 @@ describe('rscTransformUseServerPlugin function scoped "use server"', () => { await fs.promises.writeFile('settings.json', \`{ "delay": \${formData.get('delay')} }\`); } - registerServerReference(__rwjs__rsa0_formAction, "some/path/to/Component.tsx", "__rwjs__rsa0_formAction");" + registerServerReference(__rwjs__rsa0_formAction, "some/dist/path/assets/rsa-Component.tsx-0.mjs", "__rwjs__rsa0_formAction");" `) }) diff --git a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.test.ts b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.test.ts index ab43327f5524..5ed8ae724901 100644 --- a/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.test.ts +++ b/packages/vite/src/plugins/__tests__/vite-plugin-rsc-transform-server.test.ts @@ -27,8 +27,8 @@ afterAll(() => { process.env.RWJS_CWD = RWJS_CWD }) -function getPluginTransform() { - const plugin = rscTransformUseServerPlugin() +function getPluginTransform(serverEntryFiles: Record) { + const plugin = rscTransformUseServerPlugin('some/dist/path', serverEntryFiles) if (typeof plugin.transform !== 'function') { throw new Error('Plugin does not have a transform function') @@ -41,7 +41,10 @@ function getPluginTransform() { return plugin.transform.bind({} as TransformPluginContext) } -const pluginTransform = getPluginTransform() +const id = 'rw-app/web/src/some/path/to/actions.ts' +const pluginTransform = getPluginTransform({ + 'rsa-actions.ts-0': id, +}) describe('rscTransformUseServerPlugin module scoped "use server"', () => { afterEach(() => { @@ -49,7 +52,6 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { }) it('should handle one function', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -77,13 +79,12 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - registerServerReference(formAction,"some/path/to/actions.ts","formAction"); + registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); " `) }) it('should handle two functions', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -125,14 +126,13 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - registerServerReference(formAction1,"some/path/to/actions.ts","formAction1"); - registerServerReference(formAction2,"some/path/to/actions.ts","formAction2"); + registerServerReference(formAction1,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction1"); + registerServerReference(formAction2,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction2"); " `) }) it('should handle arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -160,13 +160,12 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","formAction"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); " `) }) it('should handle exports with two consts', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -194,14 +193,13 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof fortyTwo === "function") registerServerReference(fortyTwo,"some/path/to/actions.ts","fortyTwo"); - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","formAction"); + if (typeof fortyTwo === "function") registerServerReference(fortyTwo,"some/dist/path/assets/rsa-actions.ts-0.mjs","fortyTwo"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); " `) }) it('should handle named function and arrow function with separate export', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -247,14 +245,13 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { export { formAction, arrowAction } import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","formAction"); - if (typeof arrowAction === "function") registerServerReference(arrowAction,"some/path/to/actions.ts","arrowAction"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); + if (typeof arrowAction === "function") registerServerReference(arrowAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","arrowAction"); " `) }) it('should handle separate renamed export', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -300,14 +297,13 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { export { formAction as fA, arrowAction } import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","fA"); - if (typeof arrowAction === "function") registerServerReference(arrowAction,"some/path/to/actions.ts","arrowAction"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","fA"); + if (typeof arrowAction === "function") registerServerReference(arrowAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","arrowAction"); " `) }) it.todo('should handle default exported arrow function', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -335,13 +331,12 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - registerServerReference(default,"some/path/to/actions.ts","default"); + registerServerReference(default,"some/dist/path/assets/rsa-actions.ts-0.mjs","default"); " `) }) it('should handle default exported named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` "use server" @@ -373,13 +368,12 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { export default formAction import {registerServerReference} from "react-server-dom-webpack/server"; - if (typeof formAction === "function") registerServerReference(formAction,"some/path/to/actions.ts","default"); + if (typeof formAction === "function") registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","default"); " `) }) it('should handle default exported inline-named function', async () => { - const id = 'some/path/to/actions.ts' const input = ` "use server" @@ -407,13 +401,12 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - registerServerReference(formAction,"some/path/to/actions.ts","default"); + registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","default"); " `) }) it.todo('should handle default exported anonymous function', async () => { - const id = 'some/path/to/actions.ts' const input = ` 'use server' @@ -441,7 +434,7 @@ describe('rscTransformUseServerPlugin module scoped "use server"', () => { } import {registerServerReference} from "react-server-dom-webpack/server"; - registerServerReference(formAction,"some/path/to/actions.ts","formAction"); + registerServerReference(formAction,"some/dist/path/assets/rsa-actions.ts-0.mjs","formAction"); " `) }) diff --git a/packages/vite/src/plugins/vite-plugin-rsc-transform-server.ts b/packages/vite/src/plugins/vite-plugin-rsc-transform-server.ts index b5c49f6cc0cf..8daa1deabf16 100644 --- a/packages/vite/src/plugins/vite-plugin-rsc-transform-server.ts +++ b/packages/vite/src/plugins/vite-plugin-rsc-transform-server.ts @@ -1,9 +1,14 @@ +import path from 'node:path' + import * as babel from '@babel/core' import type { PluginObj, types } from '@babel/core' import * as swc from '@swc/core' import type { Plugin } from 'vite' -export function rscTransformUseServerPlugin(): Plugin { +export function rscTransformUseServerPlugin( + outDir: string, + serverEntryFiles: Record, +): Plugin { return { name: 'rsc-transform-use-server-plugin', transform: async function (code, id) { @@ -60,18 +65,48 @@ export function rscTransformUseServerPlugin(): Plugin { ) } + // We need to handle both urls (`id`s) to files in node_modules, files + // already built by Vite (at least for now, with our hybrid dev/prod + // setup), and files in /src that will be built + let builtFileName = id + + const serverEntryKey = Object.entries(serverEntryFiles).find( + ([_key, value]) => value === id, + )?.[0] + + if (serverEntryKey) { + // We output server actions in the `assets` subdirectory, and add a .mjs + // extension to the file name + builtFileName = path.join(outDir, 'assets', serverEntryKey + '.mjs') + + if (process.platform === 'win32') { + builtFileName = builtFileName.replaceAll('\\', '/') + } + } + + console.log('windows paths') + console.log('windows paths, outDir:', outDir) + console.log('windows paths, severEntryFiles:', serverEntryFiles) + console.log('windows paths, severEntryKey:', serverEntryKey) + console.log('windows paths, builtFileName:', builtFileName) + console.log('windows paths') + + if (!builtFileName) { + throw new Error( + `Could not find ${id} in serverEntryFiles: ` + + JSON.stringify(serverEntryFiles), + ) + } + let transformedCode = code if (moduleScopedUseServer) { - transformedCode = transformServerModule(mod, id, code) + transformedCode = transformServerModule(mod, builtFileName, code) } else { - // transformedCode = transformServerFunction(mod, id, code) - const result = babel.transformSync(code, { filename: id, - // presets: ['@babel/preset-typescript', '@babel/preset-react'], presets: ['@babel/preset-typescript'], - plugins: [[babelPluginTransformServerAction, { url: id }]], + plugins: [[babelPluginTransformServerAction, { url: builtFileName }]], }) if (!result) { diff --git a/packages/vite/src/rsc/rscBuildForServer.ts b/packages/vite/src/rsc/rscBuildForServer.ts index ec49631c04bb..b924d1dc255f 100644 --- a/packages/vite/src/rsc/rscBuildForServer.ts +++ b/packages/vite/src/rsc/rscBuildForServer.ts @@ -31,6 +31,9 @@ export async function rscBuildForServer( throw new Error('Server Entry file not found') } + /** Base path for where to place built artifacts */ + const outDir = rwPaths.web.distRsc + // TODO (RSC): No redwood-vite plugin, add it in here const rscServerBuildOutput = await viteBuild({ envFile: false, @@ -84,7 +87,7 @@ export async function rscBuildForServer( // That's why it needs the `clientEntryFiles` data // (It does other things as well, but that's why it needs clientEntryFiles) rscTransformUseClientPlugin(clientEntryFiles), - rscTransformUseServerPlugin(), + rscTransformUseServerPlugin(outDir, serverEntryFiles), rscRoutesImports(), ], build: { @@ -92,7 +95,7 @@ export async function rscBuildForServer( minify: false, ssr: true, ssrEmitAssets: true, - outDir: rwPaths.web.distRsc, + outDir, emptyOutDir: true, // Needed because `outDir` is not inside `root` manifest: 'server-build-manifest.json', rollupOptions: { diff --git a/packages/vite/src/rsc/rscWorker.ts b/packages/vite/src/rsc/rscWorker.ts index 47b89ceb3b2b..282d19484644 100644 --- a/packages/vite/src/rsc/rscWorker.ts +++ b/packages/vite/src/rsc/rscWorker.ts @@ -154,7 +154,7 @@ const vitePromise = createServer({ parentPort.postMessage(message) }), rscTransformUseClientPlugin({}), - rscTransformUseServerPlugin(), + rscTransformUseServerPlugin('', {}), rscRoutesAutoLoader(), ], ssr: { @@ -182,11 +182,8 @@ const shutdown = async () => { parentPort.close() } -const loadServerFile = async (fname: string) => { - const vite = await vitePromise - // TODO (RSC): In prod we shouldn't need this. We should be able to just - // import the built files - return vite.ssrLoadModule(fname) +async function loadServerFile(filePath: string) { + return import(`file://${filePath}`) } if (!parentPort) {