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: decouple DevEnvironment and server #16460

Merged
2 changes: 0 additions & 2 deletions packages/vite/src/node/__tests__/external.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, test } from 'vitest'
import type { SSROptions } from '../ssr'
import { resolveConfig } from '../config'
import { createIsConfiguredAsExternal } from '../external'
import { Environment } from '../environment'
Expand All @@ -27,6 +26,5 @@ async function createIsExternal(external?: true) {
'serve',
)
const environment = new Environment('ssr', resolvedConfig)
console.log(environment.options)
return createIsConfiguredAsExternal(environment)
}
25 changes: 11 additions & 14 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import {
import type {
EnvironmentOptions,
InlineConfig,
PluginOption,
ResolvedConfig,
ResolvedEnvironmentOptions,
} from './config'
import type { PluginOption } from './plugin'
import { getDefaultResolvedEnvironmentOptions, resolveConfig } from './config'
import { buildReporterPlugin } from './plugins/reporter'
import { buildEsbuildPlugin } from './plugins/esbuild'
Expand All @@ -55,7 +55,7 @@ import type { Logger } from './logger'
import { dataURIPlugin } from './plugins/dataUri'
import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild'
import { ssrManifestPlugin } from './ssr/ssrManifestPlugin'
import { loadFallbackPlugin } from './plugins/loadFallback'
import { buildLoadFallbackPlugin } from './plugins/loadFallback'
import { findNearestPackageData } from './packages'
import type { PackageCache } from './packages'
import { resolveChokidarOptions } from './watch'
Expand Down Expand Up @@ -256,8 +256,8 @@ export interface BuildEnvironmentOptions {
* create the Build Environment instance
*/
createEnvironment?: (
builder: ViteBuilder,
name: string,
config: ResolvedConfig,
) => Promise<BuildEnvironment> | BuildEnvironment
}

Expand Down Expand Up @@ -498,7 +498,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
buildReporterPlugin(config),
]
: []),
loadFallbackPlugin(),
buildLoadFallbackPlugin(),
],
}
}
Expand Down Expand Up @@ -1375,26 +1375,24 @@ function areSeparateFolders(a: string, b: string) {

export class BuildEnvironment extends Environment {
mode = 'build' as const
builder: ViteBuilder

constructor(
builder: ViteBuilder,
name: string,
config: ResolvedConfig,
setup?: {
options?: EnvironmentOptions
},
) {
// TODO: move this to the base Environment class?
let options =
builder.config.environments[name] ??
getDefaultResolvedEnvironmentOptions(builder.config)
config.environments[name] ?? getDefaultResolvedEnvironmentOptions(config)
if (setup?.options) {
options = mergeConfig(
options,
setup?.options,
) as ResolvedEnvironmentOptions
}
super(name, builder.config, options)
this.builder = builder
super(name, config, options)
}
}

Expand Down Expand Up @@ -1492,18 +1490,17 @@ export async function createViteBuilder(
const environmentOptions = defaultConfig.environments[name]
const createEnvironment =
environmentOptions.build?.createEnvironment ??
((builder: ViteBuilder, name: string) =>
new BuildEnvironment(builder, name))
((name: string, config: ResolvedConfig) =>
new BuildEnvironment(name, config))

// We need to resolve the config again so we can properly merge options
// and get a new set of plugins for each build environment. The ecosystem
// expects plugins to be run for the same environment once they are created
// and to process a single bundle at a time (contrary to dev mode where
// plugins are built to handle multiple environments concurrently).
const environmentConfig = await resolveConfig(environmentOptions)
const environmentBuilder = { ...builder, config: environmentConfig }

const environment = await createEnvironment(environmentBuilder, name)
const environment = await createEnvironment(name, environmentConfig)
environments[name] = environment
}

Expand Down
5 changes: 3 additions & 2 deletions packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import type { ServerOptions } from './server'
import type { CLIShortcut } from './shortcuts'
import type { LogLevel } from './logger'
import { createLogger } from './logger'
import { resolveConfig } from './config'
import { Environment } from './environment'

const cli = cac('vite')

Expand Down Expand Up @@ -345,6 +343,8 @@ cli
)
.action(
async (root: string, options: { force?: boolean } & GlobalCLIOptions) => {
/* TODO: do we need this command?

filterDuplicateOptions(options)
const { optimizeDeps } = await import('./optimizer')
try {
Expand All @@ -367,6 +367,7 @@ cli
)
process.exit(1)
}
*/
},
)

