Skip to content

fix(rsc): keep import.meta.glob during scan build for rolldown-vite #721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Dep() {
return <>test-import-meta-glob</>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export async function TestImportMetaGlob() {
const mod: any = await Object.values(import.meta.glob('./dep.tsx'))[0]()
return <mod.default />
}
2 changes: 2 additions & 0 deletions packages/plugin-rsc/examples/basic/src/routes/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -85,6 +86,7 @@ export function Root(props: { url: URL }) {
<TestUseCache />
<TestReactCache url={props.url} />
<TestCssQueries />
<TestImportMetaGlob />
</body>
</html>
)
Expand Down
33 changes: 33 additions & 0 deletions packages/plugin-rsc/examples/basic/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export default { fetch: handler };
}
},
},
testScanPlugin(),
],
build: {
minify: false,
Expand Down Expand Up @@ -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 [
{
Expand Down
12 changes: 5 additions & 7 deletions packages/plugin-rsc/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {}
Expand Down Expand Up @@ -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: '' } }
},
}
Expand Down
25 changes: 25 additions & 0 deletions packages/plugin-rsc/src/plugins/scan.test.ts
Original file line number Diff line number Diff line change
@@ -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",
}));
"
`)
})
})
41 changes: 41 additions & 0 deletions packages/plugin-rsc/src/plugins/scan.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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
}
Loading