Skip to content

Commit

Permalink
refactor: use builder in build (#18432)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red authored Oct 23, 2024
1 parent 19ce525 commit cc61d16
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 124 deletions.
2 changes: 1 addition & 1 deletion docs/guide/api-environment-frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export function createHandler(input) {

In the CLI, calling `vite build` and `vite build --ssr` will still build the client only and ssr only environments for backward compatibility.

When `builder.entireApp` is `true` (or when calling `vite build --app`), `vite build` will opt-in into building the entire app instead. This would later on become the default in a future major. A `ViteBuilder` instance will be created (build-time equivalent to a `ViteDevServer`) to build all configured environments for production. By default the build of environments is run in series respecting the order of the `environments` record. A framework or user can further configure how the environments are built using:
When `builder` is not `undefined` (or when calling `vite build --app`), `vite build` will opt-in into building the entire app instead. This would later on become the default in a future major. A `ViteBuilder` instance will be created (build-time equivalent to a `ViteDevServer`) to build all configured environments for production. By default the build of environments is run in series respecting the order of the `environments` record. A framework or user can further configure how the environments are built using:

```js
export default {
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ vite build [root]
| `-f, --filter <filter>` | Filter debug logs (`string`) |
| `-m, --mode <mode>` | Set env mode (`string`) |
| `-h, --help` | Display available CLI options |
| `--app` | Build all environments, same as `builder.entireApp` (`boolean`, experimental) |
| `--app` | Build all environments, same as `builder: {}` (`boolean`, experimental) |

## Others

Expand Down
161 changes: 73 additions & 88 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,35 +493,13 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
export async function build(
inlineConfig: InlineConfig = {},
): Promise<RollupOutput | RollupOutput[] | RollupWatcher> {
const patchConfig = (resolved: ResolvedConfig) => {
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
const environmentName = resolved.build.ssr ? 'ssr' : 'client'
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
const config = await resolveConfigToBuild(inlineConfig, patchConfig)
return buildWithResolvedConfig(config)
const builder = await createBuilder(inlineConfig, true)
const environment = Object.values(builder.environments)[0]
if (!environment) throw new Error('No environment found')
return builder.build(environment)
}

/**
* @internal used to implement `vite build` for backward compatibility
*/
export async function buildWithResolvedConfig(
config: ResolvedConfig,
): Promise<RollupOutput | RollupOutput[] | RollupWatcher> {
const environmentName = config.build.ssr ? 'ssr' : 'client'
const environment = await config.environments[
environmentName
].build.createEnvironment(environmentName, config)
await environment.init()
return buildEnvironment(environment)
}

export function resolveConfigToBuild(
function resolveConfigToBuild(
inlineConfig: InlineConfig = {},
patchConfig?: (config: ResolvedConfig) => void,
patchPlugins?: (resolvedPlugins: Plugin[]) => void,
Expand All @@ -540,7 +518,7 @@ export function resolveConfigToBuild(
/**
* Build an App environment, or a App library (if libraryOptions is provided)
**/
export async function buildEnvironment(
async function buildEnvironment(
environment: BuildEnvironment,
): Promise<RollupOutput | RollupOutput[] | RollupWatcher> {
const { root, packageCache } = environment.config
Expand Down Expand Up @@ -1486,7 +1464,6 @@ export interface ViteBuilder {
export interface BuilderOptions {
sharedConfigBuild?: boolean
sharedPlugins?: boolean
entireApp?: boolean
buildApp?: (builder: ViteBuilder) => Promise<void>
}

Expand All @@ -1497,12 +1474,12 @@ async function defaultBuildApp(builder: ViteBuilder): Promise<void> {
}

export function resolveBuilderOptions(
options: BuilderOptions = {},
): ResolvedBuilderOptions {
options: BuilderOptions | undefined,
): ResolvedBuilderOptions | undefined {
if (!options) return
return {
sharedConfigBuild: options.sharedConfigBuild ?? false,
sharedPlugins: options.sharedPlugins ?? false,
entireApp: options.entireApp ?? false,
buildApp: options.buildApp ?? defaultBuildApp,
}
}
Expand All @@ -1515,83 +1492,91 @@ export type ResolvedBuilderOptions = Required<BuilderOptions>
*/
export async function createBuilder(
inlineConfig: InlineConfig = {},
useLegacyBuilder: null | boolean = false,
): Promise<ViteBuilder> {
const config = await resolveConfigToBuild(inlineConfig)
return createBuilderWithResolvedConfig(inlineConfig, config)
}
const patchConfig = (resolved: ResolvedConfig) => {
if (!(useLegacyBuilder ?? !resolved.builder)) return

// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
const environmentName = resolved.build.ssr ? 'ssr' : 'client'
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
const config = await resolveConfigToBuild(inlineConfig, patchConfig)
useLegacyBuilder ??= !config.builder
const configBuilder = config.builder ?? resolveBuilderOptions({})!

/**
* Used to implement the `vite build` command without resolving the config twice
* @internal
*/
export async function createBuilderWithResolvedConfig(
inlineConfig: InlineConfig,
config: ResolvedConfig,
): Promise<ViteBuilder> {
const environments: Record<string, BuildEnvironment> = {}

const builder: ViteBuilder = {
environments,
config,
async buildApp() {
return config.builder.buildApp(builder)
return configBuilder.buildApp(builder)
},
async build(environment: BuildEnvironment) {
return buildEnvironment(environment)
},
}

for (const environmentName of Object.keys(config.environments)) {
// 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).
let environmentConfig = config
if (!config.builder.sharedConfigBuild) {
const patchConfig = (resolved: ResolvedConfig) => {
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
async function setupEnvironment(name: string, config: ResolvedConfig) {
const environment = await config.build.createEnvironment(name, config)
await environment.init()
environments[name] = environment
}

if (useLegacyBuilder) {
await setupEnvironment(config.build.ssr ? 'ssr' : 'client', config)
} else {
for (const environmentName of Object.keys(config.environments)) {
// 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).
let environmentConfig = config
if (!configBuilder.sharedConfigBuild) {
const patchConfig = (resolved: ResolvedConfig) => {
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
}
const patchPlugins = (resolvedPlugins: Plugin[]) => {
// Force opt-in shared plugins
let j = 0
for (let i = 0; i < resolvedPlugins.length; i++) {
const environmentPlugin = resolvedPlugins[i]
if (
config.builder.sharedPlugins ||
environmentPlugin.sharedDuringBuild
) {
for (let k = j; k < config.plugins.length; k++) {
if (environmentPlugin.name === config.plugins[k].name) {
resolvedPlugins[i] = config.plugins[k]
j = k + 1
break
const patchPlugins = (resolvedPlugins: Plugin[]) => {
// Force opt-in shared plugins
let j = 0
for (let i = 0; i < resolvedPlugins.length; i++) {
const environmentPlugin = resolvedPlugins[i]
if (
configBuilder.sharedPlugins ||
environmentPlugin.sharedDuringBuild
) {
for (let k = j; k < config.plugins.length; k++) {
if (environmentPlugin.name === config.plugins[k].name) {
resolvedPlugins[i] = config.plugins[k]
j = k + 1
break
}
}
}
}
}
environmentConfig = await resolveConfigToBuild(
inlineConfig,
patchConfig,
patchPlugins,
)
}
environmentConfig = await resolveConfigToBuild(
inlineConfig,
patchConfig,
patchPlugins,
)
}

const environment = await environmentConfig.build.createEnvironment(
environmentName,
environmentConfig,
)

await environment.init()

environments[environmentName] = environment
await setupEnvironment(environmentName, environmentConfig)
}
}

return builder
Expand Down
40 changes: 7 additions & 33 deletions packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { performance } from 'node:perf_hooks'
import { cac } from 'cac'
import colors from 'picocolors'
import { VERSION } from './constants'
import type { BuildEnvironmentOptions, ResolvedBuildOptions } from './build'
import type { BuildEnvironmentOptions } from './build'
import type { ServerOptions } from './server'
import type { CLIShortcut } from './shortcuts'
import type { LogLevel } from './logger'
import { createLogger } from './logger'
import { resolveConfig } from './config'
import type { InlineConfig, ResolvedConfig } from './config'
import type { InlineConfig } from './config'

const cli = cac('vite')

Expand Down Expand Up @@ -285,14 +285,14 @@ cli
`[boolean] force empty outDir when it's outside of root`,
)
.option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
.option('--app', `[boolean] same as builder.entireApp`)
.option('--app', `[boolean] same as \`builder: {}\``)
.action(
async (
root: string,
options: BuildEnvironmentOptions & BuilderCLIOptions & GlobalCLIOptions,
) => {
filterDuplicateOptions(options)
const build = await import('./build')
const { createBuilder } = await import('./build')

const buildOptions: BuildEnvironmentOptions = cleanGlobalCLIOptions(
cleanBuilderCLIOptions(options),
Expand All @@ -307,36 +307,10 @@ cli
logLevel: options.logLevel,
clearScreen: options.clearScreen,
build: buildOptions,
...(options.app ? { builder: { entireApp: true } } : {}),
}
const patchConfig = (resolved: ResolvedConfig) => {
if (resolved.builder.entireApp) {
return
}
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
const environmentName = resolved.build.ssr ? 'ssr' : 'client'
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
const config = await build.resolveConfigToBuild(
inlineConfig,
patchConfig,
)

if (config.builder.entireApp) {
const builder = await build.createBuilderWithResolvedConfig(
inlineConfig,
config,
)
await builder.buildApp()
} else {
// Single environment (client or ssr) build or library mode build
await build.buildWithResolvedConfig(config)
...(options.app ? { builder: {} } : {}),
}
const builder = await createBuilder(inlineConfig, null)
await builder.buildApp()
} catch (e) {
createLogger(options.logLevel).error(
colors.red(`error during build:\n${e.stack}`),
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ export type ResolvedConfig = Readonly<
server: ResolvedServerOptions
dev: ResolvedDevEnvironmentOptions
/** @experimental */
builder: ResolvedBuilderOptions
builder: ResolvedBuilderOptions | undefined
build: ResolvedBuildOptions
preview: ResolvedPreviewOptions
ssr: ResolvedSSROptions
Expand Down

0 comments on commit cc61d16

Please sign in to comment.