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

Merged
merged 29 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
547d1c7
feat: use module runner to import the config
sheremet-va Nov 11, 2024
2454b66
chore: todo
sheremet-va Nov 11, 2024
1cf3da7
chore: merge
sheremet-va Nov 11, 2024
df1efe3
chore: use RunnableDevEnvironment instead
sheremet-va Nov 11, 2024
e55e083
chore: make it run faster in tests
sheremet-va Nov 11, 2024
c9e908e
fix: support cjs globals in default modules evaluator
sheremet-va Nov 12, 2024
b4c41d3
feat: add `--bundleConfig` flag to opt-out of bundling
sheremet-va Nov 14, 2024
7c5dcf0
chore: remove cjs support
sheremet-va Nov 14, 2024
aad578d
chore: cleanup
sheremet-va Nov 14, 2024
d8f8771
test: add tests for inlineImport
sheremet-va Nov 14, 2024
0d16f85
chore: update public cjs export
sheremet-va Nov 14, 2024
f295303
refactor: rename bundleConfig to configLoader
sheremet-va Nov 15, 2024
d453876
test: fix windows pathing
sheremet-va Nov 15, 2024
65578af
fix: rename the option in the cli config
sheremet-va Nov 27, 2024
df7d928
docs: add a note to docs
sheremet-va Nov 27, 2024
001f7e9
docs: rename the heading
sheremet-va Nov 27, 2024
1adc486
fix: revert isModuleSyncConditionEnabled, fix types
sheremet-va Nov 28, 2024
5ecd529
test: add comment for moduleDirectories
sheremet-va Nov 28, 2024
275a593
test: add workspace test
sheremet-va Nov 28, 2024
b383651
refactor: mark configLoader as experimental
sheremet-va Dec 1, 2024
14571ac
perf: disable config, envFile and cacheDir
sheremet-va Dec 2, 2024
d847961
chore: remove unused directive
sheremet-va Dec 2, 2024
07867ae
chore: rename to runnerImport, rename test packages
sheremet-va Dec 4, 2024
646a069
lint: remove unesed ts-expect-error
sheremet-va Dec 4, 2024
9b2179a
fix: update other names
bluwy Dec 4, 2024
8669489
refactor: move configLoader validation
sheremet-va Dec 9, 2024
19c5019
test: dynamic import
hi-ogawa Dec 22, 2024
e06731d
Merge branch 'main' into pr/sheremet-va/18637
bluwy Jan 10, 2025
a960fd4
Merge branch 'main' into feat/use-runner-to-import
patak-dev Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ You can also explicitly specify a config file to use with the `--config` CLI opt
vite --config my-config.js
```

::: 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.
:::

## Config Intellisense

Since Vite ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
Expand Down
138 changes: 71 additions & 67 deletions docs/guide/cli.md

Large diffs are not rendered by default.

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',
'runnerImport',
]
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 @@
export default 'ok'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => import('./dynamic-import-dep')
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,5 @@
import parent from '@vitejs/parent'

export default {
__injected: parent.child,
}
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()],
})
1 change: 1 addition & 0 deletions packages/vite/src/node/__tests__/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"version": "0.0.0",
"dependencies": {
"@vitejs/parent": "link:./packages/parent",
"@vitejs/cjs-ssr-dep": "link:./fixtures/cjs-ssr-dep",
"@vitejs/test-dep-conditions": "file:./fixtures/test-dep-conditions"
}
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/__tests__/packages/child/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default true
5 changes: 5 additions & 0 deletions packages/vite/src/node/__tests__/packages/child/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@vitejs/child",
"type": "module",
"main": "./index.js"
}
6 changes: 6 additions & 0 deletions packages/vite/src/node/__tests__/packages/parent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @ts-expect-error not typed
import child from '@vitejs/child'

export default {
child,
}
8 changes: 8 additions & 0 deletions packages/vite/src/node/__tests__/packages/parent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@vitejs/parent",
"type": "module",
"main": "./index.ts",
"dependencies": {
"@vitejs/child": "link:../child"
}
}
73 changes: 73 additions & 0 deletions packages/vite/src/node/__tests__/runnerImport.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { resolve } from 'node:path'
import { describe, expect, test } from 'vitest'
import { loadConfigFromFile } from 'vite'
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/runner-import', name)

test('importing a basic file works', async () => {
const { module } = await runnerImport<
typeof import('./fixtures/runner-import/basic')
>(fixture('basic'))
expect(module.test).toEqual({
field: true,
})
})

test("cannot import cjs, 'runnerImport' doesn't support CJS syntax at all", async () => {
await expect(() =>
runnerImport<typeof import('./fixtures/runner-import/basic')>(
fixture('cjs.js'),
),
).rejects.toThrow('module is not defined')
})

test('can import vite config', async () => {
const { module, dependencies } = await runnerImport<
typeof import('./fixtures/runner-import/vite.config')
>(fixture('vite.config'))
expect(module.default).toEqual({
root: './test',
plugins: [
{
name: 'test',
},
],
})
expect(dependencies).toEqual([slash(fixture('plugin.ts'))])
})

test('can import vite config that imports a TS external module', async () => {
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)
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/runner-import')
await loadConfigFromFile(
{ mode: 'production', command: 'serve' },
resolve(root, './vite.config.outside-pkg-import.mts'),
root,
'silent',
)
}).rejects.toThrow('Unknown file extension ".ts"')
})

test('dynamic import', async () => {
const { module } = await runnerImport<any>(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"`)
})
})
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?: 'bundle' | 'runner'
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(
'--configLoader <loader>',
`[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 <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,
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,
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,
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,
logLevel: options.logLevel,
mode: options.mode,
build: {
Expand Down
77 changes: 55 additions & 22 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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 { createRequire } from 'node:module'
import crypto from 'node:crypto'
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 @@ -83,12 +83,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 @@ -100,6 +100,7 @@ import type { ResolvedSSROptions, SSROptions } from './ssr'
import { resolveSSROptions, ssrConfigDefaults } from './ssr'
import { PartialEnvironment } from './baseEnvironment'
import { createIdResolver } from './idResolver'
import { runnerImport } from './ssr/runnerImport'
import { getAdditionalAllowedHosts } from './server/middlewares/hostCheck'

const debug = createDebugger('vite:config', { depth: 10 })
Expand Down Expand Up @@ -543,6 +544,8 @@ export interface ResolvedWorkerOptions {

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

Expand Down Expand Up @@ -1024,6 +1027,7 @@ export async function resolveConfig(
config.root,
config.logLevel,
config.customLogger,
config.configLoader,
)
if (loadResult) {
config = mergeConfig(loadResult.config, config)
Expand Down Expand Up @@ -1680,11 +1684,18 @@ export async function loadConfigFromFile(
configRoot: string = process.cwd(),
logLevel?: LogLevel,
customLogger?: Logger,
configLoader: 'bundle' | 'runner' = 'bundle',
): Promise<{
path: string
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`

Expand All @@ -1710,28 +1721,23 @@ 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,
)
debug?.(`bundled config file loaded in ${getTime()}`)

const config = await (typeof userConfig === 'function'
? userConfig(configEnv)
: userConfig)
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 @@ -1744,6 +1750,33 @@ export async function loadConfigFromFile(
}
}

async function importConfigFile(resolvedPath: string) {
const { module, dependencies } = await runnerImport<{
default: UserConfigExport
}>(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,
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 { runnerImport } from './ssr/runnerImport'
export { BuildEnvironment } from './build'

export { fetchModule, type FetchModuleOptions } from './ssr/fetchModule'
Expand Down
Loading
Loading