diff --git a/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/dep.tsx b/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/dep.tsx new file mode 100644 index 000000000..e6d3de7ef --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/dep.tsx @@ -0,0 +1,3 @@ +export default function Dep() { + return <>test-import-meta-glob +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/server.tsx b/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/server.tsx new file mode 100644 index 000000000..a9ec558a2 --- /dev/null +++ b/packages/plugin-rsc/examples/basic/src/routes/import-meta-glob/server.tsx @@ -0,0 +1,4 @@ +export async function TestImportMetaGlob() { + const mod: any = await Object.values(import.meta.glob('./dep.tsx'))[0]() + return +} diff --git a/packages/plugin-rsc/examples/basic/src/routes/root.tsx b/packages/plugin-rsc/examples/basic/src/routes/root.tsx index 04b74343c..70e6e0112 100644 --- a/packages/plugin-rsc/examples/basic/src/routes/root.tsx +++ b/packages/plugin-rsc/examples/basic/src/routes/root.tsx @@ -36,6 +36,7 @@ import { TestHmrSharedServer } from './hmr-shared/server' import { TestHmrSharedClient } from './hmr-shared/client' import { TestHmrSharedAtomic } from './hmr-shared/atomic/server' import { TestCssQueries } from './css-queries/server' +import { TestImportMetaGlob } from './import-meta-glob/server' export function Root(props: { url: URL }) { return ( @@ -85,6 +86,7 @@ export function Root(props: { url: URL }) { + ) diff --git a/packages/plugin-rsc/examples/basic/vite.config.ts b/packages/plugin-rsc/examples/basic/vite.config.ts index 258a0e3ef..b6d3cc084 100644 --- a/packages/plugin-rsc/examples/basic/vite.config.ts +++ b/packages/plugin-rsc/examples/basic/vite.config.ts @@ -131,6 +131,7 @@ export default { fetch: handler }; } }, }, + testScanPlugin(), ], build: { minify: false, @@ -158,6 +159,38 @@ export default { fetch: handler }; }, }) as any +function testScanPlugin(): Plugin[] { + const moduleIds: { name: string; ids: string[] }[] = [] + return [ + { + name: 'test-scan', + apply: 'build', + buildEnd() { + moduleIds.push({ + name: this.environment.name, + ids: [...this.getModuleIds()], + }) + }, + buildApp: { + order: 'post', + async handler() { + // client scan build discovers additional modules for server references. + const [m1, m2] = moduleIds.filter((m) => m.name === 'rsc') + const diff = m2.ids.filter((id) => !m1.ids.includes(id)) + assert(diff.length > 0) + + // but make sure it's not due to import.meta.glob + // https://github.com/vitejs/rolldown-vite/issues/373 + assert.equal( + diff.find((id) => id.includes('import-meta-glob/dep.tsx')), + undefined, + ) + }, + }, + }, + ] +} + function vitePluginUseCache(): Plugin[] { return [ { diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index acdf6ab87..559ec7d38 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -40,6 +40,7 @@ import { import { cjsModuleRunnerPlugin } from './plugins/cjs' import { evalValue, parseIdQuery } from './plugins/utils' import { createDebug } from '@hiogawa/utils' +import { transformScanBuildStrip } from './plugins/scan' // state for build orchestration let serverReferences: Record = {} @@ -901,19 +902,16 @@ globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage; ] } +// During scan build, we strip all code but imports to +// traverse module graph faster and just discover client/server references. function scanBuildStripPlugin(): Plugin { return { name: 'rsc:scan-strip', apply: 'build', enforce: 'post', - transform(code, _id, _options) { + async 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('') + const output = await transformScanBuildStrip(code) return { code: output, map: { mappings: '' } } }, } diff --git a/packages/plugin-rsc/src/plugins/scan.test.ts b/packages/plugin-rsc/src/plugins/scan.test.ts new file mode 100644 index 000000000..f56737329 --- /dev/null +++ b/packages/plugin-rsc/src/plugins/scan.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { transformScanBuildStrip } from './scan' + +describe(transformScanBuildStrip, () => { + it('basic', async () => { + const input = `\ +import { a } from "a"; +import "b"; +import(String("c")) +import.meta.glob("d", { + query: "?e", +}) +import.meta.globee("d", { query: "?e" }) +export default "foo"; +` + expect(await transformScanBuildStrip(input)).toMatchInlineSnapshot(` + "import "a"; + import "b"; + console.log(import.meta.glob("d", { + query: "?e", + })); + " + `) + }) +}) diff --git a/packages/plugin-rsc/src/plugins/scan.ts b/packages/plugin-rsc/src/plugins/scan.ts new file mode 100644 index 000000000..bfaa6aaf7 --- /dev/null +++ b/packages/plugin-rsc/src/plugins/scan.ts @@ -0,0 +1,41 @@ +import * as esModuleLexer from 'es-module-lexer' +import { parseAstAsync } from 'vite' +import { walk } from 'estree-walker' + +// https://github.com/vitejs/vite/blob/86d2e8be50be535494734f9f5f5236c61626b308/packages/vite/src/node/plugins/importMetaGlob.ts#L113 +const importGlobRE = /\bimport\.meta\.glob(?:<\w+>)?\s*\(/g + +export async function transformScanBuildStrip(code: string): Promise { + const [imports] = esModuleLexer.parse(code) + let output = imports + .map((e) => e.n && `import ${JSON.stringify(e.n)};\n`) + .filter(Boolean) + .join('') + + // preserve import.meta.glob for rolldown-vite + // https://github.com/vitejs/rolldown-vite/issues/373 + if (importGlobRE.test(code)) { + const ast = await parseAstAsync(code) + walk(ast, { + enter(node) { + if ( + node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + node.callee.object.type === 'MetaProperty' && + node.callee.object.meta.type === 'Identifier' && + node.callee.object.meta.name === 'import' && + node.callee.object.property.type === 'Identifier' && + node.callee.object.property.name === 'meta' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'glob' + ) { + const importMetaGlob = code.slice(node.start, node.end) + output += `console.log(${importMetaGlob});\n` + } + }, + }) + output += '' + } + + return output +}