From 819ac474cc7ca15be538b10445b2691ee7a33033 Mon Sep 17 00:00:00 2001 From: Tobbe Lundberg Date: Wed, 21 Feb 2024 12:57:12 +0530 Subject: [PATCH] RSC: Refactor node-loader and some vite plugins --- .../react-server-dom-webpack/node-loader.ts | 86 ++++++++----------- packages/vite/src/rsc/rscVitePlugins.ts | 33 ++++--- 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/packages/vite/src/react-server-dom-webpack/node-loader.ts b/packages/vite/src/react-server-dom-webpack/node-loader.ts index 0e390c8ec8d6..86e53bd005e7 100644 --- a/packages/vite/src/react-server-dom-webpack/node-loader.ts +++ b/packages/vite/src/react-server-dom-webpack/node-loader.ts @@ -219,6 +219,7 @@ function transformServerModule( newSrc += '$$bound: { value: null }' newSrc += '});\n' }) + return newSrc } @@ -272,7 +273,6 @@ function resolveClientImport( // This resolution algorithm will not necessarily have the same configuration // as the actual client loader. It should mostly work and if it doesn't you can // always convert to explicit exported names instead. - const conditions = ['node', 'import'] if (stashedResolve === null) { throw new Error( @@ -283,14 +283,14 @@ function resolveClientImport( return stashedResolve( specifier, { - conditions, + conditions: ['node', 'import'], parentURL, }, stashedResolve ) } -async function parseExportNamesInto( +async function parseExportNamesIntoNames( body: any, names: Array, parentURL: string, @@ -305,31 +305,26 @@ async function parseExportNamesInto( addExportNames(names, node.exported) continue } else { - const _await$resolveClientI = await resolveClientImport( - node.source.value, - parentURL - ), - url = _await$resolveClientI.url - - const _await$loader = await loader( - url, - { - format: 'module', - conditions: [], - importAssertions: {}, - }, - loader - ), - source = _await$loader.source - - if (typeof source !== 'string') { + const clientImport = await resolveClientImport( + node.source.value, + parentURL + ) + const url = clientImport.url + const loadContext = { + format: 'module', + conditions: [], + importAssertions: {}, + } + const mod = await loader(url, loadContext, loader) + + if (typeof mod.source !== 'string') { throw new Error('Expected the transformed source to be a string.') } let childBody try { - childBody = acorn.parse(source, { + childBody = acorn.parse(mod.source, { ecmaVersion: '2024', sourceType: 'module', }).body @@ -338,7 +333,7 @@ async function parseExportNamesInto( continue } - await parseExportNamesInto(childBody, names, url, loader) + await parseExportNamesIntoNames(childBody, names, url, loader) continue } @@ -378,7 +373,10 @@ async function transformClientModule( loader: LoadFunction ): Promise { const names: Array = [] - await parseExportNamesInto(body, names, url, loader) + + // This will insert the names into the `names` array + await parseExportNamesIntoNames(body, names, url, loader) + let newSrc = "const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n" @@ -435,27 +433,23 @@ async function loadClientImport( ) } // TODO: Validate that this is another module by calling getFormat. - const _await$stashedGetSour = await stashedGetSource( - url, - { - format: 'module', - }, - stashedGetSource - ), - source = _await$stashedGetSour.source - - const result = await defaultTransformSource( - source, - { - format: 'module', - url, - }, - defaultTransformSource + const getSourceContext = { format: 'module' } + const { source } = await stashedGetSource( + url, + getSourceContext, + stashedGetSource ) - return { + const transformContext = { format: 'module', - source: result.source, + url, } + const { source: transformedSource } = await defaultTransformSource( + source, + transformContext, + defaultTransformSource + ) + + return { format: 'module', source: transformedSource } } async function transformModuleIfNeeded( @@ -465,10 +459,7 @@ async function transformModuleIfNeeded( ): Promise { // Do a quick check for the exact string. If it doesn't exist, don't // bother parsing. - if ( - source.indexOf('use client') === -1 && - source.indexOf('use server') === -1 - ) { + if (!source.includes('use client') && !source.includes('use server')) { return source } @@ -545,6 +536,7 @@ export async function transformSource( return loadClientImport(url, defaultTransformSource) } ) + return { source: newSrc, } @@ -579,5 +571,3 @@ export async function load( return result } - -// export { getSource, load, resolve, transformSource } diff --git a/packages/vite/src/rsc/rscVitePlugins.ts b/packages/vite/src/rsc/rscVitePlugins.ts index b2bdd5bf920c..e3b99a936e0b 100644 --- a/packages/vite/src/rsc/rscVitePlugins.ts +++ b/packages/vite/src/rsc/rscVitePlugins.ts @@ -36,7 +36,13 @@ export function rscIndexPlugin(): Plugin { export function rscTransformPlugin(): Plugin { return { name: 'rsc-transform-plugin', + // TODO(RSC): Seems like resolveId() is never called. Can we remove it? async resolveId(id, importer, options) { + console.log( + 'rscVitePlugins - rscTransformPlugin::resolveId()', + id, + options + ) if (!id.endsWith('.js')) { return id } @@ -80,8 +86,21 @@ export function rscTransformPlugin(): Plugin { return { url } } + const context = { + conditions: ['react-server'], + parentURL: '', + } + + // Calling `resolve` here stashes the resolve function for use with + // `RSDWNodeLoader.load()` below + RSDWNodeLoader.resolve('', context, resolve) + const load = async (url: string) => { - let source = url === id ? code : (await this.load({ id: url })).code + let source: string | null = code + + if (url !== id) { + source = (await this.load({ id: url })).code + } if (!source) { throw new Error(`Failed to load ${url}`) @@ -92,18 +111,12 @@ export function rscTransformPlugin(): Plugin { /^(import {.*?} from ".*?";)\s*"use (client|server)";/, '"use $2";$1' ) + return { format: 'module', source } } - RSDWNodeLoader.resolve( - '', - { conditions: ['react-server'], parentURL: '' }, - resolve - ) - - const source = (await RSDWNodeLoader.load(id, null, load)).source - - return source + const mod = await RSDWNodeLoader.load(id, null, load) + return mod.source }, } }