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

refactor: use builder in build #18432

Merged
merged 5 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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,
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
): 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