Skip to content

feat: add import.meta.resolve support for bundle config loader #20515

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Implement import.meta.resolve support for bundle config loader
Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com>
  • Loading branch information
Copilot and sapphi-red committed Jul 31, 2025
commit e6021072e246162e380e2b05d6e1c7c8ff886e76
20 changes: 20 additions & 0 deletions packages/vite/src/node/__tests__/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,11 +814,31 @@
expect(result?.config.define).toHaveProperty('IMPORT_META_URL')
expect(result?.config.define).toHaveProperty('IMPORT_META_RESOLVE_TEST')
expect(typeof result?.config.define.IMPORT_META_RESOLVE_TEST).toBe('string')
expect(result?.config.define.IMPORT_META_RESOLVE_TEST).toMatch(

Check failure on line 817 in packages/vite/src/node/__tests__/config.spec.ts

View workflow job for this annotation

GitHub Actions / Build&Test: node-22, windows-latest

packages/vite/src/node/__tests__/config.spec.ts > loadConfigFromFile > loadConfigFromFile with import.meta.resolve

AssertionError: expected 'file:///D:/D:/a/vite/vite/packages/vi…' to match /test-module\.js$/ - Expected: /test-module\.js$/ + Received: "file:///D:/D:/a/vite/vite/packages/vite/src/node/__tests__/fixtures/config/import-meta-resolve/test-module" ❯ packages/vite/src/node/__tests__/config.spec.ts:817:60
/test-module\.js$/,
)
})

test('loadConfigFromFile with import.meta.resolve advanced cases', async () => {
const result = await loadConfigFromFile(
{ command: 'build', mode: 'production' },
path.resolve(fixtures, './import-meta-resolve/vite.advanced.mjs'),
path.resolve(fixtures, './import-meta-resolve'),
)
expect(result).toBeTruthy()
expect(result?.config).toHaveProperty('define')

// Check all resolved paths
expect(result?.config.define.RESOLVED_MODULE).toMatch(/test-module\.js$/)

Check failure on line 832 in packages/vite/src/node/__tests__/config.spec.ts

View workflow job for this annotation

GitHub Actions / Build&Test: node-22, windows-latest

packages/vite/src/node/__tests__/config.spec.ts > loadConfigFromFile > loadConfigFromFile with import.meta.resolve advanced cases

AssertionError: expected 'file:///D:/D:/a/vite/vite/packages/vi…' to match /test-module\.js$/ - Expected: /test-module\.js$/ + Received: "file:///D:/D:/a/vite/vite/packages/vite/src/node/__tests__/fixtures/config/import-meta-resolve/test-module" ❯ packages/vite/src/node/__tests__/config.spec.ts:832:51
expect(result?.config.define.RESOLVED_WITH_EXT).toMatch(/test-module\.js$/)
expect(result?.config.define.BASE_URL).toMatch(/vite\.advanced\.mjs$/)

// Both should resolve to the same file
expect(result?.config.define.RESOLVED_MODULE).toBe(
result?.config.define.RESOLVED_WITH_EXT,
)
})

describe('loadConfigFromFile with configLoader: native', () => {
const fixtureRoot = path.resolve(fixtures, './native-import')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Test config that uses import.meta.resolve with different cases
export default {
define: {
// Basic usage
RESOLVED_MODULE: import.meta.resolve('./test-module'),
// With explicit extension
RESOLVED_WITH_EXT: import.meta.resolve('./test-module.js'),
// URL context
BASE_URL: import.meta.url,
},
}
67 changes: 48 additions & 19 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2038,28 +2038,21 @@ async function bundleConfigFile(
setup(build) {
build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => {
const contents = await fsp.readFile(args.path, 'utf-8')

// Create a working import.meta.resolve function
const importMetaResolver = await createImportMetaResolver()
const fileUrl = pathToFileURL(args.path).href

// Create a serializable resolve function
const resolveFunction = importMetaResolver
? `(function(specifier, parent) {
// This will be replaced with actual implementation
return __vite_import_meta_resolve_impl(specifier, parent || ${JSON.stringify(fileUrl)});
})`
: `(function(specifier, parent) {
throw new Error('[config] "import.meta.resolve" is not supported.');
})`

const injectValues =
`const ${dirnameVarName} = ${JSON.stringify(
path.dirname(args.path),
)};` +
`const ${filenameVarName} = ${JSON.stringify(args.path)};` +
`const ${importMetaUrlVarName} = ${JSON.stringify(fileUrl)};` +
`const ${importMetaResolveVarName} = ${resolveFunction};`
`const ${importMetaResolveVarName} = (function(specifier, parent) {
// Use Node.js built-in import.meta.resolve when available
if (typeof globalThis !== 'undefined' && globalThis.__vite_import_meta_resolve_impl) {
return globalThis.__vite_import_meta_resolve_impl(specifier, parent || ${JSON.stringify(fileUrl)});
}
throw new Error('[config] "import.meta.resolve" is not supported.');
});`

return {
loader: args.path.endsWith('ts') ? 'ts' : 'js',
Expand Down Expand Up @@ -2106,13 +2099,49 @@ async function loadConfigFromBundledFile(
// Set up import.meta.resolve support
await setupImportMetaResolverForConfig()

// Create global resolver implementation
const importMetaResolver = await createImportMetaResolver()
if (importMetaResolver) {
// @ts-expect-error - adding global function for config loading
globalThis.__vite_import_meta_resolve_impl = importMetaResolver
// Create simpler resolver implementation using Node.js built-in capabilities
const createSimpleResolver = () => {
// Try to use native import.meta.resolve if available
try {
const testResolve = eval('import.meta.resolve')
if (typeof testResolve === 'function') {
return (specifier: string, parent: string) => {
return testResolve(specifier, parent)
}
}
} catch {
// Fall back to require.resolve for relative paths
}

return (specifier: string, parent: string) => {
if (specifier.startsWith('.')) {
// Handle relative imports
const parentDir = path.dirname(parent.replace(/^file:\/\//, ''))
let resolved = path.resolve(parentDir, specifier)

// Try to add extension if not present
if (!path.extname(resolved)) {
// Try common extensions
const extensions = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']
for (const ext of extensions) {
const withExt = resolved + ext
if (fs.existsSync(withExt)) {
resolved = withExt
break
}
}
}

return pathToFileURL(resolved).href
}
// For non-relative imports, just return as is or throw
throw new Error(`[config] Cannot resolve "${specifier}" from "${parent}"`)
}
}

// @ts-expect-error - adding global function for config loading
globalThis.__vite_import_meta_resolve_impl = createSimpleResolver()

try {
// for esm, before we can register loaders without requiring users to run node
// with --experimental-loader themselves, we have to do a hack here:
Expand Down
Loading