Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: use module runner to import the config #18637

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions packages/vite/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const asyncFunctions = [
'loadConfigFromFile',
'preprocessCSS',
'createBuilder',
'inlineImport',
]
asyncFunctions.forEach((name) => {
module.exports[name] = (...args) =>
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/module-runner/esmEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import type { ModuleEvaluator, ModuleRunnerContext } from './types'

export class ESModulesEvaluator implements ModuleEvaluator {
startOffset = getAsyncFunctionDeclarationPaddingLineCount()
public readonly startOffset = getAsyncFunctionDeclarationPaddingLineCount()

async runInlinedModule(
context: ModuleRunnerContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface Test {
field: true
}

export const test: Test = {
field: true,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Plugin } from 'vite'

export default function testPlugin(): Plugin {
return {
name: 'test',
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import plugin from './plugin'

export default defineConfig({
root: './test',
plugins: [plugin()],
})
41 changes: 41 additions & 0 deletions packages/vite/src/node/__tests__/inlineImport.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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) =>
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<typeof import('./fixtures/inline-import/basic')>(
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([slash(fixture('plugin.ts'))])
})
})
10 changes: 10 additions & 0 deletions packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface GlobalCLIOptions {
l?: LogLevel
logLevel?: LogLevel
clearScreen?: boolean
configLoader?: string
d?: boolean | string
debug?: boolean | string
f?: string
Expand Down Expand Up @@ -87,6 +88,7 @@ function cleanGlobalCLIOptions<Options extends GlobalCLIOptions>(
delete ret.l
delete ret.logLevel
delete ret.clearScreen
delete ret.configLoader
delete ret.d
delete ret.debug
delete ret.f
Expand Down Expand Up @@ -151,6 +153,10 @@ cli
})
.option('-l, --logLevel <level>', `[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 <filter>', `[string] filter debug logs`)
.option('-m, --mode <mode>', `[string] set env mode`)
Expand Down Expand Up @@ -180,6 +186,7 @@ cli
base: options.base,
mode: options.mode,
configFile: options.config,
configLoader: options.configLoader as undefined,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
optimizeDeps: { force: options.force },
Expand Down Expand Up @@ -304,6 +311,7 @@ cli
base: options.base,
mode: options.mode,
configFile: options.config,
configLoader: options.configLoader as undefined,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
build: buildOptions,
Expand Down Expand Up @@ -340,6 +348,7 @@ cli
root,
base: options.base,
configFile: options.config,
configLoader: options.configLoader as undefined,
logLevel: options.logLevel,
mode: options.mode,
},
Expand Down Expand Up @@ -382,6 +391,7 @@ cli
root,
base: options.base,
configFile: options.config,
configLoader: options.configLoader as undefined,
logLevel: options.logLevel,
mode: options.mode,
build: {
Expand Down
74 changes: 53 additions & 21 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import fs from 'node:fs'
import fsp from 'node:fs/promises'
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, createRequire } 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 { build } from 'esbuild'
import type { AnymatchFn } from '../types/anymatch'
import { withTrailingSlash } from '../shared/utils'
import {
Expand Down Expand Up @@ -81,12 +81,12 @@ 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 { tryNodeResolve } from './plugins/resolve'
import type { LogLevel, Logger } from './logger'
import { createLogger } from './logger'
import type { DepOptimizationOptions } from './optimizer'
Expand All @@ -98,6 +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'

const debug = createDebugger('vite:config', { depth: 10 })
const promisifiedRealpath = promisify(fs.realpath)
Expand Down Expand Up @@ -530,6 +531,7 @@ export interface ResolvedWorkerOptions {

export interface InlineConfig extends UserConfig {
configFile?: string | false
configLoader?: 'bundle' | 'runner'
envFile?: false
}

Expand Down Expand Up @@ -996,6 +998,7 @@ export async function resolveConfig(
config.root,
config.logLevel,
config.customLogger,
config.configLoader,
)
if (loadResult) {
config = mergeConfig(loadResult.config, config)
Expand Down Expand Up @@ -1635,6 +1638,7 @@ export async function loadConfigFromFile(
configRoot: string = process.cwd(),
logLevel?: LogLevel,
customLogger?: Logger,
configLoader = 'bundle',
): Promise<{
path: string
config: UserConfig
Expand Down Expand Up @@ -1665,28 +1669,29 @@ 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,
if (configLoader !== 'bundle' && configLoader !== 'runner') {
throw new Error(
`Unsupported configLoader: ${configLoader}. Accepted values are 'bundle' and 'runner'.`,
)
debug?.(`bundled config file loaded in ${getTime()}`)
}

const config = await (typeof userConfig === 'function'
? userConfig(configEnv)
: userConfig)
try {
const resolver =
configLoader === 'bundle' ? bundleAndLoadConfigFile : importConfigFile
const { configExport, dependencies } = await resolver(resolvedPath)
debug?.(`config file loaded in ${getTime()}`)

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,
dependencies: bundled.dependencies,
dependencies,
}
} catch (e) {
createLogger(logLevel, { customLogger }).error(
Expand All @@ -1699,6 +1704,33 @@ export async function loadConfigFromFile(
}
}

async function importConfigFile(resolvedPath: string) {
const { module, dependencies } = await inlineImport<{
default: UserConfigExport
}>(resolvedPath, { configFile: false })
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,
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function createRunnableDevEnvironment(
name: string,
config: ResolvedConfig,
context: RunnableDevEnvironmentContext = {},
): DevEnvironment {
): RunnableDevEnvironment {
if (context.transport == null) {
context.transport = createServerHotChannel()
}
Expand Down
70 changes: 70 additions & 0 deletions packages/vite/src/node/ssr/inlineImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { InlineConfig } from '../config'
import { resolveConfig } from '../config'
import { createRunnableDevEnvironment } from '../server/environments/runnableEnvironment'
import { mergeConfig } from '../utils'

interface InlineImportResult<T> {
module: T
dependencies: string[]
}

/**
* Import any file using the default Vite environment.
* @experimental
*/
export async function inlineImport<T>(
moduleId: string,
inlineConfig?: InlineConfig,
): Promise<InlineImportResult<T>> {
const environment = createRunnableDevEnvironment(
'inline',
// TODO: provide a dummy config?
await resolveConfig(
mergeConfig(inlineConfig || {}, {
environments: {
inline: {
consumer: 'server',
dev: {
moduleRunnerTransform: true,
},
resolve: {
external: true,
},
},
},
}),
'serve',
),
{
runnerOptions: {
hmr: {
logger: false,
},
},
hot: false,
},
)
await environment.init()
try {
const module = await environment.runner.import(moduleId)
const modules = [
...environment.runner.evaluatedModules.urlToIdModuleMap.values(),
]
const dependencies = modules
.filter((m) => {
// ignore all externalized modules
if (!m.meta || 'externalize' in m.meta) {
return false
}
// ignore the current module
return m.exports !== module
})
.map((m) => m.file)
return {
module,
dependencies,
}
} finally {
await environment.close()
}
}
3 changes: 3 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default defineConfig({
'./playground/**/*.*',
'./playground-temp/**/*.*',
],
deps: {
moduleDirectories: ['node_modules', 'packages'],
},
testTimeout: 20000,
isolate: false,
},
Expand Down