From 547d1c78d1ad3ffb7242c5b2b5f39a5c42edb750 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 11 Nov 2024 11:01:47 +0100 Subject: [PATCH 01/27] feat: use module runner to import the config --- packages/vite/src/node/config.ts | 247 +++---------------------------- 1 file changed, 24 insertions(+), 223 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 6c65a2aaaa9012..73614799859989 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1,13 +1,9 @@ import fs from 'node:fs' -import fsp from 'node:fs/promises' import path from 'node:path' -import { pathToFileURL } from 'node:url' -import { promisify } from 'node:util' import { performance } from 'node:perf_hooks' -import { builtinModules, createRequire } from 'node:module' +import { builtinModules } from 'node:module' import colors from 'picocolors' import type { Alias, AliasOptions } from 'dep-types/alias' -import { build } from 'esbuild' import type { RollupOptions } from 'rollup' import picomatch from 'picomatch' import type { AnymatchFn } from '../types/anymatch' @@ -61,11 +57,8 @@ import { asyncFlatten, createDebugger, createFilter, - isBuiltin, isExternalUrl, - isFilePathESM, isInNodeModules, - isNodeBuiltin, isObject, isParentDirectory, mergeAlias, @@ -86,7 +79,6 @@ import type { InternalResolveOptions, ResolveOptions, } from './plugins/resolve' -import { tryNodeResolve } from './plugins/resolve' import type { LogLevel, Logger } from './logger' import { createLogger } from './logger' import type { DepOptimizationOptions } from './optimizer' @@ -98,9 +90,9 @@ import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' +import { createServerModuleRunner } from './ssr/runtime/serverModuleRunner' const debug = createDebugger('vite:config', { depth: 10 }) -const promisifiedRealpath = promisify(fs.realpath) export interface ConfigEnv { /** @@ -1666,17 +1658,27 @@ export async function loadConfigFromFile( return null } - const isESM = - typeof process.versions.deno === 'string' || isFilePathESM(resolvedPath) - try { - const bundled = await bundleConfigFile(resolvedPath, isESM) - const userConfig = await loadConfigFromBundledFile( - resolvedPath, - bundled.code, - isESM, + // console.time('config') + const environment = new DevEnvironment( + 'config', + await resolveConfig({ configFile: false }, 'serve'), + { + options: { + consumer: 'server', + dev: { + moduleRunnerTransform: true, + }, + }, + hot: false, + }, ) - debug?.(`bundled config file loaded in ${getTime()}`) + await environment.init() + const runner = createServerModuleRunner(environment) + const { default: userConfig } = (await runner.import(resolvedPath)) as { + default: UserConfigExport + } + debug?.(`config file loaded in ${getTime()}`) const config = await (typeof userConfig === 'function' ? userConfig(configEnv) @@ -1687,7 +1689,7 @@ export async function loadConfigFromFile( return { path: normalizePath(resolvedPath), config, - dependencies: bundled.dependencies, + dependencies: [], } } catch (e) { createLogger(logLevel, { customLogger }).error( @@ -1697,209 +1699,8 @@ export async function loadConfigFromFile( }, ) throw e - } -} - -async function bundleConfigFile( - fileName: string, - isESM: boolean, -): Promise<{ code: string; dependencies: string[] }> { - const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) - .default - - const dirnameVarName = '__vite_injected_original_dirname' - const filenameVarName = '__vite_injected_original_filename' - const importMetaUrlVarName = '__vite_injected_original_import_meta_url' - const result = await build({ - absWorkingDir: process.cwd(), - entryPoints: [fileName], - write: false, - target: [`node${process.versions.node}`], - platform: 'node', - bundle: true, - format: isESM ? 'esm' : 'cjs', - mainFields: ['main'], - sourcemap: 'inline', - metafile: true, - define: { - __dirname: dirnameVarName, - __filename: filenameVarName, - 'import.meta.url': importMetaUrlVarName, - 'import.meta.dirname': dirnameVarName, - 'import.meta.filename': filenameVarName, - }, - plugins: [ - { - 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: [ - 'node', - ...(isModuleSyncConditionEnabled ? ['module-sync'] : []), - ], - externalConditions: [], - external: [], - noExternal: [], - dedupe: [], - extensions: configDefaults.resolve.extensions, - preserveSymlinks: false, - packageCache, - isRequire, - })?.id - } - - // externalize bare imports - build.onResolve( - { filter: /^[^.].*/ }, - async ({ path: id, importer, kind }) => { - if ( - kind === 'entry-point' || - path.isAbsolute(id) || - isNodeBuiltin(id) - ) { - return - } - - // With the `isNodeBuiltin` check above, this check captures if the builtin is a - // non-node built-in, which esbuild doesn't know how to handle. In that case, we - // externalize it so the non-node runtime handles it instead. - if (isBuiltin(id)) { - return { external: true } - } - - const isImport = isESM || kind === 'dynamic-import' - let idFsPath: string | undefined - try { - idFsPath = resolveByViteResolver(id, importer, !isImport) - } catch (e) { - if (!isImport) { - let canResolveWithImport = false - try { - canResolveWithImport = !!resolveByViteResolver( - id, - importer, - false, - ) - } catch {} - if (canResolveWithImport) { - throw new Error( - `Failed to resolve ${JSON.stringify( - id, - )}. This package is ESM only but it was tried to load by \`require\`. See https://vite.dev/guide/troubleshooting.html#this-package-is-esm-only for more details.`, - ) - } - } - throw e - } - if (idFsPath && isImport) { - idFsPath = pathToFileURL(idFsPath).href - } - return { - path: idFsPath, - external: true, - } - }, - ) - }, - }, - { - name: 'inject-file-scope-variables', - setup(build) { - build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => { - const contents = await fsp.readFile(args.path, 'utf-8') - const injectValues = - `const ${dirnameVarName} = ${JSON.stringify( - path.dirname(args.path), - )};` + - `const ${filenameVarName} = ${JSON.stringify(args.path)};` + - `const ${importMetaUrlVarName} = ${JSON.stringify( - pathToFileURL(args.path).href, - )};` - - return { - loader: args.path.endsWith('ts') ? 'ts' : 'js', - contents: injectValues + contents, - } - }) - }, - }, - ], - }) - const { text } = result.outputFiles[0] - return { - code: text, - dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [], - } -} - -interface NodeModuleWithCompile extends NodeModule { - _compile(code: string, filename: string): any -} - -const _require = createRequire(import.meta.url) -async function loadConfigFromBundledFile( - fileName: string, - bundledCode: string, - isESM: boolean, -): Promise { - // 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. - if (isESM) { - const nodeModulesDir = findNearestNodeModules(path.dirname(fileName)) - if (nodeModulesDir) { - await fsp.mkdir(path.resolve(nodeModulesDir, '.vite-temp/'), { - recursive: true, - }) - } - const hash = `timestamp-${Date.now()}-${Math.random().toString(16).slice(2)}` - const tempFileName = nodeModulesDir - ? path.resolve( - nodeModulesDir, - `.vite-temp/${path.basename(fileName)}.${hash}.mjs`, - ) - : `${fileName}.${hash}.mjs` - await fsp.writeFile(tempFileName, bundledCode) - try { - return (await import(pathToFileURL(tempFileName).href)).default - } finally { - fs.unlink(tempFileName, () => {}) // Ignore errors - } - } - // for cjs, we can register a custom loader via `_require.extensions` - else { - const extension = path.extname(fileName) - // We don't use fsp.realpath() here because it has the same behaviour as - // fs.realpath.native. On some Windows systems, it returns uppercase volume - // letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters. - // See https://github.com/vitejs/vite/issues/12923 - const realFileName = await promisifiedRealpath(fileName) - const loaderExt = extension in _require.extensions ? extension : '.js' - const defaultLoader = _require.extensions[loaderExt]! - _require.extensions[loaderExt] = (module: NodeModule, filename: string) => { - if (filename === realFileName) { - ;(module as NodeModuleWithCompile)._compile(bundledCode, filename) - } else { - defaultLoader(module, filename) - } - } - // clear cache in case of server restart - delete _require.cache[_require.resolve(fileName)] - const raw = _require(fileName) - _require.extensions[loaderExt] = defaultLoader - return raw.__esModule ? raw.default : raw + } finally { + // console.timeEnd('config') } } From 2454b660404e86e2856c1a0632aeb4154af76891 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 11 Nov 2024 11:04:00 +0100 Subject: [PATCH 02/27] chore: todo --- 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 73614799859989..1a893689f7d74d 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1689,6 +1689,7 @@ export async function loadConfigFromFile( return { path: normalizePath(resolvedPath), config, + // TODO: use hot to reload the config instead? dependencies: [], } } catch (e) { From 1cf3da7d88d4fc291a57512d7c3d4a5ff56cf0b9 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 11 Nov 2024 12:03:49 +0100 Subject: [PATCH 03/27] chore: merge --- packages/vite/src/node/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 1a893689f7d74d..a06e97dd5846ce 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -84,7 +84,7 @@ import { createLogger } from './logger' import type { DepOptimizationOptions } from './optimizer' import type { JsonOptions } from './plugins/json' import type { PackageCache } from './packages' -import { findNearestNodeModules, findNearestPackageData } from './packages' +import { findNearestPackageData } from './packages' import { loadEnv, resolveEnvPrefix } from './env' import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' From df1efe3d05d2acc5fc33d191419cb666ac72eb60 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 11 Nov 2024 14:41:26 +0100 Subject: [PATCH 04/27] chore: use RunnableDevEnvironment instead --- packages/vite/src/node/config.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index a06e97dd5846ce..36c27795b388f7 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -43,6 +43,7 @@ import { import type { ResolvedServerOptions, ServerOptions } from './server' import { resolveServerOptions, serverConfigDefaults } from './server' import { DevEnvironment } from './server/environment' +import type { RunnableDevEnvironment } from './server/environments/runnableEnvironment' import { createRunnableDevEnvironment } from './server/environments/runnableEnvironment' import type { WebSocketServer } from './server/ws' import type { PreviewOptions, ResolvedPreviewOptions } from './preview' @@ -90,7 +91,6 @@ import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' -import { createServerModuleRunner } from './ssr/runtime/serverModuleRunner' const debug = createDebugger('vite:config', { depth: 10 }) @@ -1658,9 +1658,11 @@ export async function loadConfigFromFile( return null } + let environment: RunnableDevEnvironment | undefined + try { // console.time('config') - const environment = new DevEnvironment( + environment = createRunnableDevEnvironment( 'config', await resolveConfig({ configFile: false }, 'serve'), { @@ -1670,12 +1672,18 @@ export async function loadConfigFromFile( moduleRunnerTransform: true, }, }, + runnerOptions: { + hmr: { + logger: false, + }, + }, hot: false, }, ) await environment.init() - const runner = createServerModuleRunner(environment) - const { default: userConfig } = (await runner.import(resolvedPath)) as { + const { default: userConfig } = (await environment.runner.import( + resolvedPath, + )) as { default: UserConfigExport } debug?.(`config file loaded in ${getTime()}`) @@ -1686,6 +1694,7 @@ export async function loadConfigFromFile( if (!isObject(config)) { throw new Error(`config must export or return an object.`) } + await environment.runner.close() return { path: normalizePath(resolvedPath), config, @@ -1693,6 +1702,7 @@ export async function loadConfigFromFile( dependencies: [], } } catch (e) { + await environment?.runner.close() createLogger(logLevel, { customLogger }).error( colors.red(`failed to load config from ${resolvedPath}`), { From e55e0832fce6242cd868022ab30bbcc844bdf866 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 11 Nov 2024 16:00:43 +0100 Subject: [PATCH 05/27] chore: make it run faster in tests --- packages/vite/src/node/config.ts | 49 +++++++++++++++++++++++++------- vitest.config.ts | 3 ++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 36c27795b388f7..e1741f22a3f112 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1661,17 +1661,36 @@ export async function loadConfigFromFile( let environment: RunnableDevEnvironment | undefined try { - // console.time('config') environment = createRunnableDevEnvironment( 'config', - await resolveConfig({ configFile: false }, 'serve'), - { - options: { - consumer: 'server', - dev: { - moduleRunnerTransform: true, + await resolveConfig( + { + configFile: false, + environments: { + config: { + consumer: 'server', + dev: { + moduleRunnerTransform: true, + }, + resolve: { + external: true, + }, + }, }, }, + 'serve', + ), + { + // options: { + // consumer: 'server', + // dev: { + // moduleRunnerTransform: true, + // }, + // TODO for some reason this doesn't work, only setting it the config works + // resolve: { + // external: true, + // }, + // }, runnerOptions: { hmr: { logger: false, @@ -1694,12 +1713,22 @@ export async function loadConfigFromFile( if (!isObject(config)) { throw new Error(`config must export or return an object.`) } + const modules = [ + ...environment.runner.evaluatedModules.fileToModulesMap.entries(), + ] await environment.runner.close() + const dependencies = modules + .filter(([file, modules]) => { + const isExternal = [...modules].some( + (m) => !m.meta || 'externalize' in m.meta, + ) + return !isExternal && file !== resolvedPath + }) + .map(([file]) => file) return { path: normalizePath(resolvedPath), config, - // TODO: use hot to reload the config instead? - dependencies: [], + dependencies, } } catch (e) { await environment?.runner.close() @@ -1710,8 +1739,6 @@ export async function loadConfigFromFile( }, ) throw e - } finally { - // console.timeEnd('config') } } diff --git a/vitest.config.ts b/vitest.config.ts index 7ee61c4585006d..4cece0f89e7293 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -13,6 +13,9 @@ export default defineConfig({ './playground/**/*.*', './playground-temp/**/*.*', ], + deps: { + moduleDirectories: ['node_modules', 'packages'], + }, testTimeout: 20000, isolate: false, }, From c9e908ea0c5e42a7dbbc0ec52f37775adcb02a3c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Tue, 12 Nov 2024 11:44:22 +0100 Subject: [PATCH 06/27] fix: support cjs globals in default modules evaluator --- .../vite/src/module-runner/esmEvaluator.ts | 18 ++- packages/vite/src/module-runner/index.ts | 5 +- packages/vite/src/node/config.ts | 129 ++++++++++-------- 3 files changed, 92 insertions(+), 60 deletions(-) diff --git a/packages/vite/src/module-runner/esmEvaluator.ts b/packages/vite/src/module-runner/esmEvaluator.ts index f7f8c8ab52de80..a090b6bd02fdab 100644 --- a/packages/vite/src/module-runner/esmEvaluator.ts +++ b/packages/vite/src/module-runner/esmEvaluator.ts @@ -11,8 +11,18 @@ import { } from './constants' import type { ModuleEvaluator, ModuleRunnerContext } from './types' +export interface ESModulesEvaluatorOptions { + cjsGlobals?: boolean + startOffset?: number +} + export class ESModulesEvaluator implements ModuleEvaluator { - startOffset = getAsyncFunctionDeclarationPaddingLineCount() + public readonly startOffset: number + + constructor(private options: ESModulesEvaluatorOptions = {}) { + this.startOffset = + options.startOffset ?? getAsyncFunctionDeclarationPaddingLineCount() + } async runInlinedModule( context: ModuleRunnerContext, @@ -25,16 +35,22 @@ export class ESModulesEvaluator implements ModuleEvaluator { ssrImportKey, ssrDynamicImportKey, ssrExportAllKey, + '__filename', + '__dirname', // source map should already be inlined by Vite '"use strict";' + code, ) + const meta = context[ssrImportMetaKey] + await initModule( context[ssrModuleExportsKey], context[ssrImportMetaKey], context[ssrImportKey], context[ssrDynamicImportKey], context[ssrExportAllKey], + this.options.cjsGlobals ? meta.filename : undefined, + this.options.cjsGlobals ? meta.dirname : undefined, ) Object.seal(context[ssrModuleExportsKey]) diff --git a/packages/vite/src/module-runner/index.ts b/packages/vite/src/module-runner/index.ts index a5489011d9441f..30501e2590b54a 100644 --- a/packages/vite/src/module-runner/index.ts +++ b/packages/vite/src/module-runner/index.ts @@ -2,7 +2,10 @@ export { EvaluatedModules, type EvaluatedModuleNode } from './evaluatedModules' export { ModuleRunner } from './runner' -export { ESModulesEvaluator } from './esmEvaluator' +export { + ESModulesEvaluator, + type ESModulesEvaluatorOptions, +} from './esmEvaluator' export { createWebSocketModuleRunnerTransport } from '../shared/moduleRunnerTransport' diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e1741f22a3f112..1f0a64c1b9ae88 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -6,6 +6,7 @@ import colors from 'picocolors' import type { Alias, AliasOptions } from 'dep-types/alias' import type { RollupOptions } from 'rollup' import picomatch from 'picomatch' +import { ESModulesEvaluator } from 'vite/module-runner' import type { AnymatchFn } from '../types/anymatch' import { withTrailingSlash } from '../shared/utils' import { @@ -43,7 +44,6 @@ import { import type { ResolvedServerOptions, ServerOptions } from './server' import { resolveServerOptions, serverConfigDefaults } from './server' import { DevEnvironment } from './server/environment' -import type { RunnableDevEnvironment } from './server/environments/runnableEnvironment' import { createRunnableDevEnvironment } from './server/environments/runnableEnvironment' import type { WebSocketServer } from './server/ws' import type { PreviewOptions, ResolvedPreviewOptions } from './preview' @@ -1658,87 +1658,100 @@ export async function loadConfigFromFile( return null } - let environment: RunnableDevEnvironment | undefined - try { - environment = createRunnableDevEnvironment( - 'config', - await resolveConfig( - { - configFile: false, - environments: { - config: { - consumer: 'server', - dev: { - moduleRunnerTransform: true, - }, - resolve: { - external: true, - }, + const { userConfig, dependencies } = await importConfig(resolvedPath) + debug?.(`config file loaded in ${getTime()}`) + + const config = await (typeof userConfig === 'function' + ? userConfig(configEnv) + : userConfig) + if (!isObject(config)) { + throw new Error(`config must export or return an object.`) + } + return { + path: normalizePath(resolvedPath), + config, + dependencies, + } + } catch (e) { + createLogger(logLevel, { customLogger }).error( + colors.red(`failed to load config from ${resolvedPath}`), + { + error: e, + }, + ) + throw e + } +} + +async function importConfig(resolvedPath: string) { + const environment = createRunnableDevEnvironment( + 'config', + // TODO: provide a dummy config? + await resolveConfig( + { + configFile: false, + environments: { + config: { + consumer: 'server', + dev: { + moduleRunnerTransform: true, + }, + resolve: { + external: true, }, }, }, - 'serve', - ), - { - // options: { - // consumer: 'server', - // dev: { - // moduleRunnerTransform: true, - // }, - // TODO for some reason this doesn't work, only setting it the config works - // resolve: { - // external: true, - // }, - // }, - runnerOptions: { - hmr: { - logger: false, - }, + }, + 'serve', + ), + { + // options: { + // consumer: 'server', + // dev: { + // moduleRunnerTransform: true, + // }, + // TODO for some reason this doesn't work, only setting it the config works + // resolve: { + // external: true, + // }, + // }, + runnerOptions: { + hmr: { + logger: false, }, - hot: false, + evaluator: new ESModulesEvaluator({ + cjsGlobals: true, + }), }, - ) - await environment.init() + hot: false, + }, + ) + await environment.init() + try { const { default: userConfig } = (await environment.runner.import( resolvedPath, )) as { default: UserConfigExport } - debug?.(`config file loaded in ${getTime()}`) - - const config = await (typeof userConfig === 'function' - ? userConfig(configEnv) - : userConfig) - if (!isObject(config)) { - throw new Error(`config must export or return an object.`) - } const modules = [ ...environment.runner.evaluatedModules.fileToModulesMap.entries(), ] - await environment.runner.close() const dependencies = modules .filter(([file, modules]) => { - const isExternal = [...modules].some( + const isExternal = [...modules].every( (m) => !m.meta || 'externalize' in m.meta, ) return !isExternal && file !== resolvedPath }) .map(([file]) => file) return { - path: normalizePath(resolvedPath), - config, + userConfig, dependencies, } - } catch (e) { - await environment?.runner.close() - createLogger(logLevel, { customLogger }).error( - colors.red(`failed to load config from ${resolvedPath}`), - { - error: e, - }, - ) - throw e + } catch (err) { + await environment.close() + throw err } } From b4c41d3a2d10736ce2c2a5c65636993cd2229eb9 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 17:13:37 +0100 Subject: [PATCH 07/27] feat: add `--bundleConfig` flag to opt-out of bundling --- packages/vite/src/node/cli.ts | 9 + packages/vite/src/node/config.ts | 304 +++++++++++++++++++++------ packages/vite/src/node/ssr/import.ts | 72 +++++++ 3 files changed, 315 insertions(+), 70 deletions(-) create mode 100644 packages/vite/src/node/ssr/import.ts diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index b520b9b5144b46..8d0a5133f27031 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -23,6 +23,7 @@ interface GlobalCLIOptions { l?: LogLevel logLevel?: LogLevel clearScreen?: boolean + bundleConfig?: boolean d?: boolean | string debug?: boolean | string f?: string @@ -151,6 +152,10 @@ cli }) .option('-l, --logLevel ', `[string] info | warn | error | silent`) .option('--clearScreen', `[boolean] allow/disable clear screen when logging`) + .option( + '--bundleConfig', + `[boolean] should the config be bundled or evaluated with a module runner when importing`, + ) .option('-d, --debug [feat]', `[string | boolean] show debug logs`) .option('-f, --filter ', `[string] filter debug logs`) .option('-m, --mode ', `[string] set env mode`) @@ -180,6 +185,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, + bundleConfig: options.bundleConfig, logLevel: options.logLevel, clearScreen: options.clearScreen, optimizeDeps: { force: options.force }, @@ -304,6 +310,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, + bundleConfig: options.bundleConfig, logLevel: options.logLevel, clearScreen: options.clearScreen, build: buildOptions, @@ -340,6 +347,7 @@ cli root, base: options.base, configFile: options.config, + bundleConfig: options.bundleConfig, logLevel: options.logLevel, mode: options.mode, }, @@ -382,6 +390,7 @@ cli root, base: options.base, configFile: options.config, + bundleConfig: options.bundleConfig, logLevel: options.logLevel, mode: options.mode, build: { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 1f0a64c1b9ae88..d86f7a3537ebf2 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1,12 +1,15 @@ import fs from 'node:fs' import path from 'node:path' +import fsp from 'node:fs/promises' +import { pathToFileURL } from 'node:url' +import { promisify } from 'node:util' import { performance } from 'node:perf_hooks' -import { builtinModules } from 'node:module' +import { builtinModules, createRequire } from 'node:module' import colors from 'picocolors' import type { Alias, AliasOptions } from 'dep-types/alias' import type { RollupOptions } from 'rollup' import picomatch from 'picomatch' -import { ESModulesEvaluator } from 'vite/module-runner' +import { build } from 'esbuild' import type { AnymatchFn } from '../types/anymatch' import { withTrailingSlash } from '../shared/utils' import { @@ -58,8 +61,11 @@ import { asyncFlatten, createDebugger, createFilter, + isBuiltin, isExternalUrl, + isFilePathESM, isInNodeModules, + isNodeBuiltin, isObject, isParentDirectory, mergeAlias, @@ -75,24 +81,27 @@ import { resolvePlugins, } from './plugins' import type { ESBuildOptions } from './plugins/esbuild' -import type { - EnvironmentResolveOptions, - InternalResolveOptions, - ResolveOptions, +import { + type EnvironmentResolveOptions, + type InternalResolveOptions, + type ResolveOptions, + tryNodeResolve, } from './plugins/resolve' import type { LogLevel, Logger } from './logger' import { createLogger } from './logger' import type { DepOptimizationOptions } from './optimizer' import type { JsonOptions } from './plugins/json' import type { PackageCache } from './packages' -import { findNearestPackageData } from './packages' +import { findNearestNodeModules, findNearestPackageData } from './packages' import { loadEnv, resolveEnvPrefix } from './env' import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' +import { inlineImport } from './ssr/import' const debug = createDebugger('vite:config', { depth: 10 }) +const promisifiedRealpath = promisify(fs.realpath) export interface ConfigEnv { /** @@ -520,6 +529,7 @@ export interface ResolvedWorkerOptions { export interface InlineConfig extends UserConfig { configFile?: string | false + bundleConfig?: boolean envFile?: false } @@ -1628,6 +1638,7 @@ export async function loadConfigFromFile( configRoot: string = process.cwd(), logLevel?: LogLevel, customLogger?: Logger, + bundleConfig = true, ): Promise<{ path: string config: UserConfig @@ -1659,15 +1670,17 @@ export async function loadConfigFromFile( } try { - const { userConfig, dependencies } = await importConfig(resolvedPath) + const resolver = bundleConfig ? bundleAndLoadConfigFile : importConfigFile + const { configExport, dependencies } = await resolver(resolvedPath) debug?.(`config file loaded in ${getTime()}`) - const config = await (typeof userConfig === 'function' - ? userConfig(configEnv) - : userConfig) + const config = await (typeof configExport === 'function' + ? configExport(configEnv) + : configExport) if (!isObject(config)) { throw new Error(`config must export or return an object.`) } + return { path: normalizePath(resolvedPath), config, @@ -1684,74 +1697,225 @@ export async function loadConfigFromFile( } } -async function importConfig(resolvedPath: string) { - const environment = createRunnableDevEnvironment( - 'config', - // TODO: provide a dummy config? - await resolveConfig( +async function importConfigFile(resolvedPath: string) { + const { module, dependencies } = await inlineImport(resolvedPath) + return { + configExport: module.default, + dependencies, + } +} + +async function bundleAndLoadConfigFile(resolvedPath: string) { + const isESM = + typeof process.versions.deno === 'string' || isFilePathESM(resolvedPath) + + const bundled = await bundleConfigFile(resolvedPath, isESM) + const userConfig = await loadConfigFromBundledFile( + resolvedPath, + bundled.code, + isESM, + ) + + return { + configExport: userConfig, + dependencies: bundled.dependencies, + } +} + +async function bundleConfigFile( + fileName: string, + isESM: boolean, +): Promise<{ code: string; dependencies: string[] }> { + const dirnameVarName = '__vite_injected_original_dirname' + const filenameVarName = '__vite_injected_original_filename' + const importMetaUrlVarName = '__vite_injected_original_import_meta_url' + const result = await build({ + absWorkingDir: process.cwd(), + entryPoints: [fileName], + write: false, + target: [`node${process.versions.node}`], + platform: 'node', + bundle: true, + format: isESM ? 'esm' : 'cjs', + mainFields: ['main'], + sourcemap: 'inline', + metafile: true, + define: { + __dirname: dirnameVarName, + __filename: filenameVarName, + 'import.meta.url': importMetaUrlVarName, + 'import.meta.dirname': dirnameVarName, + 'import.meta.filename': filenameVarName, + }, + plugins: [ { - configFile: false, - environments: { - config: { - consumer: 'server', - dev: { - moduleRunnerTransform: true, + 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: ['node'], + externalConditions: [], + external: [], + noExternal: [], + dedupe: [], + extensions: configDefaults.resolve.extensions, + preserveSymlinks: false, + packageCache, + isRequire, + })?.id + } + + // externalize bare imports + build.onResolve( + { filter: /^[^.].*/ }, + async ({ path: id, importer, kind }) => { + if ( + kind === 'entry-point' || + path.isAbsolute(id) || + isNodeBuiltin(id) + ) { + return + } + + // With the `isNodeBuiltin` check above, this check captures if the builtin is a + // non-node built-in, which esbuild doesn't know how to handle. In that case, we + // externalize it so the non-node runtime handles it instead. + if (isBuiltin(id)) { + return { external: true } + } + + const isImport = isESM || kind === 'dynamic-import' + let idFsPath: string | undefined + try { + idFsPath = resolveByViteResolver(id, importer, !isImport) + } catch (e) { + if (!isImport) { + let canResolveWithImport = false + try { + canResolveWithImport = !!resolveByViteResolver( + id, + importer, + false, + ) + } catch {} + if (canResolveWithImport) { + throw new Error( + `Failed to resolve ${JSON.stringify( + id, + )}. This package is ESM only but it was tried to load by \`require\`. See https://vite.dev/guide/troubleshooting.html#this-package-is-esm-only for more details.`, + ) + } + } + throw e + } + if (idFsPath && isImport) { + idFsPath = pathToFileURL(idFsPath).href + } + return { + path: idFsPath, + external: true, + } }, - resolve: { - external: true, - }, - }, + ) }, }, - 'serve', - ), - { - // options: { - // consumer: 'server', - // dev: { - // moduleRunnerTransform: true, - // }, - // TODO for some reason this doesn't work, only setting it the config works - // resolve: { - // external: true, - // }, - // }, - runnerOptions: { - hmr: { - logger: false, + { + name: 'inject-file-scope-variables', + setup(build) { + build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => { + const contents = await fsp.readFile(args.path, 'utf-8') + const injectValues = + `const ${dirnameVarName} = ${JSON.stringify( + path.dirname(args.path), + )};` + + `const ${filenameVarName} = ${JSON.stringify(args.path)};` + + `const ${importMetaUrlVarName} = ${JSON.stringify( + pathToFileURL(args.path).href, + )};` + + return { + loader: args.path.endsWith('ts') ? 'ts' : 'js', + contents: injectValues + contents, + } + }) }, - evaluator: new ESModulesEvaluator({ - cjsGlobals: true, - }), }, - hot: false, - }, - ) - await environment.init() - try { - const { default: userConfig } = (await environment.runner.import( - resolvedPath, - )) as { - default: UserConfigExport + ], + }) + const { text } = result.outputFiles[0] + return { + code: text, + dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [], + } +} + +interface NodeModuleWithCompile extends NodeModule { + _compile(code: string, filename: string): any +} + +const _require = createRequire(import.meta.url) +async function loadConfigFromBundledFile( + fileName: string, + bundledCode: string, + isESM: boolean, +): Promise { + // 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. + if (isESM) { + const nodeModulesDir = findNearestNodeModules(path.dirname(fileName)) + if (nodeModulesDir) { + await fsp.mkdir(path.resolve(nodeModulesDir, '.vite-temp/'), { + recursive: true, + }) } - const modules = [ - ...environment.runner.evaluatedModules.fileToModulesMap.entries(), - ] - const dependencies = modules - .filter(([file, modules]) => { - const isExternal = [...modules].every( - (m) => !m.meta || 'externalize' in m.meta, + const hash = `timestamp-${Date.now()}-${Math.random().toString(16).slice(2)}` + const tempFileName = nodeModulesDir + ? path.resolve( + nodeModulesDir, + `.vite-temp/${path.basename(fileName)}.${hash}.mjs`, ) - return !isExternal && file !== resolvedPath - }) - .map(([file]) => file) - return { - userConfig, - dependencies, + : `${fileName}.${hash}.mjs` + await fsp.writeFile(tempFileName, bundledCode) + try { + return (await import(pathToFileURL(tempFileName).href)).default + } finally { + fs.unlink(tempFileName, () => {}) // Ignore errors + } + } + // for cjs, we can register a custom loader via `_require.extensions` + else { + const extension = path.extname(fileName) + // We don't use fsp.realpath() here because it has the same behaviour as + // fs.realpath.native. On some Windows systems, it returns uppercase volume + // letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters. + // See https://github.com/vitejs/vite/issues/12923 + const realFileName = await promisifiedRealpath(fileName) + const loaderExt = extension in _require.extensions ? extension : '.js' + const defaultLoader = _require.extensions[loaderExt]! + _require.extensions[loaderExt] = (module: NodeModule, filename: string) => { + if (filename === realFileName) { + ;(module as NodeModuleWithCompile)._compile(bundledCode, filename) + } else { + defaultLoader(module, filename) + } } - } catch (err) { - await environment.close() - throw err + // clear cache in case of server restart + delete _require.cache[_require.resolve(fileName)] + const raw = _require(fileName) + _require.extensions[loaderExt] = defaultLoader + return raw.__esModule ? raw.default : raw } } diff --git a/packages/vite/src/node/ssr/import.ts b/packages/vite/src/node/ssr/import.ts new file mode 100644 index 00000000000000..fe54efd0ddf7f9 --- /dev/null +++ b/packages/vite/src/node/ssr/import.ts @@ -0,0 +1,72 @@ +import { resolveConfig } from '../config' +import { createRunnableDevEnvironment } from '../server/environments/runnableEnvironment' + +interface InlineImportResult { + module: any + dependencies: string[] +} + +export async function inlineImport( + moduleId: string, +): Promise { + const environment = createRunnableDevEnvironment( + 'config', + // TODO: provide a dummy config? + await resolveConfig( + { + configFile: false, + environments: { + config: { + consumer: 'server', + dev: { + moduleRunnerTransform: true, + }, + resolve: { + external: true, + }, + }, + }, + }, + 'serve', + ), + { + // options: { + // consumer: 'server', + // dev: { + // moduleRunnerTransform: true, + // }, + // TODO for some reason this doesn't work, only setting it the config works + // resolve: { + // external: true, + // }, + // }, + runnerOptions: { + hmr: { + logger: false, + }, + }, + hot: false, + }, + ) + await environment.init() + try { + const module = await environment.runner.import(moduleId) + const modules = [ + ...environment.runner.evaluatedModules.fileToModulesMap.entries(), + ] + const dependencies = modules + .filter(([file, modules]) => { + const isExternal = [...modules].every( + (m) => !m.meta || 'externalize' in m.meta, + ) + return !isExternal && file !== moduleId + }) + .map(([file]) => file) + return { + module, + dependencies, + } + } finally { + await environment.close() + } +} From 7c5dcf066007fce4ce314d889d956c3f363043f1 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 17:16:33 +0100 Subject: [PATCH 08/27] chore: remove cjs support --- .../vite/src/module-runner/esmEvaluator.ts | 18 +----------------- packages/vite/src/module-runner/index.ts | 5 +---- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/vite/src/module-runner/esmEvaluator.ts b/packages/vite/src/module-runner/esmEvaluator.ts index a090b6bd02fdab..003d6b2d242b88 100644 --- a/packages/vite/src/module-runner/esmEvaluator.ts +++ b/packages/vite/src/module-runner/esmEvaluator.ts @@ -11,18 +11,8 @@ import { } from './constants' import type { ModuleEvaluator, ModuleRunnerContext } from './types' -export interface ESModulesEvaluatorOptions { - cjsGlobals?: boolean - startOffset?: number -} - export class ESModulesEvaluator implements ModuleEvaluator { - public readonly startOffset: number - - constructor(private options: ESModulesEvaluatorOptions = {}) { - this.startOffset = - options.startOffset ?? getAsyncFunctionDeclarationPaddingLineCount() - } + public readonly startOffset = getAsyncFunctionDeclarationPaddingLineCount() async runInlinedModule( context: ModuleRunnerContext, @@ -35,22 +25,16 @@ export class ESModulesEvaluator implements ModuleEvaluator { ssrImportKey, ssrDynamicImportKey, ssrExportAllKey, - '__filename', - '__dirname', // source map should already be inlined by Vite '"use strict";' + code, ) - const meta = context[ssrImportMetaKey] - await initModule( context[ssrModuleExportsKey], context[ssrImportMetaKey], context[ssrImportKey], context[ssrDynamicImportKey], context[ssrExportAllKey], - this.options.cjsGlobals ? meta.filename : undefined, - this.options.cjsGlobals ? meta.dirname : undefined, ) Object.seal(context[ssrModuleExportsKey]) diff --git a/packages/vite/src/module-runner/index.ts b/packages/vite/src/module-runner/index.ts index 30501e2590b54a..a5489011d9441f 100644 --- a/packages/vite/src/module-runner/index.ts +++ b/packages/vite/src/module-runner/index.ts @@ -2,10 +2,7 @@ export { EvaluatedModules, type EvaluatedModuleNode } from './evaluatedModules' export { ModuleRunner } from './runner' -export { - ESModulesEvaluator, - type ESModulesEvaluatorOptions, -} from './esmEvaluator' +export { ESModulesEvaluator } from './esmEvaluator' export { createWebSocketModuleRunnerTransport } from '../shared/moduleRunnerTransport' From aad578d6ff93057c6e4795a1c29306be213843bc Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 17:22:58 +0100 Subject: [PATCH 09/27] chore: cleanup --- packages/vite/src/node/cli.ts | 1 + packages/vite/src/node/ssr/import.ts | 14 ++------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 8d0a5133f27031..cce1d0cd3a9333 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -88,6 +88,7 @@ function cleanGlobalCLIOptions( delete ret.l delete ret.logLevel delete ret.clearScreen + delete ret.bundleConfig delete ret.d delete ret.debug delete ret.f diff --git a/packages/vite/src/node/ssr/import.ts b/packages/vite/src/node/ssr/import.ts index fe54efd0ddf7f9..f12ea0ff4a6cc1 100644 --- a/packages/vite/src/node/ssr/import.ts +++ b/packages/vite/src/node/ssr/import.ts @@ -10,13 +10,13 @@ export async function inlineImport( moduleId: string, ): Promise { const environment = createRunnableDevEnvironment( - 'config', + 'inline', // TODO: provide a dummy config? await resolveConfig( { configFile: false, environments: { - config: { + inline: { consumer: 'server', dev: { moduleRunnerTransform: true, @@ -30,16 +30,6 @@ export async function inlineImport( 'serve', ), { - // options: { - // consumer: 'server', - // dev: { - // moduleRunnerTransform: true, - // }, - // TODO for some reason this doesn't work, only setting it the config works - // resolve: { - // external: true, - // }, - // }, runnerOptions: { hmr: { logger: false, From d8f8771884a5c9f6ebd0063b2cddf2e7a69800e2 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 17:45:33 +0100 Subject: [PATCH 10/27] test: add tests for inlineImport --- .../__tests__/fixtures/inline-import/basic.ts | 7 ++++ .../__tests__/fixtures/inline-import/cjs.js | 1 + .../fixtures/inline-import/plugin.ts | 7 ++++ .../fixtures/inline-import/vite.config.ts | 7 ++++ .../src/node/__tests__/inlineImport.spec.ts | 40 +++++++++++++++++++ packages/vite/src/node/config.ts | 6 ++- packages/vite/src/node/index.ts | 1 + .../node/ssr/{import.ts => inlineImport.ts} | 36 ++++++++++------- 8 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts create mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js create mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts create mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts create mode 100644 packages/vite/src/node/__tests__/inlineImport.spec.ts rename packages/vite/src/node/ssr/{import.ts => inlineImport.ts} (58%) diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts b/packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts new file mode 100644 index 00000000000000..2fee8764679469 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts @@ -0,0 +1,7 @@ +interface Test { + field: true +} + +export const test: Test = { + field: true, +} diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js b/packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js new file mode 100644 index 00000000000000..4ba52ba2c8df67 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts b/packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts new file mode 100644 index 00000000000000..173c7d928c4f17 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts @@ -0,0 +1,7 @@ +import { Plugin } from 'vite' + +export default function testPlugin(): Plugin { + return { + name: 'test', + } +} diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts new file mode 100644 index 00000000000000..2cbd562498124b --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import plugin from './plugin' + +export default defineConfig({ + root: './test', + plugins: [plugin()], +}) diff --git a/packages/vite/src/node/__tests__/inlineImport.spec.ts b/packages/vite/src/node/__tests__/inlineImport.spec.ts new file mode 100644 index 00000000000000..f899adc04d380d --- /dev/null +++ b/packages/vite/src/node/__tests__/inlineImport.spec.ts @@ -0,0 +1,40 @@ +import { resolve } from 'node:path' +import { describe, expect, test } from 'vitest' +import { inlineImport } from '../ssr/inlineImport' + +describe('importing files using inlined environment', () => { + const fixture = (name: string) => + resolve(import.meta.dirname, './fixtures/inline-import', name) + + test('importing a basic file works', async () => { + const { module } = await inlineImport< + typeof import('./fixtures/inline-import/basic') + >(fixture('basic')) + expect(module.test).toEqual({ + field: true, + }) + }) + + test("cannot import cjs, 'inlineImport' doesn't support CJS syntax at all", async () => { + await expect(() => + inlineImport( + fixture('cjs.js'), + ), + ).rejects.toThrow('module is not defined') + }) + + test('can import vite config', async () => { + const { module, dependencies } = await inlineImport< + typeof import('./fixtures/inline-import/vite.config') + >(fixture('vite.config')) + expect(module.default).toEqual({ + root: './test', + plugins: [ + { + name: 'test', + }, + ], + }) + expect(dependencies).toEqual([fixture('plugin.ts')]) + }) +}) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index d86f7a3537ebf2..a011070c7211cc 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -98,7 +98,7 @@ import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' -import { inlineImport } from './ssr/import' +import { inlineImport } from './ssr/inlineImport' const debug = createDebugger('vite:config', { depth: 10 }) const promisifiedRealpath = promisify(fs.realpath) @@ -1698,7 +1698,9 @@ export async function loadConfigFromFile( } async function importConfigFile(resolvedPath: string) { - const { module, dependencies } = await inlineImport(resolvedPath) + const { module, dependencies } = await inlineImport<{ + default: UserConfigExport + }>(resolvedPath, { configFile: false }) return { configExport: module.default, dependencies, diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 69f47f3164c148..6f9b7d240766db 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -31,6 +31,7 @@ export { DevEnvironment, type DevEnvironmentContext, } from './server/environment' +export { inlineImport } from './ssr/inlineImport' export { BuildEnvironment } from './build' export { fetchModule, type FetchModuleOptions } from './ssr/fetchModule' diff --git a/packages/vite/src/node/ssr/import.ts b/packages/vite/src/node/ssr/inlineImport.ts similarity index 58% rename from packages/vite/src/node/ssr/import.ts rename to packages/vite/src/node/ssr/inlineImport.ts index f12ea0ff4a6cc1..b39715f0274bcb 100644 --- a/packages/vite/src/node/ssr/import.ts +++ b/packages/vite/src/node/ssr/inlineImport.ts @@ -1,20 +1,26 @@ +import type { InlineConfig } from '../config' import { resolveConfig } from '../config' import { createRunnableDevEnvironment } from '../server/environments/runnableEnvironment' +import { mergeConfig } from '../utils' -interface InlineImportResult { - module: any +interface InlineImportResult { + module: T dependencies: string[] } -export async function inlineImport( +/** + * Import any file using the default Vite environment. + * @experimental + */ +export async function inlineImport( moduleId: string, -): Promise { + inlineConfig?: InlineConfig, +): Promise> { const environment = createRunnableDevEnvironment( 'inline', // TODO: provide a dummy config? await resolveConfig( - { - configFile: false, + mergeConfig(inlineConfig || {}, { environments: { inline: { consumer: 'server', @@ -26,7 +32,7 @@ export async function inlineImport( }, }, }, - }, + }), 'serve', ), { @@ -42,16 +48,18 @@ export async function inlineImport( try { const module = await environment.runner.import(moduleId) const modules = [ - ...environment.runner.evaluatedModules.fileToModulesMap.entries(), + ...environment.runner.evaluatedModules.urlToIdModuleMap.values(), ] const dependencies = modules - .filter(([file, modules]) => { - const isExternal = [...modules].every( - (m) => !m.meta || 'externalize' in m.meta, - ) - return !isExternal && file !== moduleId + .filter((m) => { + // ignore all externalized modules + if (!m.meta || 'externalize' in m.meta) { + return false + } + // ignore the current module + return m.exports !== module }) - .map(([file]) => file) + .map((m) => m.file) return { module, dependencies, From 0d16f859e2201ca9d8be0dcad9b11a6107a90337 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 14 Nov 2024 17:48:28 +0100 Subject: [PATCH 11/27] chore: update public cjs export --- packages/vite/index.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vite/index.cjs b/packages/vite/index.cjs index 70515aa90c7a8d..b40a7a9f520ba6 100644 --- a/packages/vite/index.cjs +++ b/packages/vite/index.cjs @@ -21,6 +21,7 @@ const asyncFunctions = [ 'loadConfigFromFile', 'preprocessCSS', 'createBuilder', + 'inlineImport', ] asyncFunctions.forEach((name) => { module.exports[name] = (...args) => From f295303c6e8a6ca079d562a1f2dd09904f9d1e94 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 09:16:41 +0100 Subject: [PATCH 12/27] refactor: rename bundleConfig to configLoader --- packages/vite/src/node/cli.ts | 12 ++++++------ packages/vite/src/node/config.ts | 14 +++++++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index cce1d0cd3a9333..7c68d392c13d50 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -23,7 +23,7 @@ interface GlobalCLIOptions { l?: LogLevel logLevel?: LogLevel clearScreen?: boolean - bundleConfig?: boolean + configLoader?: string d?: boolean | string debug?: boolean | string f?: string @@ -88,7 +88,7 @@ function cleanGlobalCLIOptions( delete ret.l delete ret.logLevel delete ret.clearScreen - delete ret.bundleConfig + delete ret.configLoader delete ret.d delete ret.debug delete ret.f @@ -186,7 +186,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, - bundleConfig: options.bundleConfig, + configLoader: options.configLoader as undefined, logLevel: options.logLevel, clearScreen: options.clearScreen, optimizeDeps: { force: options.force }, @@ -311,7 +311,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, - bundleConfig: options.bundleConfig, + configLoader: options.configLoader as undefined, logLevel: options.logLevel, clearScreen: options.clearScreen, build: buildOptions, @@ -348,7 +348,7 @@ cli root, base: options.base, configFile: options.config, - bundleConfig: options.bundleConfig, + configLoader: options.configLoader as undefined, logLevel: options.logLevel, mode: options.mode, }, @@ -391,7 +391,7 @@ cli root, base: options.base, configFile: options.config, - bundleConfig: options.bundleConfig, + configLoader: options.configLoader as undefined, logLevel: options.logLevel, mode: options.mode, build: { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index a011070c7211cc..3d38793450fc84 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -529,7 +529,7 @@ export interface ResolvedWorkerOptions { export interface InlineConfig extends UserConfig { configFile?: string | false - bundleConfig?: boolean + configLoader?: 'bundle' | 'runner' envFile?: false } @@ -996,6 +996,7 @@ export async function resolveConfig( config.root, config.logLevel, config.customLogger, + config.configLoader, ) if (loadResult) { config = mergeConfig(loadResult.config, config) @@ -1638,7 +1639,7 @@ export async function loadConfigFromFile( configRoot: string = process.cwd(), logLevel?: LogLevel, customLogger?: Logger, - bundleConfig = true, + configLoader = 'bundle', ): Promise<{ path: string config: UserConfig @@ -1669,8 +1670,15 @@ export async function loadConfigFromFile( return null } + if (configLoader !== 'bundle' && configLoader !== 'runner') { + throw new Error( + `Unsupported configLoader: ${configLoader}. Accepted values are 'bundle' and 'runner'.`, + ) + } + try { - const resolver = bundleConfig ? bundleAndLoadConfigFile : importConfigFile + const resolver = + configLoader === 'bundle' ? bundleAndLoadConfigFile : importConfigFile const { configExport, dependencies } = await resolver(resolvedPath) debug?.(`config file loaded in ${getTime()}`) From d453876223fef1b896c43e7d9ebfc6680bbec94c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 15 Nov 2024 10:55:03 +0100 Subject: [PATCH 13/27] test: fix windows pathing --- packages/vite/src/node/__tests__/inlineImport.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/inlineImport.spec.ts b/packages/vite/src/node/__tests__/inlineImport.spec.ts index f899adc04d380d..9c8304866a0194 100644 --- a/packages/vite/src/node/__tests__/inlineImport.spec.ts +++ b/packages/vite/src/node/__tests__/inlineImport.spec.ts @@ -1,6 +1,7 @@ import { resolve } from 'node:path' import { describe, expect, test } from 'vitest' import { inlineImport } from '../ssr/inlineImport' +import { slash } from '../../shared/utils' describe('importing files using inlined environment', () => { const fixture = (name: string) => @@ -35,6 +36,6 @@ describe('importing files using inlined environment', () => { }, ], }) - expect(dependencies).toEqual([fixture('plugin.ts')]) + expect(dependencies).toEqual([slash(fixture('plugin.ts'))]) }) }) From 65578af8a6e0154ccbf620c6d9c9a828068dbc2f Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 27 Nov 2024 11:22:54 +0100 Subject: [PATCH 14/27] fix: rename the option in the cli config --- docs/guide/cli.md | 37 ++++++++++++++++++----------------- packages/vite/src/node/cli.ts | 4 ++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/guide/cli.md b/docs/guide/cli.md index eb3254be98b80a..5714091a0ece74 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -14,24 +14,25 @@ vite [root] #### Options -| Options | | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| `--host [host]` | Specify hostname (`string`) | -| `--port ` | Specify port (`number`) | -| `--open [path]` | Open browser on startup (`boolean \| string`) | -| `--cors` | Enable CORS (`boolean`) | -| `--strictPort` | Exit if specified port is already in use (`boolean`) | -| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | -| `-v, --version` | Display version number | +| Options | | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `--host [host]` | Specify hostname (`string`) | +| `--port ` | Specify port (`number`) | +| `--open [path]` | Open browser on startup (`boolean \| string`) | +| `--cors` | Enable CORS (`boolean`) | +| `--strictPort` | Exit if specified port is already in use (`boolean`) | +| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | +| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | +| `-v, --version` | Display version number | ## Build diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 7c68d392c13d50..cd6769c90df9a6 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -154,8 +154,8 @@ cli .option('-l, --logLevel ', `[string] info | warn | error | silent`) .option('--clearScreen', `[boolean] allow/disable clear screen when logging`) .option( - '--bundleConfig', - `[boolean] should the config be bundled or evaluated with a module runner when importing`, + '--configLoader ', + `[string] use 'bundle' to bundle the config with esbuild or 'runner' to process it on the fly (default: bundle)`, ) .option('-d, --debug [feat]', `[string | boolean] show debug logs`) .option('-f, --filter ', `[string] filter debug logs`) From df7d928ceedf40ed50d7179e3f8ed725e2add5ed Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 27 Nov 2024 11:30:51 +0100 Subject: [PATCH 15/27] docs: add a note to docs --- docs/config/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/config/index.md b/docs/config/index.md index b380b4b5988230..bf1b1442278d6d 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -23,6 +23,10 @@ You can also explicitly specify a config file to use with the `--config` CLI opt vite --config my-config.js ``` +::: tip LOADING CONFIG BY A MODULE RUNNER +By default, Vite uses `esbuild` to bundle the config into a temporary file. This can cause issues when importing TypeScript files in a monorepo. If you encounter any issues with this approach, you can specify `--configLoader=runner` to use the module runner instead - it will not create a temporary config and will transform any files on the fly. Note that module runner doesn't support CJS in config files, but external CJS packages should work as usual. +::: + ## Config Intellisense Since Vite ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: From 001f7e9d06c8ed7e3e52d7b289483558530edd83 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 27 Nov 2024 11:36:21 +0100 Subject: [PATCH 16/27] docs: rename the heading --- docs/config/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/index.md b/docs/config/index.md index bf1b1442278d6d..5606e4e5dd07ee 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -23,7 +23,7 @@ You can also explicitly specify a config file to use with the `--config` CLI opt vite --config my-config.js ``` -::: tip LOADING CONFIG BY A MODULE RUNNER +::: tip BUNDLING THE CONFIG By default, Vite uses `esbuild` to bundle the config into a temporary file. This can cause issues when importing TypeScript files in a monorepo. If you encounter any issues with this approach, you can specify `--configLoader=runner` to use the module runner instead - it will not create a temporary config and will transform any files on the fly. Note that module runner doesn't support CJS in config files, but external CJS packages should work as usual. ::: From 1adc48692180e04f3d1a61fe8444ff1a2151504c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 28 Nov 2024 15:06:49 +0100 Subject: [PATCH 17/27] fix: revert isModuleSyncConditionEnabled, fix types --- docs/guide/cli.md | 55 ++++++++++++---------- packages/vite/src/node/cli.ts | 10 ++-- packages/vite/src/node/config.ts | 9 +++- packages/vite/src/node/ssr/inlineImport.ts | 9 +++- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 5714091a0ece74..c61abbeb5b72a7 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -65,6 +65,7 @@ vite build [root] | `--base ` | Public base path (default: `/`) (`string`) | | `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | | `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | | `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | | `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | | `-f, --filter ` | Filter debug logs (`string`) | @@ -86,17 +87,18 @@ vite optimize [root] #### Options -| Options | | -| ------------------------ | ----------------------------------------------------------------- | -| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | +| Options | | +| ------------------------- | ------------------------------------------------------------------------------------------------------- | +| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | ### `vite preview` @@ -110,18 +112,19 @@ vite preview [root] #### Options -| Options | | -| ------------------------ | ---------------------------------------------------- | -| `--host [host]` | Specify hostname (`string`) | -| `--port ` | Specify port (`number`) | -| `--strictPort` | Exit if specified port is already in use (`boolean`) | -| `--open [path]` | Open browser on startup (`boolean \| string`) | -| `--outDir ` | Output directory (default: `dist`)(`string`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | +| Options | | +| ------------------------- | ------------------------------------------------------------------------------------------------------- | +| `--host [host]` | Specify hostname (`string`) | +| `--port ` | Specify port (`number`) | +| `--strictPort` | Exit if specified port is already in use (`boolean`) | +| `--open [path]` | Open browser on startup (`boolean \| string`) | +| `--outDir ` | Output directory (default: `dist`)(`string`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index cd6769c90df9a6..31db22b763b3ca 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -23,7 +23,7 @@ interface GlobalCLIOptions { l?: LogLevel logLevel?: LogLevel clearScreen?: boolean - configLoader?: string + configLoader?: 'bundle' | 'runner' d?: boolean | string debug?: boolean | string f?: string @@ -186,7 +186,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, - configLoader: options.configLoader as undefined, + configLoader: options.configLoader, logLevel: options.logLevel, clearScreen: options.clearScreen, optimizeDeps: { force: options.force }, @@ -311,7 +311,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, - configLoader: options.configLoader as undefined, + configLoader: options.configLoader, logLevel: options.logLevel, clearScreen: options.clearScreen, build: buildOptions, @@ -348,7 +348,7 @@ cli root, base: options.base, configFile: options.config, - configLoader: options.configLoader as undefined, + configLoader: options.configLoader, logLevel: options.logLevel, mode: options.mode, }, @@ -391,7 +391,7 @@ cli root, base: options.base, configFile: options.config, - configLoader: options.configLoader as undefined, + configLoader: options.configLoader, logLevel: options.logLevel, mode: options.mode, build: { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 3d38793450fc84..2c108c3e1e40ae 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1639,7 +1639,7 @@ export async function loadConfigFromFile( configRoot: string = process.cwd(), logLevel?: LogLevel, customLogger?: Logger, - configLoader = 'bundle', + configLoader: 'bundle' | 'runner' = 'bundle', ): Promise<{ path: string config: UserConfig @@ -1736,6 +1736,8 @@ async function bundleConfigFile( fileName: string, isESM: boolean, ): Promise<{ code: string; dependencies: string[] }> { + const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) + .default const dirnameVarName = '__vite_injected_original_dirname' const filenameVarName = '__vite_injected_original_filename' const importMetaUrlVarName = '__vite_injected_original_import_meta_url' @@ -1774,7 +1776,10 @@ async function bundleConfigFile( preferRelative: false, tryIndex: true, mainFields: [], - conditions: ['node'], + conditions: [ + 'node', + ...(isModuleSyncConditionEnabled ? ['module-sync'] : []), + ], externalConditions: [], external: [], noExternal: [], diff --git a/packages/vite/src/node/ssr/inlineImport.ts b/packages/vite/src/node/ssr/inlineImport.ts index b39715f0274bcb..734e7a3cafe8b3 100644 --- a/packages/vite/src/node/ssr/inlineImport.ts +++ b/packages/vite/src/node/ssr/inlineImport.ts @@ -16,6 +16,8 @@ export async function inlineImport( moduleId: string, inlineConfig?: InlineConfig, ): Promise> { + const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) + .default const environment = createRunnableDevEnvironment( 'inline', // TODO: provide a dummy config? @@ -29,10 +31,15 @@ export async function inlineImport( }, resolve: { external: true, + mainFields: [], + conditions: [ + 'node', + ...(isModuleSyncConditionEnabled ? ['module-sync'] : []), + ], }, }, }, - }), + } satisfies InlineConfig), 'serve', ), { From 5ecd5290f4bc50d70be8cf1a14f7249bfcfdc0c6 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 28 Nov 2024 15:07:46 +0100 Subject: [PATCH 18/27] test: add comment for moduleDirectories --- vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vitest.config.ts b/vitest.config.ts index 4cece0f89e7293..919e0a30d650bd 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ './playground-temp/**/*.*', ], deps: { + // we specify 'packages' so Vitest doesn't inline the files moduleDirectories: ['node_modules', 'packages'], }, testTimeout: 20000, From 275a593d418fd8e6dc2cffc69280e951c93bd531 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 28 Nov 2024 16:33:39 +0100 Subject: [PATCH 19/27] test: add workspace test --- .../vite.config.outside-pkg-import.mts | 6 +++++ .../src/node/__tests__/inlineImport.spec.ts | 23 +++++++++++++++++++ packages/vite/src/node/__tests__/package.json | 1 + .../node/__tests__/packages/child/index.js | 1 + .../__tests__/packages/child/package.json | 5 ++++ .../node/__tests__/packages/parent/index.ts | 6 +++++ .../__tests__/packages/parent/package.json | 8 +++++++ pnpm-lock.yaml | 11 +++++++++ 8 files changed, 61 insertions(+) create mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts create mode 100644 packages/vite/src/node/__tests__/packages/child/index.js create mode 100644 packages/vite/src/node/__tests__/packages/child/package.json create mode 100644 packages/vite/src/node/__tests__/packages/parent/index.ts create mode 100644 packages/vite/src/node/__tests__/packages/parent/package.json diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts new file mode 100644 index 00000000000000..748bbd3326d376 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts @@ -0,0 +1,6 @@ +// @ts-expect-error not typed +import parent from 'parent' + +export default { + __injected: parent.child, +} diff --git a/packages/vite/src/node/__tests__/inlineImport.spec.ts b/packages/vite/src/node/__tests__/inlineImport.spec.ts index 9c8304866a0194..b876c9debdda09 100644 --- a/packages/vite/src/node/__tests__/inlineImport.spec.ts +++ b/packages/vite/src/node/__tests__/inlineImport.spec.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path' import { describe, expect, test } from 'vitest' +import { loadConfigFromFile } from 'vite' import { inlineImport } from '../ssr/inlineImport' import { slash } from '../../shared/utils' @@ -38,4 +39,26 @@ describe('importing files using inlined environment', () => { }) expect(dependencies).toEqual([slash(fixture('plugin.ts'))]) }) + + test('can import vite config that imports a TS external module', async () => { + const { module, dependencies } = await inlineImport< + typeof import('./fixtures/inline-import/vite.config.outside-pkg-import.mjs') + >(fixture('vite.config.outside-pkg-import.mts')) + + expect(module.default.__injected).toBe(true) + expect(dependencies).toEqual([ + slash(resolve(import.meta.dirname, './packages/parent/index.ts')), + ]) + + // confirm that it fails with a bundle approach + await expect(async () => { + const root = resolve(import.meta.dirname, './fixtures/inline-import') + await loadConfigFromFile( + { mode: 'production', command: 'serve' }, + resolve(root, './vite.config.outside-pkg-import.mts'), + root, + 'silent', + ) + }).rejects.toThrow('Unknown file extension ".ts"') + }) }) diff --git a/packages/vite/src/node/__tests__/package.json b/packages/vite/src/node/__tests__/package.json index 90a5d7c132c1ee..605363e5411c6a 100644 --- a/packages/vite/src/node/__tests__/package.json +++ b/packages/vite/src/node/__tests__/package.json @@ -3,6 +3,7 @@ "private": true, "version": "0.0.0", "dependencies": { + "parent": "link:./packages/parent", "@vitejs/cjs-ssr-dep": "link:./fixtures/cjs-ssr-dep", "@vitejs/test-dep-conditions": "file:./fixtures/test-dep-conditions" } diff --git a/packages/vite/src/node/__tests__/packages/child/index.js b/packages/vite/src/node/__tests__/packages/child/index.js new file mode 100644 index 00000000000000..186b120756be19 --- /dev/null +++ b/packages/vite/src/node/__tests__/packages/child/index.js @@ -0,0 +1 @@ +export default true diff --git a/packages/vite/src/node/__tests__/packages/child/package.json b/packages/vite/src/node/__tests__/packages/child/package.json new file mode 100644 index 00000000000000..ea0965b5b6d53d --- /dev/null +++ b/packages/vite/src/node/__tests__/packages/child/package.json @@ -0,0 +1,5 @@ +{ + "name": "child", + "type": "module", + "main": "./index.js" +} diff --git a/packages/vite/src/node/__tests__/packages/parent/index.ts b/packages/vite/src/node/__tests__/packages/parent/index.ts new file mode 100644 index 00000000000000..0a8feb5e9b1992 --- /dev/null +++ b/packages/vite/src/node/__tests__/packages/parent/index.ts @@ -0,0 +1,6 @@ +// @ts-expect-error not typed +import child from 'child' + +export default { + child, +} diff --git a/packages/vite/src/node/__tests__/packages/parent/package.json b/packages/vite/src/node/__tests__/packages/parent/package.json new file mode 100644 index 00000000000000..bbb7f8414e4a55 --- /dev/null +++ b/packages/vite/src/node/__tests__/packages/parent/package.json @@ -0,0 +1,8 @@ +{ + "name": "parent", + "type": "module", + "main": "./index.ts", + "dependencies": { + "child": "link:../child" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 461326d448fb52..4258a658d38cac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -420,6 +420,9 @@ importers: '@vitejs/test-dep-conditions': specifier: file:./fixtures/test-dep-conditions version: file:packages/vite/src/node/__tests__/fixtures/test-dep-conditions + parent: + specifier: link:./packages/parent + version: link:packages/parent packages/vite/src/node/__tests__/fixtures/cjs-ssr-dep: {} @@ -427,12 +430,20 @@ importers: packages/vite/src/node/__tests__/fixtures/test-dep-conditions: {} + packages/vite/src/node/__tests__/packages/child: {} + packages/vite/src/node/__tests__/packages/module: {} packages/vite/src/node/__tests__/packages/name: {} packages/vite/src/node/__tests__/packages/noname: {} + packages/vite/src/node/__tests__/packages/parent: + dependencies: + child: + specifier: link:../child + version: link:../child + packages/vite/src/node/server/__tests__/fixtures/lerna/nested: {} packages/vite/src/node/server/__tests__/fixtures/none/nested: {} From b38365120a8c9615a19324ccac6b70573300312a Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sun, 1 Dec 2024 23:29:59 +0100 Subject: [PATCH 20/27] refactor: mark configLoader as experimental --- docs/guide/cli.md | 142 +++++++++++++++---------------- packages/vite/src/node/cli.ts | 2 +- packages/vite/src/node/config.ts | 2 + 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/docs/guide/cli.md b/docs/guide/cli.md index c61abbeb5b72a7..faaeefa18cb20f 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -14,25 +14,25 @@ vite [root] #### Options -| Options | | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `--host [host]` | Specify hostname (`string`) | -| `--port ` | Specify port (`number`) | -| `--open [path]` | Open browser on startup (`boolean \| string`) | -| `--cors` | Enable CORS (`boolean`) | -| `--strictPort` | Exit if specified port is already in use (`boolean`) | -| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | -| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | -| `-v, --version` | Display version number | +| Options | | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `--host [host]` | Specify hostname (`string`) | +| `--port ` | Specify port (`number`) | +| `--open [path]` | Open browser on startup (`boolean \| string`) | +| `--cors` | Enable CORS (`boolean`) | +| `--strictPort` | Exit if specified port is already in use (`boolean`) | +| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) | +| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | +| `-v, --version` | Display version number | ## Build @@ -48,30 +48,30 @@ vite build [root] #### Options -| Options | | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `--target ` | Transpile target (default: `"modules"`) (`string`) | -| `--outDir ` | Output directory (default: `dist`) (`string`) | -| `--assetsDir ` | Directory under outDir to place assets in (default: `"assets"`) (`string`) | -| `--assetsInlineLimit ` | Static asset base64 inline threshold in bytes (default: `4096`) (`number`) | -| `--ssr [entry]` | Build specified entry for server-side rendering (`string`) | -| `--sourcemap [output]` | Output source maps for build (default: `false`) (`boolean \| "inline" \| "hidden"`) | -| `--minify [minifier]` | Enable/disable minification, or specify minifier to use (default: `"esbuild"`) (`boolean \| "terser" \| "esbuild"`) | -| `--manifest [name]` | Emit build manifest json (`boolean \| string`) | -| `--ssrManifest [name]` | Emit ssr manifest json (`boolean \| string`) | -| `--emptyOutDir` | Force empty outDir when it's outside of root (`boolean`) | -| `-w, --watch` | Rebuilds when modules have changed on disk (`boolean`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | -| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | -| `--app` | Build all environments, same as `builder: {}` (`boolean`, experimental) | +| Options | | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| `--target ` | Transpile target (default: `"modules"`) (`string`) | +| `--outDir ` | Output directory (default: `dist`) (`string`) | +| `--assetsDir ` | Directory under outDir to place assets in (default: `"assets"`) (`string`) | +| `--assetsInlineLimit ` | Static asset base64 inline threshold in bytes (default: `4096`) (`number`) | +| `--ssr [entry]` | Build specified entry for server-side rendering (`string`) | +| `--sourcemap [output]` | Output source maps for build (default: `false`) (`boolean \| "inline" \| "hidden"`) | +| `--minify [minifier]` | Enable/disable minification, or specify minifier to use (default: `"esbuild"`) (`boolean \| "terser" \| "esbuild"`) | +| `--manifest [name]` | Emit build manifest json (`boolean \| string`) | +| `--ssrManifest [name]` | Emit ssr manifest json (`boolean \| string`) | +| `--emptyOutDir` | Force empty outDir when it's outside of root (`boolean`) | +| `-w, --watch` | Rebuilds when modules have changed on disk (`boolean`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) | +| `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](/guide/troubleshooting#performance-bottlenecks)) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | +| `--app` | Build all environments, same as `builder: {}` (`boolean`, experimental) | ## Others @@ -87,18 +87,18 @@ vite optimize [root] #### Options -| Options | | -| ------------------------- | ------------------------------------------------------------------------------------------------------- | -| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | +| Options | | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | ### `vite preview` @@ -112,19 +112,19 @@ vite preview [root] #### Options -| Options | | -| ------------------------- | ------------------------------------------------------------------------------------------------------- | -| `--host [host]` | Specify hostname (`string`) | -| `--port ` | Specify port (`number`) | -| `--strictPort` | Exit if specified port is already in use (`boolean`) | -| `--open [path]` | Open browser on startup (`boolean \| string`) | -| `--outDir ` | Output directory (default: `dist`)(`string`) | -| `-c, --config ` | Use specified config file (`string`) | -| `--base ` | Public base path (default: `/`) (`string`) | -| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | -| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | -| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` to process it on the fly (default: `bundle`) | -| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | -| `-f, --filter ` | Filter debug logs (`string`) | -| `-m, --mode ` | Set env mode (`string`) | -| `-h, --help` | Display available CLI options | +| Options | | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `--host [host]` | Specify hostname (`string`) | +| `--port ` | Specify port (`number`) | +| `--strictPort` | Exit if specified port is already in use (`boolean`) | +| `--open [path]` | Open browser on startup (`boolean \| string`) | +| `--outDir ` | Output directory (default: `dist`)(`string`) | +| `-c, --config ` | Use specified config file (`string`) | +| `--base ` | Public base path (default: `/`) (`string`) | +| `-l, --logLevel ` | Info \| warn \| error \| silent (`string`) | +| `--clearScreen` | Allow/disable clear screen when logging (`boolean`) | +| `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) | +| `-d, --debug [feat]` | Show debug logs (`string \| boolean`) | +| `-f, --filter ` | Filter debug logs (`string`) | +| `-m, --mode ` | Set env mode (`string`) | +| `-h, --help` | Display available CLI options | diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 31db22b763b3ca..bb86e1c6e8a34a 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -155,7 +155,7 @@ cli .option('--clearScreen', `[boolean] allow/disable clear screen when logging`) .option( '--configLoader ', - `[string] use 'bundle' to bundle the config with esbuild or 'runner' to process it on the fly (default: bundle)`, + `[string] use 'bundle' to bundle the config with esbuild or 'runner' (experimental) to process it on the fly (default: bundle)`, ) .option('-d, --debug [feat]', `[string | boolean] show debug logs`) .option('-f, --filter ', `[string] filter debug logs`) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 2c108c3e1e40ae..c5c6861cad154e 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -529,6 +529,7 @@ export interface ResolvedWorkerOptions { export interface InlineConfig extends UserConfig { configFile?: string | false + /** @experimental */ configLoader?: 'bundle' | 'runner' envFile?: false } @@ -1738,6 +1739,7 @@ async function bundleConfigFile( ): Promise<{ code: string; dependencies: string[] }> { const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) .default + const dirnameVarName = '__vite_injected_original_dirname' const filenameVarName = '__vite_injected_original_filename' const importMetaUrlVarName = '__vite_injected_original_import_meta_url' From 14571ac2764a896c80314bc873aa6d61d1a193b5 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 2 Dec 2024 11:29:42 +0100 Subject: [PATCH 21/27] perf: disable config, envFile and cacheDir --- packages/vite/src/node/config.ts | 2 +- packages/vite/src/node/ssr/inlineImport.ts | 57 +++++++++++----------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index c5c6861cad154e..ae3b2608259807 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1709,7 +1709,7 @@ export async function loadConfigFromFile( async function importConfigFile(resolvedPath: string) { const { module, dependencies } = await inlineImport<{ default: UserConfigExport - }>(resolvedPath, { configFile: false }) + }>(resolvedPath) return { configExport: module.default, dependencies, diff --git a/packages/vite/src/node/ssr/inlineImport.ts b/packages/vite/src/node/ssr/inlineImport.ts index 734e7a3cafe8b3..3099c09c92dc56 100644 --- a/packages/vite/src/node/ssr/inlineImport.ts +++ b/packages/vite/src/node/ssr/inlineImport.ts @@ -18,39 +18,38 @@ export async function inlineImport( ): Promise> { const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) .default - const environment = createRunnableDevEnvironment( - 'inline', - // TODO: provide a dummy config? - await resolveConfig( - mergeConfig(inlineConfig || {}, { - environments: { - inline: { - consumer: 'server', - dev: { - moduleRunnerTransform: true, - }, - resolve: { - external: true, - mainFields: [], - conditions: [ - 'node', - ...(isModuleSyncConditionEnabled ? ['module-sync'] : []), - ], - }, + const config = await resolveConfig( + mergeConfig(inlineConfig || {}, { + configFile: false, + envFile: false, + cacheDir: process.cwd(), + environments: { + inline: { + consumer: 'server', + dev: { + moduleRunnerTransform: true, + }, + resolve: { + external: true, + mainFields: [], + conditions: [ + 'node', + ...(isModuleSyncConditionEnabled ? ['module-sync'] : []), + ], }, - }, - } satisfies InlineConfig), - 'serve', - ), - { - runnerOptions: { - hmr: { - logger: false, }, }, - hot: false, - }, + } satisfies InlineConfig), + 'serve', ) + const environment = createRunnableDevEnvironment('inline', config, { + runnerOptions: { + hmr: { + logger: false, + }, + }, + hot: false, + }) await environment.init() try { const module = await environment.runner.import(moduleId) From d8479615d24aa5ebe4316cd1e412b9a323cb4286 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 2 Dec 2024 11:46:19 +0100 Subject: [PATCH 22/27] chore: remove unused directive --- .../fixtures/inline-import/vite.config.outside-pkg-import.mts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts index 748bbd3326d376..401aadce8bf803 100644 --- a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts +++ b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts @@ -1,4 +1,3 @@ -// @ts-expect-error not typed import parent from 'parent' export default { From 07867ae15a93eede1cfcdb9174efacd3559b3b26 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 4 Dec 2024 10:04:31 +0100 Subject: [PATCH 23/27] chore: rename to runnerImport, rename test packages --- .../vite.config.outside-pkg-import.mts | 5 ----- .../{inline-import => runner-import}/basic.ts | 0 .../{inline-import => runner-import}/cjs.js | 0 .../plugin.ts | 0 .../vite.config.outside-pkg-import.mts | 6 ++++++ .../vite.config.ts | 0 packages/vite/src/node/__tests__/package.json | 2 +- .../__tests__/packages/child/package.json | 2 +- .../node/__tests__/packages/parent/index.ts | 2 +- .../__tests__/packages/parent/package.json | 4 ++-- ...ineImport.spec.ts => runnerImport.spec.ts} | 20 +++++++++---------- packages/vite/src/node/config.ts | 4 ++-- packages/vite/src/node/index.ts | 2 +- .../ssr/{inlineImport.ts => runnerImport.ts} | 2 +- pnpm-lock.yaml | 8 ++++---- 15 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts rename packages/vite/src/node/__tests__/fixtures/{inline-import => runner-import}/basic.ts (100%) rename packages/vite/src/node/__tests__/fixtures/{inline-import => runner-import}/cjs.js (100%) rename packages/vite/src/node/__tests__/fixtures/{inline-import => runner-import}/plugin.ts (100%) create mode 100644 packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts rename packages/vite/src/node/__tests__/fixtures/{inline-import => runner-import}/vite.config.ts (100%) rename packages/vite/src/node/__tests__/{inlineImport.spec.ts => runnerImport.spec.ts} (72%) rename packages/vite/src/node/ssr/{inlineImport.ts => runnerImport.ts} (97%) diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts b/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts deleted file mode 100644 index 401aadce8bf803..00000000000000 --- a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.outside-pkg-import.mts +++ /dev/null @@ -1,5 +0,0 @@ -import parent from 'parent' - -export default { - __injected: parent.child, -} diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts b/packages/vite/src/node/__tests__/fixtures/runner-import/basic.ts similarity index 100% rename from packages/vite/src/node/__tests__/fixtures/inline-import/basic.ts rename to packages/vite/src/node/__tests__/fixtures/runner-import/basic.ts diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js b/packages/vite/src/node/__tests__/fixtures/runner-import/cjs.js similarity index 100% rename from packages/vite/src/node/__tests__/fixtures/inline-import/cjs.js rename to packages/vite/src/node/__tests__/fixtures/runner-import/cjs.js diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts b/packages/vite/src/node/__tests__/fixtures/runner-import/plugin.ts similarity index 100% rename from packages/vite/src/node/__tests__/fixtures/inline-import/plugin.ts rename to packages/vite/src/node/__tests__/fixtures/runner-import/plugin.ts diff --git a/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts b/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts new file mode 100644 index 00000000000000..21bff5f0bf9a93 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts @@ -0,0 +1,6 @@ +// @ts-expect-error not typed +import parent from '@vitejs/parent' + +export default { + __injected: parent.child, +} diff --git a/packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts b/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.ts similarity index 100% rename from packages/vite/src/node/__tests__/fixtures/inline-import/vite.config.ts rename to packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.ts diff --git a/packages/vite/src/node/__tests__/package.json b/packages/vite/src/node/__tests__/package.json index 605363e5411c6a..4f6f029f385542 100644 --- a/packages/vite/src/node/__tests__/package.json +++ b/packages/vite/src/node/__tests__/package.json @@ -3,7 +3,7 @@ "private": true, "version": "0.0.0", "dependencies": { - "parent": "link:./packages/parent", + "@vitejs/parent": "link:./packages/parent", "@vitejs/cjs-ssr-dep": "link:./fixtures/cjs-ssr-dep", "@vitejs/test-dep-conditions": "file:./fixtures/test-dep-conditions" } diff --git a/packages/vite/src/node/__tests__/packages/child/package.json b/packages/vite/src/node/__tests__/packages/child/package.json index ea0965b5b6d53d..77e2aa64615b63 100644 --- a/packages/vite/src/node/__tests__/packages/child/package.json +++ b/packages/vite/src/node/__tests__/packages/child/package.json @@ -1,5 +1,5 @@ { - "name": "child", + "name": "@vitejs/child", "type": "module", "main": "./index.js" } diff --git a/packages/vite/src/node/__tests__/packages/parent/index.ts b/packages/vite/src/node/__tests__/packages/parent/index.ts index 0a8feb5e9b1992..747305283cadb2 100644 --- a/packages/vite/src/node/__tests__/packages/parent/index.ts +++ b/packages/vite/src/node/__tests__/packages/parent/index.ts @@ -1,5 +1,5 @@ // @ts-expect-error not typed -import child from 'child' +import child from '@vitejs/child' export default { child, diff --git a/packages/vite/src/node/__tests__/packages/parent/package.json b/packages/vite/src/node/__tests__/packages/parent/package.json index bbb7f8414e4a55..d966448a0560a8 100644 --- a/packages/vite/src/node/__tests__/packages/parent/package.json +++ b/packages/vite/src/node/__tests__/packages/parent/package.json @@ -1,8 +1,8 @@ { - "name": "parent", + "name": "@vitejs/parent", "type": "module", "main": "./index.ts", "dependencies": { - "child": "link:../child" + "@vitejs/child": "link:../child" } } diff --git a/packages/vite/src/node/__tests__/inlineImport.spec.ts b/packages/vite/src/node/__tests__/runnerImport.spec.ts similarity index 72% rename from packages/vite/src/node/__tests__/inlineImport.spec.ts rename to packages/vite/src/node/__tests__/runnerImport.spec.ts index b876c9debdda09..b847d6e2d871f2 100644 --- a/packages/vite/src/node/__tests__/inlineImport.spec.ts +++ b/packages/vite/src/node/__tests__/runnerImport.spec.ts @@ -1,16 +1,16 @@ import { resolve } from 'node:path' import { describe, expect, test } from 'vitest' import { loadConfigFromFile } from 'vite' -import { inlineImport } from '../ssr/inlineImport' +import { runnerImport } from '../ssr/runnerImport' import { slash } from '../../shared/utils' describe('importing files using inlined environment', () => { const fixture = (name: string) => - resolve(import.meta.dirname, './fixtures/inline-import', name) + resolve(import.meta.dirname, './fixtures/runner-import', name) test('importing a basic file works', async () => { - const { module } = await inlineImport< - typeof import('./fixtures/inline-import/basic') + const { module } = await runnerImport< + typeof import('./fixtures/runner-import/basic') >(fixture('basic')) expect(module.test).toEqual({ field: true, @@ -19,15 +19,15 @@ describe('importing files using inlined environment', () => { test("cannot import cjs, 'inlineImport' doesn't support CJS syntax at all", async () => { await expect(() => - inlineImport( + runnerImport( fixture('cjs.js'), ), ).rejects.toThrow('module is not defined') }) test('can import vite config', async () => { - const { module, dependencies } = await inlineImport< - typeof import('./fixtures/inline-import/vite.config') + const { module, dependencies } = await runnerImport< + typeof import('./fixtures/runner-import/vite.config') >(fixture('vite.config')) expect(module.default).toEqual({ root: './test', @@ -41,8 +41,8 @@ describe('importing files using inlined environment', () => { }) test('can import vite config that imports a TS external module', async () => { - const { module, dependencies } = await inlineImport< - typeof import('./fixtures/inline-import/vite.config.outside-pkg-import.mjs') + const { module, dependencies } = await runnerImport< + typeof import('./fixtures/runner-import/vite.config.outside-pkg-import.mjs') >(fixture('vite.config.outside-pkg-import.mts')) expect(module.default.__injected).toBe(true) @@ -52,7 +52,7 @@ describe('importing files using inlined environment', () => { // confirm that it fails with a bundle approach await expect(async () => { - const root = resolve(import.meta.dirname, './fixtures/inline-import') + const root = resolve(import.meta.dirname, './fixtures/runner-import') await loadConfigFromFile( { mode: 'production', command: 'serve' }, resolve(root, './vite.config.outside-pkg-import.mts'), diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index ae3b2608259807..228f36b6426ab6 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -98,7 +98,7 @@ import type { ResolvedSSROptions, SSROptions } from './ssr' import { resolveSSROptions, ssrConfigDefaults } from './ssr' import { PartialEnvironment } from './baseEnvironment' import { createIdResolver } from './idResolver' -import { inlineImport } from './ssr/inlineImport' +import { runnerImport } from './ssr/runnerImport' const debug = createDebugger('vite:config', { depth: 10 }) const promisifiedRealpath = promisify(fs.realpath) @@ -1707,7 +1707,7 @@ export async function loadConfigFromFile( } async function importConfigFile(resolvedPath: string) { - const { module, dependencies } = await inlineImport<{ + const { module, dependencies } = await runnerImport<{ default: UserConfigExport }>(resolvedPath) return { diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 6f9b7d240766db..4e74ee6ed45638 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -31,7 +31,7 @@ export { DevEnvironment, type DevEnvironmentContext, } from './server/environment' -export { inlineImport } from './ssr/inlineImport' +export { runnerImport as inlineImport } from './ssr/runnerImport' export { BuildEnvironment } from './build' export { fetchModule, type FetchModuleOptions } from './ssr/fetchModule' diff --git a/packages/vite/src/node/ssr/inlineImport.ts b/packages/vite/src/node/ssr/runnerImport.ts similarity index 97% rename from packages/vite/src/node/ssr/inlineImport.ts rename to packages/vite/src/node/ssr/runnerImport.ts index 3099c09c92dc56..a8267f6abd7655 100644 --- a/packages/vite/src/node/ssr/inlineImport.ts +++ b/packages/vite/src/node/ssr/runnerImport.ts @@ -12,7 +12,7 @@ interface InlineImportResult { * Import any file using the default Vite environment. * @experimental */ -export async function inlineImport( +export async function runnerImport( moduleId: string, inlineConfig?: InlineConfig, ): Promise> { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4258a658d38cac..683ca86bee4d62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -417,12 +417,12 @@ importers: '@vitejs/cjs-ssr-dep': specifier: link:./fixtures/cjs-ssr-dep version: link:fixtures/cjs-ssr-dep + '@vitejs/parent': + specifier: link:./packages/parent + version: link:packages/parent '@vitejs/test-dep-conditions': specifier: file:./fixtures/test-dep-conditions version: file:packages/vite/src/node/__tests__/fixtures/test-dep-conditions - parent: - specifier: link:./packages/parent - version: link:packages/parent packages/vite/src/node/__tests__/fixtures/cjs-ssr-dep: {} @@ -440,7 +440,7 @@ importers: packages/vite/src/node/__tests__/packages/parent: dependencies: - child: + '@vitejs/child': specifier: link:../child version: link:../child From 646a069f72d5bfce2bc85e74679d5e237b160114 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 4 Dec 2024 12:04:02 +0100 Subject: [PATCH 24/27] lint: remove unesed ts-expect-error --- .../fixtures/runner-import/vite.config.outside-pkg-import.mts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts b/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts index 21bff5f0bf9a93..86fea8ffe713f7 100644 --- a/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts +++ b/packages/vite/src/node/__tests__/fixtures/runner-import/vite.config.outside-pkg-import.mts @@ -1,4 +1,3 @@ -// @ts-expect-error not typed import parent from '@vitejs/parent' export default { From 9b2179a48146c38eeb194122661a5cc53640f9ea Mon Sep 17 00:00:00 2001 From: bluwy Date: Wed, 4 Dec 2024 21:51:14 +0800 Subject: [PATCH 25/27] fix: update other names --- packages/vite/index.cjs | 2 +- packages/vite/src/node/__tests__/runnerImport.spec.ts | 2 +- packages/vite/src/node/index.ts | 2 +- packages/vite/src/node/ssr/runnerImport.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vite/index.cjs b/packages/vite/index.cjs index b40a7a9f520ba6..c46eb3b7333e7d 100644 --- a/packages/vite/index.cjs +++ b/packages/vite/index.cjs @@ -21,7 +21,7 @@ const asyncFunctions = [ 'loadConfigFromFile', 'preprocessCSS', 'createBuilder', - 'inlineImport', + 'runnerImport', ] asyncFunctions.forEach((name) => { module.exports[name] = (...args) => diff --git a/packages/vite/src/node/__tests__/runnerImport.spec.ts b/packages/vite/src/node/__tests__/runnerImport.spec.ts index b847d6e2d871f2..bb074e8a68b4e8 100644 --- a/packages/vite/src/node/__tests__/runnerImport.spec.ts +++ b/packages/vite/src/node/__tests__/runnerImport.spec.ts @@ -17,7 +17,7 @@ describe('importing files using inlined environment', () => { }) }) - test("cannot import cjs, 'inlineImport' doesn't support CJS syntax at all", async () => { + test("cannot import cjs, 'runnerImport' doesn't support CJS syntax at all", async () => { await expect(() => runnerImport( fixture('cjs.js'), diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 4e74ee6ed45638..5dcc1cc670825e 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -31,7 +31,7 @@ export { DevEnvironment, type DevEnvironmentContext, } from './server/environment' -export { runnerImport as inlineImport } from './ssr/runnerImport' +export { runnerImport } from './ssr/runnerImport' export { BuildEnvironment } from './build' export { fetchModule, type FetchModuleOptions } from './ssr/fetchModule' diff --git a/packages/vite/src/node/ssr/runnerImport.ts b/packages/vite/src/node/ssr/runnerImport.ts index a8267f6abd7655..d24f9e58a55368 100644 --- a/packages/vite/src/node/ssr/runnerImport.ts +++ b/packages/vite/src/node/ssr/runnerImport.ts @@ -3,7 +3,7 @@ import { resolveConfig } from '../config' import { createRunnableDevEnvironment } from '../server/environments/runnableEnvironment' import { mergeConfig } from '../utils' -interface InlineImportResult { +interface RunnerImportResult { module: T dependencies: string[] } @@ -15,7 +15,7 @@ interface InlineImportResult { export async function runnerImport( moduleId: string, inlineConfig?: InlineConfig, -): Promise> { +): Promise> { const isModuleSyncConditionEnabled = (await import('#module-sync-enabled')) .default const config = await resolveConfig( From 86694897667e26d7dc929ebf2c982e0108d1ba25 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 9 Dec 2024 10:16:45 +0100 Subject: [PATCH 26/27] refactor: move configLoader validation --- packages/vite/src/node/config.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 228f36b6426ab6..f67716e920501d 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1646,6 +1646,12 @@ export async function loadConfigFromFile( config: UserConfig dependencies: string[] } | null> { + if (configLoader !== 'bundle' && configLoader !== 'runner') { + throw new Error( + `Unsupported configLoader: ${configLoader}. Accepted values are 'bundle' and 'runner'.`, + ) + } + const start = performance.now() const getTime = () => `${(performance.now() - start).toFixed(2)}ms` @@ -1671,12 +1677,6 @@ export async function loadConfigFromFile( return null } - if (configLoader !== 'bundle' && configLoader !== 'runner') { - throw new Error( - `Unsupported configLoader: ${configLoader}. Accepted values are 'bundle' and 'runner'.`, - ) - } - try { const resolver = configLoader === 'bundle' ? bundleAndLoadConfigFile : importConfigFile From 19c501947d2b6f12a18acc600fe71bd032d0381b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 22 Dec 2024 15:44:33 +0900 Subject: [PATCH 27/27] test: dynamic import --- .../fixtures/runner-import/dynamic-import-dep.ts | 1 + .../__tests__/fixtures/runner-import/dynamic-import.ts | 1 + packages/vite/src/node/__tests__/runnerImport.spec.ts | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import-dep.ts create mode 100644 packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import.ts diff --git a/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import-dep.ts b/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import-dep.ts new file mode 100644 index 00000000000000..60c71f346d9a3e --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import-dep.ts @@ -0,0 +1 @@ +export default 'ok' diff --git a/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import.ts b/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import.ts new file mode 100644 index 00000000000000..b406cfff6e5ac2 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/runner-import/dynamic-import.ts @@ -0,0 +1 @@ +export default () => import('./dynamic-import-dep') diff --git a/packages/vite/src/node/__tests__/runnerImport.spec.ts b/packages/vite/src/node/__tests__/runnerImport.spec.ts index bb074e8a68b4e8..d6084b84bbbf30 100644 --- a/packages/vite/src/node/__tests__/runnerImport.spec.ts +++ b/packages/vite/src/node/__tests__/runnerImport.spec.ts @@ -61,4 +61,13 @@ describe('importing files using inlined environment', () => { ) }).rejects.toThrow('Unknown file extension ".ts"') }) + + test('dynamic import', async () => { + const { module } = await runnerImport(fixture('dynamic-import.ts')) + await expect(() => module.default()).rejects.toMatchInlineSnapshot( + `[Error: Vite module runner has been closed.]`, + ) + // const dep = await module.default(); + // expect(dep.default).toMatchInlineSnapshot(`"ok"`) + }) })