Expand Down
131 changes: 83 additions & 48 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import {
ENV_ENTRY,
FS_PREFIX,
} from './constants'
import type { HookHandler, Plugin, PluginWithRequiredHook } from './plugin'
import type {
HookHandler,
Plugin,
PluginEnvironment,
PluginOption,
PluginWithRequiredHook,
} from './plugin'
import type {
BuildEnvironmentOptions,
BuildOptions,
Expand All @@ -35,12 +41,9 @@ import {
resolveBuildOptions,
resolveBuilderOptions,
} from './build'
import type {
ResolvedServerOptions,
ServerOptions,
ViteDevServer,
} from './server'
import type { ResolvedServerOptions, ServerOptions } from './server'
import { resolveServerOptions } from './server'
import { Environment } from './environment'
import type { DevEnvironment } from './server/environment'
import type { PreviewOptions, ResolvedPreviewOptions } from './preview'
import { resolvePreviewOptions } from './preview'
Expand Down Expand Up @@ -78,8 +81,8 @@ import type { LogLevel, Logger } from './logger'
import { createLogger } from './logger'
import type { DepOptimizationConfig, DepOptimizationOptions } from './optimizer'
import type { JsonOptions } from './plugins/json'
import type { PluginContainer } from './server/pluginContainer'
import { createPluginContainer } from './server/pluginContainer'
import type { BoundedPluginContainer } from './server/pluginContainer'
import { createBoundedPluginContainer } from './server/pluginContainer'
import type { PackageCache } from './packages'
import { findNearestPackageData } from './packages'
import { loadEnv, resolveEnvPrefix } from './env'
Expand Down Expand Up @@ -133,22 +136,14 @@ export function defineConfig(config: UserConfigExport): UserConfigExport {
return config
}

export type PluginOption =
| Plugin
| false
| null
| undefined
| PluginOption[]
| Promise<Plugin | false | null | undefined | PluginOption[]>

export interface DevEnvironmentOptions {
/**
* Files tßo be pre-transformed. Supports glob patterns.
*/
warmup?: string[]
/**ß
/**
* Pre-transform known direct imports
* @default true
* defaults to true for the client environment, false for the rest
*/
preTransformRequests?: boolean
/**
Expand Down Expand Up @@ -178,8 +173,8 @@ export interface DevEnvironmentOptions {
* create the Dev Environment instance
*/
createEnvironment?: (
server: ViteDevServer,
name: string,
config: ResolvedConfig,
) => Promise<DevEnvironment> | DevEnvironment

/**
Expand All @@ -190,6 +185,13 @@ export interface DevEnvironmentOptions {
*/
recoverable?: boolean

/**
* For environments associated with a module runner.
* By default it is true for the client environment and false for non-client environments.
* This option can also be used instead of the removed config.experimental.skipSsrTransform.
*/
moduleRunnerTransform?: boolean

/**
* Defaults to true for the client environment and false for others, following node permissive
* security model.
Expand All @@ -208,8 +210,8 @@ export type ResolvedDevEnvironmentOptions = Required<
// TODO: Should we set the default at config time? For now, it is defined on server init
createEnvironment:
| ((
server: ViteDevServer,
name: string,
config: ResolvedConfig,
) => Promise<DevEnvironment> | DevEnvironment)
| undefined
}
Expand Down Expand Up @@ -558,43 +560,54 @@ export function resolveDevEnvironmentOptions(
dev: DevEnvironmentOptions | undefined,
preserverSymlinks: boolean,
environmentName: string | undefined,
// Backward compatibility
skipSsrTransform?: boolean,
): ResolvedDevEnvironmentOptions {
return {
sourcemap: dev?.sourcemap ?? { js: true },
sourcemapIgnoreList:
dev?.sourcemapIgnoreList === false
? () => false
: dev?.sourcemapIgnoreList || isInNodeModules,
preTransformRequests: dev?.preTransformRequests ?? true,
preTransformRequests:
dev?.preTransformRequests ?? environmentName === 'client',
warmup: dev?.warmup ?? [],
optimizeDeps: resolveOptimizeDepsConfig(
dev?.optimizeDeps,
preserverSymlinks,
),
createEnvironment: dev?.createEnvironment,
recoverable: dev?.recoverable ?? environmentName === 'client',
moduleRunnerTransform:
dev?.moduleRunnerTransform ??
(skipSsrTransform !== undefined && environmentName === 'ssr'
? skipSsrTransform
: environmentName !== 'client'),
}
}

function resolveEnvironmentOptions(
config: EnvironmentOptions,
options: EnvironmentOptions,
resolvedRoot: string,
logger: Logger,
environmentName: string,
// Backward compatibility
skipSsrTransform?: boolean,
): ResolvedEnvironmentOptions {
const resolve = resolveEnvironmentResolveOptions(config.resolve, logger)
const resolve = resolveEnvironmentResolveOptions(options.resolve, logger)
return {
resolve,
nodeCompatible: config.nodeCompatible ?? environmentName !== 'client',
webCompatible: config.webCompatible ?? environmentName === 'client',
injectInvalidationTimestamp: config.injectInvalidationTimestamp ?? true,
nodeCompatible: options.nodeCompatible ?? environmentName !== 'client',
webCompatible: options.webCompatible ?? environmentName === 'client',
injectInvalidationTimestamp: options.injectInvalidationTimestamp ?? true,
dev: resolveDevEnvironmentOptions(
config.dev,
options.dev,
resolve.preserveSymlinks,
environmentName,
skipSsrTransform,
),
build: resolveBuildEnvironmentOptions(
config.build ?? {},
options.build ?? {},
logger,
resolvedRoot,
environmentName,
Expand Down Expand Up @@ -916,6 +929,7 @@ export async function resolveConfig(
resolvedRoot,
logger,
name,
config.experimental?.skipSsrTransform,
)
}

Expand Down Expand Up @@ -1186,29 +1200,52 @@ export async function resolveConfig(
// create an internal resolver to be used in special scenarios, e.g.
// optimizer & handling css @imports
createResolver(options) {
let aliasContainer: PluginContainer | undefined
let resolverContainer: PluginContainer | undefined
const alias: {
client?: BoundedPluginContainer
ssr?: BoundedPluginContainer
} = {}
const resolver: {
client?: BoundedPluginContainer
ssr?: BoundedPluginContainer
} = {}
const environments = this.environments ?? resolvedEnvironments
const createPluginContainer = async (
environmentName: string,
plugins: Plugin[],
) => {
// The used alias and resolve plugins only use configuration options from the
// environment so we can safely cast to a base Environment instance to a
// PluginEnvironment here
const environment = new Environment(environmentName, this)
const pluginContainer = await createBoundedPluginContainer(
environment as PluginEnvironment,
plugins,
)
await pluginContainer.buildStart({})
return pluginContainer
}
async function resolve(
id: string,
importer?: string,
aliasOnly?: boolean,
ssr?: boolean,
): Promise<PartialResolvedId | null> {
let container: PluginContainer
const environmentName = ssr ? 'ssr' : 'client'
let container: BoundedPluginContainer
if (aliasOnly) {
container =
aliasContainer ||
(aliasContainer = await createPluginContainer({
...resolved,
plugins: [aliasPlugin({ entries: resolved.resolve.alias })],
}))
let aliasContainer = alias[environmentName]
if (!aliasContainer) {
aliasContainer = alias[environmentName] =
await createPluginContainer(environmentName, [
aliasPlugin({ entries: resolved.resolve.alias }),
])
}
container = aliasContainer
} else {
container =
resolverContainer ||
(resolverContainer = await createPluginContainer({
...resolved,
plugins: [
let resolverContainer = resolver[environmentName]
if (!resolverContainer) {
resolverContainer = resolver[environmentName] =
await createPluginContainer(environmentName, [
aliasPlugin({ entries: resolved.resolve.alias }),
resolvePlugin(
{
Expand All @@ -1225,13 +1262,11 @@ export async function resolveConfig(
},
environments,
),
],
}))
])
}
container = resolverContainer
}
return await container.resolveId(id, importer, {
ssr,
scan: options?.scan,
})
return await container.resolveId(id, importer, { scan: options?.scan })
}
return async (id, importer, aliasOnly, ssr) =>
(await resolve(id, importer, aliasOnly, ssr))?.id
Expand Down
Loading