From 3aba00766fb2d0e91ba699e1468ed72d5ba89311 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 16:39:12 +0900 Subject: [PATCH 01/15] test: add dynamic import test --- playground/config/__tests__/config.spec.ts | 21 +++++++++++++++++++ .../packages/entry/vite.config.dynamic.ts | 6 ++++++ .../config/packages/siblings/dynamic.js | 1 + playground/config/packages/siblings/ok.js | 1 + 4 files changed, 29 insertions(+) create mode 100644 playground/config/packages/entry/vite.config.dynamic.ts create mode 100644 playground/config/packages/siblings/dynamic.js create mode 100644 playground/config/packages/siblings/ok.js diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index b4a665e87e41e8..ece1e1ff83ad4c 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -47,3 +47,24 @@ it.runIf(isImportAttributesSupported)( `) }, ) + +it('dynamic import with non-static arguments', async () => { + const { config } = (await loadConfigFromFile( + { command: 'serve', mode: 'development' }, + resolve(__dirname, '../packages/entry/vite.config.dynamic.ts'), + )) as any + expect(await config.rawImport('../siblings/ok.js')).toMatchInlineSnapshot(` + { + "default": "ok", + } + `) + expect(await config.rawImport('@vite/test-config-plugin-module-condition')) + .toMatchInlineSnapshot(` + { + "default": "import condition", + } + `) + await expect(() => config.siblingsDynamic('./ok.js')).rejects.toThrow( + 'Cannot find module', + ) +}) diff --git a/playground/config/packages/entry/vite.config.dynamic.ts b/playground/config/packages/entry/vite.config.dynamic.ts new file mode 100644 index 00000000000000..ed7601c4870222 --- /dev/null +++ b/playground/config/packages/entry/vite.config.dynamic.ts @@ -0,0 +1,6 @@ +import siblingsDynamic from '../siblings/dynamic.js' + +export default { + rawImport: (id: string) => import(id), + siblingsDynamic, +} diff --git a/playground/config/packages/siblings/dynamic.js b/playground/config/packages/siblings/dynamic.js new file mode 100644 index 00000000000000..de0315ff8d79e0 --- /dev/null +++ b/playground/config/packages/siblings/dynamic.js @@ -0,0 +1 @@ +export default (id) => import(id) diff --git a/playground/config/packages/siblings/ok.js b/playground/config/packages/siblings/ok.js new file mode 100644 index 00000000000000..60c71f346d9a3e --- /dev/null +++ b/playground/config/packages/siblings/ok.js @@ -0,0 +1 @@ +export default 'ok' From df9814934a037f8ae510d2abfef00c4440aeb21a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 16:46:00 +0900 Subject: [PATCH 02/15] wip: transformViteConfigDynamicImport --- packages/vite/src/node/config.ts | 77 ++++++++++++++++++++++++++++++++ packages/vite/src/node/index.ts | 1 + 2 files changed, 78 insertions(+) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e38e5b5959809b..74a66da399db08 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -10,6 +10,8 @@ import type { Alias, AliasOptions } from 'dep-types/alias' import aliasPlugin from '@rollup/plugin-alias' import { build } from 'esbuild' import type { RollupOptions } from 'rollup' +import { init, parse } from 'es-module-lexer' +import MagicString from 'magic-string' import { withTrailingSlash } from '../shared/utils' import { CLIENT_ENTRY, @@ -1220,6 +1222,80 @@ async function bundleConfigFile( } } +async function transformViteConfigDynamicImport( + text: string, + importer: string, +): Promise { + if (!/import\s*\(/.test(text)) { + return text + } + await init + const [imports] = parse(text) + const output = new MagicString(text) + for (const imp of imports) { + if (imp.d >= 0 && typeof imp.n === 'undefined') { + // dynamic import should be resolved relative to the original file but + // that was never the case so here we're doing the same to resolve it from current config file. + output.update( + imp.ss, + imp.d, + `__vite_config_import_helper__.bind(null, ${JSON.stringify(importer)})`, + ) + } + } + if (output.hasChanged()) { + const vitePath = pathToFileURL( + path.resolve( + _require.resolve('vite/package.json'), + '../dist/node/index.js', + ), + ).href + output.prepend( + `import { __vite_config_import_helper__ } from ${JSON.stringify(vitePath)};`, + ) + text = output.toString() + } + return text +} + +export async function __vite_config_import_helper__( + importer: string, + id: string, +): Promise { + let resolved: string + if (isBuiltin(id) || id[0] === '/') { + resolved = id + } else if (id[0] === '.') { + resolved = path.resolve(path.dirname(importer), id) + } else { + const result = tryNodeResolve( + id, + importer, + { + root: path.dirname(importer), + isBuild: true, + isProduction: true, + preferRelative: false, + tryIndex: true, + mainFields: [], + conditions: [], + overrideConditions: ['node'], + dedupe: [], + extensions: DEFAULT_EXTENSIONS, + preserveSymlinks: false, + packageCache: new Map(), + isRequire: false, + }, + false, + ) + if (!result) { + throw new Error(`Failed to resolve dynamic import '${id}'`) + } + resolved = result.id + } + return import(resolved) +} + interface NodeModuleWithCompile extends NodeModule { _compile(code: string, filename: string): any } @@ -1234,6 +1310,7 @@ async function loadConfigFromBundledFile( // with --experimental-loader themselves, we have to do a hack here: // write it to disk, load it with native Node ESM, then delete the file. if (isESM) { + bundledCode = await transformViteConfigDynamicImport(bundledCode, fileName) const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random() .toString(16) .slice(2)}` diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 3d17f8737379d6..9e9f341de1e705 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -7,6 +7,7 @@ export { loadConfigFromFile, resolveConfig, sortUserPlugins, + __vite_config_import_helper__, } from './config' export { createServer } from './server' export { preview } from './preview' From 729169871fc6b8de01cc4e2875c4b95383e6687f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 16:55:04 +0900 Subject: [PATCH 03/15] fix: use `data:` imports from https://github.com/vitejs/vite/issues/9470 --- packages/vite/src/node/config.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 74a66da399db08..e451a2b1806b5f 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1309,18 +1309,18 @@ async function loadConfigFromBundledFile( // for esm, before we can register loaders without requiring users to run node // with --experimental-loader themselves, we have to do a hack here: // write it to disk, load it with native Node ESM, then delete the file. + // convert to base64, load it with native Node ESM. if (isESM) { bundledCode = await transformViteConfigDynamicImport(bundledCode, fileName) - const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random() - .toString(16) - .slice(2)}` - const fileNameTmp = `${fileBase}.mjs` - const fileUrl = `${pathToFileURL(fileBase)}.mjs` - await fsp.writeFile(fileNameTmp, bundledCode) try { - return (await import(fileUrl)).default - } finally { - fs.unlink(fileNameTmp, () => {}) // Ignore errors + return ( + await import( + 'data:text/javascript;base64,' + + Buffer.from(bundledCode).toString('base64') + ) + ).default + } catch (e) { + throw new Error(`${e.message} at ${fileName}`) } } // for cjs, we can register a custom loader via `_require.extensions` From 9ac9efaacc7574fa8e54fce810988bee8f2d55f2 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 17:06:44 +0900 Subject: [PATCH 04/15] chore: comment --- playground/config/__tests__/config.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index ece1e1ff83ad4c..aec79e0f876b2b 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -64,6 +64,8 @@ it('dynamic import with non-static arguments', async () => { "default": "import condition", } `) + // importing "./ok.js" inside "siblings/dynamic.js" should resolve to "siblings/ok.js" + // but this case has never been supported. await expect(() => config.siblingsDynamic('./ok.js')).rejects.toThrow( 'Cannot find module', ) From a12dedba68986b83267377ea1e75e66c33d705d3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 17:11:20 +0900 Subject: [PATCH 05/15] chore: comment --- packages/vite/src/node/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e451a2b1806b5f..5c885924c6954e 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1226,6 +1226,7 @@ async function transformViteConfigDynamicImport( text: string, importer: string, ): Promise { + // replace `import(anything)` with `__vite_config_import_helper__.bind(null, importer)(anything)` if (!/import\s*\(/.test(text)) { return text } From 34f831fd577cd263e59ed9d1a99c4273bff7524b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 17:57:41 +0900 Subject: [PATCH 06/15] test: more tests --- packages/vite/src/node/config.ts | 1 + playground/config/__tests__/config.spec.ts | 19 ++++++++++++++++++- .../packages/entry/vite.config.dynamic.ts | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 5c885924c6954e..c8ea2138be31b7 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1245,6 +1245,7 @@ async function transformViteConfigDynamicImport( } } if (output.hasChanged()) { + // TODO: we could also simply inject via globalThis? const vitePath = pathToFileURL( path.resolve( _require.resolve('vite/package.json'), diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index aec79e0f876b2b..e7213352c82fa9 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -48,25 +48,42 @@ it.runIf(isImportAttributesSupported)( }, ) -it('dynamic import with non-static arguments', async () => { +it('dynamic import', async () => { const { config } = (await loadConfigFromFile( { command: 'serve', mode: 'development' }, resolve(__dirname, '../packages/entry/vite.config.dynamic.ts'), )) as any + expect(await config.knownImport()).toMatchInlineSnapshot(` + { + "default": "ok", + } + `) expect(await config.rawImport('../siblings/ok.js')).toMatchInlineSnapshot(` { "default": "ok", } `) + // two are different since one is bundled but the other is from node + expect(await config.knownImport()).not.toBe( + await config.rawImport('../siblings/ok.js'), + ) + expect(await config.rawImport('@vite/test-config-plugin-module-condition')) .toMatchInlineSnapshot(` { "default": "import condition", } `) + // importing "./ok.js" inside "siblings/dynamic.js" should resolve to "siblings/ok.js" // but this case has never been supported. await expect(() => config.siblingsDynamic('./ok.js')).rejects.toThrow( 'Cannot find module', ) + + await expect(() => + config.siblingsDynamic('no-such-module'), + ).rejects.toMatchInlineSnapshot( + `[Error: Failed to resolve dynamic import 'no-such-module']`, + ) }) diff --git a/playground/config/packages/entry/vite.config.dynamic.ts b/playground/config/packages/entry/vite.config.dynamic.ts index ed7601c4870222..c9983dce4cac5f 100644 --- a/playground/config/packages/entry/vite.config.dynamic.ts +++ b/playground/config/packages/entry/vite.config.dynamic.ts @@ -1,6 +1,7 @@ import siblingsDynamic from '../siblings/dynamic.js' export default { + knownImport: () => import('../siblings/ok.js'), rawImport: (id: string) => import(id), siblingsDynamic, } From 3df58a7275933f9d1a247cd3cd36eda335f84281 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 18:08:31 +0900 Subject: [PATCH 07/15] chore: hide dts --- packages/vite/src/node/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 9e9f341de1e705..814715f12552e6 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -7,7 +7,6 @@ export { loadConfigFromFile, resolveConfig, sortUserPlugins, - __vite_config_import_helper__, } from './config' export { createServer } from './server' export { preview } from './preview' @@ -20,6 +19,11 @@ export { fetchModule } from './ssr/fetchModule' export type { FetchModuleOptions } from './ssr/fetchModule' export * from './publicUtils' +// hide it from dts +/** @internal */ +export const __vite_config_import_helper__ = __tmp_vite_config_import_helper__ +import { __vite_config_import_helper__ as __tmp_vite_config_import_helper__ } from './config' + // additional types export type { AppType, From 972ce064f2ee66946fb4723bf10d4091eba2e9c8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 18:20:52 +0900 Subject: [PATCH 08/15] refactor: minor --- packages/vite/src/node/config.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index c8ea2138be31b7..d80151333ca552 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1226,7 +1226,7 @@ async function transformViteConfigDynamicImport( text: string, importer: string, ): Promise { - // replace `import(anything)` with `__vite_config_import_helper__.bind(null, importer)(anything)` + // replace `import(anything)` with `__vite_config_import__(anything)` if (!/import\s*\(/.test(text)) { return text } @@ -1237,15 +1237,11 @@ async function transformViteConfigDynamicImport( if (imp.d >= 0 && typeof imp.n === 'undefined') { // dynamic import should be resolved relative to the original file but // that was never the case so here we're doing the same to resolve it from current config file. - output.update( - imp.ss, - imp.d, - `__vite_config_import_helper__.bind(null, ${JSON.stringify(importer)})`, - ) + output.update(imp.ss, imp.d, `__vite_config_import__`) } } if (output.hasChanged()) { - // TODO: we could also simply inject via globalThis? + // TODO: could we simply inject it via globalThis? const vitePath = pathToFileURL( path.resolve( _require.resolve('vite/package.json'), @@ -1253,7 +1249,8 @@ async function transformViteConfigDynamicImport( ), ).href output.prepend( - `import { __vite_config_import_helper__ } from ${JSON.stringify(vitePath)};`, + `import { __vite_config_import_helper__ } from ${JSON.stringify(vitePath)};` + + `const __vite_config_import__ = (id) => __vite_config_import_helper__(${JSON.stringify(importer)}, id);`, ) text = output.toString() } From 13b72d073a4ca4560850dc9142574f44e0f84bfc Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 18:26:50 +0900 Subject: [PATCH 09/15] fix: more pathToFileURL --- packages/vite/src/node/config.ts | 4 ++-- playground/config/__tests__/config.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index d80151333ca552..5c246d6fb5e08c 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1265,7 +1265,7 @@ export async function __vite_config_import_helper__( if (isBuiltin(id) || id[0] === '/') { resolved = id } else if (id[0] === '.') { - resolved = path.resolve(path.dirname(importer), id) + resolved = pathToFileURL(path.resolve(path.dirname(importer), id)).href } else { const result = tryNodeResolve( id, @@ -1290,7 +1290,7 @@ export async function __vite_config_import_helper__( if (!result) { throw new Error(`Failed to resolve dynamic import '${id}'`) } - resolved = result.id + resolved = pathToFileURL(result.id).href } return import(resolved) } diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index e7213352c82fa9..6d79fba0f5ea0b 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -82,7 +82,7 @@ it('dynamic import', async () => { ) await expect(() => - config.siblingsDynamic('no-such-module'), + config.rawImport('no-such-module'), ).rejects.toMatchInlineSnapshot( `[Error: Failed to resolve dynamic import 'no-such-module']`, ) From 6aa9f18303862d7112aaefc007add05d83dd4871 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 18:35:31 +0900 Subject: [PATCH 10/15] refactor: reuse more --- packages/vite/src/node/config.ts | 91 ++++++++++++++------------------ 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 5c246d6fb5e08c..a577c959bdb65f 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1070,6 +1070,34 @@ export async function loadConfigFromFile( } } +function createNodeResolveForConfigFile( + fileName: string, + packageCache: PackageCache, +) { + return (id: string, importer: string, isRequire: boolean) => { + return tryNodeResolve( + id, + importer, + { + root: path.dirname(fileName), + isBuild: true, + isProduction: true, + preferRelative: false, + tryIndex: true, + mainFields: [], + conditions: [], + overrideConditions: ['node'], + dedupe: [], + extensions: DEFAULT_EXTENSIONS, + preserveSymlinks: false, + packageCache, + isRequire, + }, + false, + )?.id + } +} + async function bundleConfigFile( fileName: string, isESM: boolean, @@ -1100,32 +1128,10 @@ async function bundleConfigFile( name: 'externalize-deps', setup(build) { const packageCache = new Map() - const resolveByViteResolver = ( - id: string, - importer: string, - isRequire: boolean, - ) => { - return tryNodeResolve( - id, - importer, - { - root: path.dirname(fileName), - isBuild: true, - isProduction: true, - preferRelative: false, - tryIndex: true, - mainFields: [], - conditions: [], - overrideConditions: ['node'], - dedupe: [], - extensions: DEFAULT_EXTENSIONS, - preserveSymlinks: false, - packageCache, - isRequire, - }, - false, - )?.id - } + const resolveByViteResolver = createNodeResolveForConfigFile( + fileName, + packageCache, + ) // externalize bare imports build.onResolve( @@ -1235,8 +1241,6 @@ async function transformViteConfigDynamicImport( const output = new MagicString(text) for (const imp of imports) { if (imp.d >= 0 && typeof imp.n === 'undefined') { - // dynamic import should be resolved relative to the original file but - // that was never the case so here we're doing the same to resolve it from current config file. output.update(imp.ss, imp.d, `__vite_config_import__`) } } @@ -1248,6 +1252,8 @@ async function transformViteConfigDynamicImport( '../dist/node/index.js', ), ).href + // dynamic import should be resolved relative to the original file but + // that was never the case so here we're doing the same to resolve it from current config file. output.prepend( `import { __vite_config_import_helper__ } from ${JSON.stringify(vitePath)};` + `const __vite_config_import__ = (id) => __vite_config_import_helper__(${JSON.stringify(importer)}, id);`, @@ -1267,30 +1273,12 @@ export async function __vite_config_import_helper__( } else if (id[0] === '.') { resolved = pathToFileURL(path.resolve(path.dirname(importer), id)).href } else { - const result = tryNodeResolve( - id, - importer, - { - root: path.dirname(importer), - isBuild: true, - isProduction: true, - preferRelative: false, - tryIndex: true, - mainFields: [], - conditions: [], - overrideConditions: ['node'], - dedupe: [], - extensions: DEFAULT_EXTENSIONS, - preserveSymlinks: false, - packageCache: new Map(), - isRequire: false, - }, - false, - ) + const resolver = createNodeResolveForConfigFile(importer, new Map()) + const result = resolver(id, importer, false) if (!result) { throw new Error(`Failed to resolve dynamic import '${id}'`) } - resolved = pathToFileURL(result.id).href + resolved = pathToFileURL(result).href } return import(resolved) } @@ -1310,12 +1298,11 @@ async function loadConfigFromBundledFile( // write it to disk, load it with native Node ESM, then delete the file. // convert to base64, load it with native Node ESM. if (isESM) { - bundledCode = await transformViteConfigDynamicImport(bundledCode, fileName) + const code = await transformViteConfigDynamicImport(bundledCode, fileName) try { return ( await import( - 'data:text/javascript;base64,' + - Buffer.from(bundledCode).toString('base64') + 'data:text/javascript;base64,' + Buffer.from(code).toString('base64') ) ).default } catch (e) { From d5c3a34e4858adcd00bbf62b2d1e66b6d66fd127 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 18 Aug 2024 18:37:32 +0900 Subject: [PATCH 11/15] refactor: minor --- packages/vite/src/node/config.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index a577c959bdb65f..6b037a93bf2c0d 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1221,7 +1221,10 @@ async function bundleConfigFile( }, ], }) - const { text } = result.outputFiles[0] + let { text } = result.outputFiles[0] + if (isESM) { + text = await transformViteConfigDynamicImport(text, fileName) + } return { code: text, dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [], @@ -1232,7 +1235,6 @@ async function transformViteConfigDynamicImport( text: string, importer: string, ): Promise { - // replace `import(anything)` with `__vite_config_import__(anything)` if (!/import\s*\(/.test(text)) { return text } @@ -1240,6 +1242,7 @@ async function transformViteConfigDynamicImport( const [imports] = parse(text) const output = new MagicString(text) for (const imp of imports) { + // replace `import(anything)` with `__vite_config_import__(anything)` if (imp.d >= 0 && typeof imp.n === 'undefined') { output.update(imp.ss, imp.d, `__vite_config_import__`) } @@ -1298,11 +1301,11 @@ async function loadConfigFromBundledFile( // write it to disk, load it with native Node ESM, then delete the file. // convert to base64, load it with native Node ESM. if (isESM) { - const code = await transformViteConfigDynamicImport(bundledCode, fileName) try { return ( await import( - 'data:text/javascript;base64,' + Buffer.from(code).toString('base64') + 'data:text/javascript;base64,' + + Buffer.from(bundledCode).toString('base64') ) ).default } catch (e) { From eef58f3671e93350558d96e0a0fb47746b0e59a6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Aug 2024 18:04:07 +0900 Subject: [PATCH 12/15] refactor: use globalThis.__vite_config_import_helper__ --- packages/vite/src/node/config.ts | 15 +++------------ packages/vite/src/node/index.ts | 5 ----- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 6b037a93bf2c0d..7e7d4e3eebda86 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1248,25 +1248,16 @@ async function transformViteConfigDynamicImport( } } if (output.hasChanged()) { - // TODO: could we simply inject it via globalThis? - const vitePath = pathToFileURL( - path.resolve( - _require.resolve('vite/package.json'), - '../dist/node/index.js', - ), - ).href - // dynamic import should be resolved relative to the original file but - // that was never the case so here we're doing the same to resolve it from current config file. + Object.assign(globalThis, { __vite_config_import_helper__ }) output.prepend( - `import { __vite_config_import_helper__ } from ${JSON.stringify(vitePath)};` + - `const __vite_config_import__ = (id) => __vite_config_import_helper__(${JSON.stringify(importer)}, id);`, + `const __vite_config_import__ = (id) => globalThis.__vite_config_import_helper__(${JSON.stringify(importer)}, id);`, ) text = output.toString() } return text } -export async function __vite_config_import_helper__( +async function __vite_config_import_helper__( importer: string, id: string, ): Promise { diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 814715f12552e6..3d17f8737379d6 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -19,11 +19,6 @@ export { fetchModule } from './ssr/fetchModule' export type { FetchModuleOptions } from './ssr/fetchModule' export * from './publicUtils' -// hide it from dts -/** @internal */ -export const __vite_config_import_helper__ = __tmp_vite_config_import_helper__ -import { __vite_config_import_helper__ as __tmp_vite_config_import_helper__ } from './config' - // additional types export type { AppType, From e8e092af44c3f08d705b22027726d80f88428784 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 22 Aug 2024 16:00:00 +0900 Subject: [PATCH 13/15] fix: cache bust --- packages/vite/src/node/config.ts | 3 +++ playground/config/__tests__/config.spec.ts | 11 +++++++++++ .../config/packages/entry/vite.config.reload.ts | 1 + 3 files changed, 15 insertions(+) create mode 100644 playground/config/packages/entry/vite.config.reload.ts diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 7e7d4e3eebda86..67d3c02526d591 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1293,6 +1293,9 @@ async function loadConfigFromBundledFile( // convert to base64, load it with native Node ESM. if (isESM) { try { + // prepend timestamp for import cache busting + bundledCode = + `"${Date.now()}-${Math.random().toString(16).slice(2)}";` + bundledCode return ( await import( 'data:text/javascript;base64,' + diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index 6d79fba0f5ea0b..f1fbcc48c30b9f 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -87,3 +87,14 @@ it('dynamic import', async () => { `[Error: Failed to resolve dynamic import 'no-such-module']`, ) }) + +it('can reload even when same content', async () => { + const load = () => + loadConfigFromFile( + { command: 'serve', mode: 'development' }, + resolve(__dirname, '../packages/entry/vite.config.reload.ts'), + ) + const result1 = await load() + const result2 = await load() + expect(result1.config).not.toBe(result2.config) +}) diff --git a/playground/config/packages/entry/vite.config.reload.ts b/playground/config/packages/entry/vite.config.reload.ts new file mode 100644 index 00000000000000..b1c6ea436a5400 --- /dev/null +++ b/playground/config/packages/entry/vite.config.reload.ts @@ -0,0 +1 @@ +export default {} From bc144dd33f3d1904a34597b15b87f153de9a3c30 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 22 Aug 2024 16:44:53 +0900 Subject: [PATCH 14/15] test: test config load error --- playground/config/__tests__/config.spec.ts | 24 +++++++++++++++++++ .../packages/entry/vite.config.error-dep.ts | 1 + .../packages/entry/vite.config.error.ts | 1 + .../packages/entry/vite.config.no-export.ts | 0 4 files changed, 26 insertions(+) create mode 100644 playground/config/packages/entry/vite.config.error-dep.ts create mode 100644 playground/config/packages/entry/vite.config.error.ts create mode 100644 playground/config/packages/entry/vite.config.no-export.ts diff --git a/playground/config/__tests__/config.spec.ts b/playground/config/__tests__/config.spec.ts index f1fbcc48c30b9f..29c2d233cb8513 100644 --- a/playground/config/__tests__/config.spec.ts +++ b/playground/config/__tests__/config.spec.ts @@ -98,3 +98,27 @@ it('can reload even when same content', async () => { const result2 = await load() expect(result1.config).not.toBe(result2.config) }) + +it('no export error', async () => { + await expect(() => + loadConfigFromFile( + { command: 'serve', mode: 'development' }, + resolve(__dirname, '../packages/entry/vite.config.no-export.ts'), + undefined, + 'silent', + ), + ).rejects.toMatchInlineSnapshot( + `[Error: config must export or return an object.]`, + ) +}) + +it('error', async () => { + await expect(() => + loadConfigFromFile( + { command: 'serve', mode: 'development' }, + resolve(__dirname, '../packages/entry/vite.config.error.ts'), + undefined, + 'silent', + ), + ).rejects.toThrow(`error-dep at `) +}) diff --git a/playground/config/packages/entry/vite.config.error-dep.ts b/playground/config/packages/entry/vite.config.error-dep.ts new file mode 100644 index 00000000000000..4ac02245580728 --- /dev/null +++ b/playground/config/packages/entry/vite.config.error-dep.ts @@ -0,0 +1 @@ +throw new Error('error-dep') diff --git a/playground/config/packages/entry/vite.config.error.ts b/playground/config/packages/entry/vite.config.error.ts new file mode 100644 index 00000000000000..999e43671a46d7 --- /dev/null +++ b/playground/config/packages/entry/vite.config.error.ts @@ -0,0 +1 @@ +import './vite.config.error-dep' diff --git a/playground/config/packages/entry/vite.config.no-export.ts b/playground/config/packages/entry/vite.config.no-export.ts new file mode 100644 index 00000000000000..e69de29bb2d1d6 From 5d5b1730e9772070ca07e104ac586a452063845c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 22 Aug 2024 18:22:21 +0900 Subject: [PATCH 15/15] test: add vite.config.error-plugin.ts --- .../entry/vite.config.error-plugin.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 playground/config/packages/entry/vite.config.error-plugin.ts diff --git a/playground/config/packages/entry/vite.config.error-plugin.ts b/playground/config/packages/entry/vite.config.error-plugin.ts new file mode 100644 index 00000000000000..f800f7349bfa01 --- /dev/null +++ b/playground/config/packages/entry/vite.config.error-plugin.ts @@ -0,0 +1,34 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [ + { + name: 'test-plugin-error', + transform(code, id, options) { + testError() + }, + }, + { + name: 'virtual-entry', + resolveId(source, importer, options) { + if (source === 'virtual:entry') { + return '\0' + source + } + }, + load(id, options) { + if (id === '\0virtual:entry') { + return `export default {}` + } + }, + }, + ], + build: { + rollupOptions: { + input: 'virtual:entry', + }, + }, +}) + +function testError() { + throw new Error('Testing Error') +}