diff --git a/goldens/public-api/angular_devkit/build_angular/index.md b/goldens/public-api/angular_devkit/build_angular/index.md index 00108df05faf..1b25ec2e56c7 100644 --- a/goldens/public-api/angular_devkit/build_angular/index.md +++ b/goldens/public-api/angular_devkit/build_angular/index.md @@ -10,9 +10,54 @@ import type { ConfigOptions } from 'karma'; import { Configuration } from 'webpack'; import { DevServerBuildOutput } from '@angular-devkit/build-webpack'; import { Observable } from 'rxjs'; +import { OutputFile } from 'esbuild'; +import type { Plugin as Plugin_2 } from 'esbuild'; import webpack from 'webpack'; import { WebpackLoggingCallback } from '@angular-devkit/build-webpack'; +// @public +export interface ApplicationBuilderOptions { + allowedCommonJsDependencies?: string[]; + aot?: boolean; + appShell?: boolean; + assets?: AssetPattern_2[]; + baseHref?: string; + browser: string; + budgets?: Budget_2[]; + crossOrigin?: CrossOrigin_2; + deleteOutputPath?: boolean; + externalDependencies?: string[]; + extractLicenses?: boolean; + fileReplacements?: FileReplacement_2[]; + i18nDuplicateTranslation?: I18NTranslation_2; + i18nMissingTranslation?: I18NTranslation_2; + index: IndexUnion_2; + inlineStyleLanguage?: InlineStyleLanguage_2; + localize?: Localize_2; + namedChunks?: boolean; + optimization?: OptimizationUnion_2; + outputHashing?: OutputHashing_2; + outputPath: string; + poll?: number; + polyfills?: string[]; + prerender?: PrerenderUnion; + preserveSymlinks?: boolean; + progress?: boolean; + scripts?: ScriptElement_2[]; + server?: string; + serviceWorker?: ServiceWorker_2; + sourceMap?: SourceMapUnion_2; + ssr?: ServiceWorker_2; + statsJson?: boolean; + stylePreprocessorOptions?: StylePreprocessorOptions_2; + styles?: StyleElement_2[]; + subresourceIntegrity?: boolean; + tsConfig: string; + verbose?: boolean; + watch?: boolean; + webWorkerTsConfig?: string; +} + // @public (undocumented) export type AssetPattern = AssetPatternObject | string; @@ -94,6 +139,15 @@ export interface Budget { warning?: string; } +// @public +export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin_2[]): AsyncIterable; + // @public export enum CrossOrigin { // (undocumented) @@ -149,7 +203,7 @@ export function executeDevServerBuilder(options: DevServerBuilderOptions, contex webpackConfiguration?: ExecutionTransformer; logging?: WebpackLoggingCallback; indexHtml?: IndexHtmlTransform; -}): Observable; +}, plugins?: Plugin_2[]): Observable; // @public export function executeExtractI18nBuilder(options: ExtractI18nBuilderOptions, context: BuilderContext, transforms?: { @@ -201,14 +255,14 @@ export interface FileReplacement { // @public export interface KarmaBuilderOptions { - assets?: AssetPattern_2[]; + assets?: AssetPattern_3[]; browsers?: string; codeCoverage?: boolean; codeCoverageExclude?: string[]; exclude?: string[]; - fileReplacements?: FileReplacement_2[]; + fileReplacements?: FileReplacement_3[]; include?: string[]; - inlineStyleLanguage?: InlineStyleLanguage_2; + inlineStyleLanguage?: InlineStyleLanguage_3; karmaConfig?: string; main?: string; poll?: number; @@ -216,10 +270,10 @@ export interface KarmaBuilderOptions { preserveSymlinks?: boolean; progress?: boolean; reporters?: string[]; - scripts?: ScriptElement_2[]; - sourceMap?: SourceMapUnion_2; - stylePreprocessorOptions?: StylePreprocessorOptions_2; - styles?: StyleElement_2[]; + scripts?: ScriptElement_3[]; + sourceMap?: SourceMapUnion_3; + stylePreprocessorOptions?: StylePreprocessorOptions_3; + styles?: StyleElement_3[]; tsConfig: string; watch?: boolean; webWorkerTsConfig?: string; @@ -276,30 +330,30 @@ export interface ProtractorBuilderOptions { // @public (undocumented) export interface ServerBuilderOptions { - assets?: AssetPattern_3[]; + assets?: AssetPattern_4[]; buildOptimizer?: boolean; deleteOutputPath?: boolean; // @deprecated deployUrl?: string; externalDependencies?: string[]; extractLicenses?: boolean; - fileReplacements?: FileReplacement_3[]; - i18nDuplicateTranslation?: I18NTranslation_2; - i18nMissingTranslation?: I18NTranslation_2; - inlineStyleLanguage?: InlineStyleLanguage_3; - localize?: Localize_2; + fileReplacements?: FileReplacement_4[]; + i18nDuplicateTranslation?: I18NTranslation_3; + i18nMissingTranslation?: I18NTranslation_3; + inlineStyleLanguage?: InlineStyleLanguage_4; + localize?: Localize_3; main: string; namedChunks?: boolean; - optimization?: OptimizationUnion_2; - outputHashing?: OutputHashing_2; + optimization?: OptimizationUnion_3; + outputHashing?: OutputHashing_3; outputPath: string; poll?: number; preserveSymlinks?: boolean; progress?: boolean; resourcesOutputPath?: string; - sourceMap?: SourceMapUnion_3; + sourceMap?: SourceMapUnion_4; statsJson?: boolean; - stylePreprocessorOptions?: StylePreprocessorOptions_3; + stylePreprocessorOptions?: StylePreprocessorOptions_4; tsConfig: string; vendorChunk?: boolean; verbose?: boolean; diff --git a/packages/angular_devkit/build_angular/src/builders/application/index.ts b/packages/angular_devkit/build_angular/src/builders/application/index.ts index 99a8664497bd..127b6146af0f 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/index.ts @@ -7,6 +7,7 @@ */ import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import type { Plugin } from 'esbuild'; import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context'; import { purgeStaleBuildCache } from '../../utils/purge-cache'; import { assertCompatibleAngularVersion } from '../../utils/version'; @@ -15,6 +16,8 @@ import { executeBuild } from './execute-build'; import { ApplicationBuilderInternalOptions, normalizeOptions } from './options'; import { Schema as ApplicationBuilderOptions } from './schema'; +export { ApplicationBuilderOptions }; + export async function* buildApplicationInternal( options: ApplicationBuilderInternalOptions, // TODO: Integrate abort signal support into builder system @@ -22,6 +25,7 @@ export async function* buildApplicationInternal( infrastructureSettings?: { write?: boolean; }, + plugins?: Plugin[], ): AsyncIterable< BuilderOutput & { outputFiles?: BuildOutputFile[]; @@ -42,7 +46,7 @@ export async function* buildApplicationInternal( return; } - const normalizedOptions = await normalizeOptions(context, projectName, options); + const normalizedOptions = await normalizeOptions(context, projectName, options, plugins); yield* runEsBuildBuildAction( (rebuildState) => executeBuild(normalizedOptions, context, rebuildState), @@ -69,16 +73,31 @@ export async function* buildApplicationInternal( ); } +/** + * Builds an application using the `application` builder with the provided + * options. + * + * Usage of the `plugins` parameter is NOT supported and may cause unexpected + * build output or build failures. + * + * @experimental Direct usage of this function is considered experimental. + * + * @param options The options defined by the builder's schema to use. + * @param context An Architect builder context instance. + * @param plugins An array of plugins to apply to the main code bundling. + * @returns The build output results of the build. + */ export function buildApplication( options: ApplicationBuilderOptions, context: BuilderContext, + plugins?: Plugin[], ): AsyncIterable< BuilderOutput & { outputFiles?: BuildOutputFile[]; assetFiles?: { source: string; destination: string }[]; } > { - return buildApplicationInternal(options, context); + return buildApplicationInternal(options, context, undefined, plugins); } export default createBuilder(buildApplication); diff --git a/packages/angular_devkit/build_angular/src/builders/application/options.ts b/packages/angular_devkit/build_angular/src/builders/application/options.ts index e292671ca519..0f3b2d63aab5 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/options.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/options.ts @@ -7,6 +7,7 @@ */ import { BuilderContext } from '@angular-devkit/architect'; +import type { Plugin } from 'esbuild'; import { createRequire } from 'node:module'; import path from 'node:path'; import { @@ -71,6 +72,7 @@ export type ApplicationBuilderInternalOptions = Omit< * @param context The context for current builder execution. * @param projectName The name of the project for the current execution. * @param options An object containing the options to use for the build. + * @param plugins An optional array of programmatically supplied build plugins. * @returns An object containing normalized options required to perform the build. */ // eslint-disable-next-line max-lines-per-function @@ -78,6 +80,7 @@ export async function normalizeOptions( context: BuilderContext, projectName: string, options: ApplicationBuilderInternalOptions, + plugins?: Plugin[], ) { const workspaceRoot = context.workspaceRoot; const projectMetadata = await context.getProjectMetadata(projectName); @@ -295,6 +298,7 @@ export async function normalizeOptions( namedChunks, budgets: budgets?.length ? budgets : undefined, publicPath: deployUrl ? deployUrl : undefined, + plugins: plugins?.length ? plugins : undefined, }; } diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts index 57c6fdfa2898..e1e3f243be53 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -7,6 +7,7 @@ */ import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import type { Plugin } from 'esbuild'; import { constants as fsConstants } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; @@ -29,6 +30,7 @@ export async function* buildEsbuildBrowser( infrastructureSettings?: { write?: boolean; }, + plugins?: Plugin[], ): AsyncIterable< BuilderOutput & { outputFiles?: BuildOutputFile[]; @@ -40,9 +42,14 @@ export async function* buildEsbuildBrowser( const normalizedOptions = normalizeOptions(userOptions); const fullOutputPath = path.join(context.workspaceRoot, normalizedOptions.outputPath); - for await (const result of buildApplicationInternal(normalizedOptions, context, { - write: false, - })) { + for await (const result of buildApplicationInternal( + normalizedOptions, + context, + { + write: false, + }, + plugins, + )) { if (infrastructureSettings?.write !== false && result.outputFiles) { // Write output files await writeResultFiles(result.outputFiles, result.assetFiles, fullOutputPath); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts index 5af0eca63017..a53ff413bed7 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts @@ -7,6 +7,7 @@ */ import type { BuilderContext } from '@angular-devkit/architect'; +import type { Plugin } from 'esbuild'; import { EMPTY, Observable, defer, switchMap } from 'rxjs'; import type { ExecutionTransformer } from '../../transforms'; import { checkPort } from '../../utils/check-port'; @@ -33,6 +34,7 @@ export function execute( logging?: import('@angular-devkit/build-webpack').WebpackLoggingCallback; indexHtml?: IndexHtmlTransform; } = {}, + plugins?: Plugin[], ): Observable { // Determine project name from builder context target const projectName = context.target?.project; @@ -50,11 +52,23 @@ export function execute( builderName === '@angular-devkit/build-angular:browser-esbuild' || normalizedOptions.forceEsbuild ) { + if (Object.keys(transforms).length > 0) { + throw new Error( + 'The `application` and `browser-esbuild` builders do not support Webpack transforms.', + ); + } + return defer(() => import('./vite-server')).pipe( - switchMap(({ serveWithVite }) => serveWithVite(normalizedOptions, builderName, context)), + switchMap(({ serveWithVite }) => + serveWithVite(normalizedOptions, builderName, context, plugins), + ), ); } + if (plugins?.length) { + throw new Error('Only the `application` and `browser-esbuild` builders support plugins.'); + } + // Use Webpack for all other browser targets return defer(() => import('./webpack-server')).pipe( switchMap(({ serveWebpackBrowser }) => diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts index c1df69d936fd..cba4b48c3be0 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts @@ -8,6 +8,7 @@ import type { BuilderContext } from '@angular-devkit/architect'; import type { json, logging } from '@angular-devkit/core'; +import type { Plugin } from 'esbuild'; import { lookup as lookupMimeType } from 'mrmime'; import assert from 'node:assert'; import { BinaryLike, createHash } from 'node:crypto'; @@ -48,6 +49,7 @@ export async function* serveWithVite( serverOptions: NormalizedDevServerOptions, builderName: string, context: BuilderContext, + plugins?: Plugin[], ): AsyncIterableIterator { // Get the browser configuration from the target name. const rawBrowserOptions = (await context.getTargetOptions( @@ -115,9 +117,14 @@ export async function* serveWithVite( const generatedFiles = new Map(); const assetFiles = new Map(); // TODO: Switch this to an architect schedule call when infrastructure settings are supported - for await (const result of buildEsbuildBrowser(browserOptions, context, { - write: false, - })) { + for await (const result of buildEsbuildBrowser( + browserOptions, + context, + { + write: false, + }, + plugins, + )) { assert(result.outputFiles, 'Builder did not provide result files.'); // Analyze result files for changes diff --git a/packages/angular_devkit/build_angular/src/index.ts b/packages/angular_devkit/build_angular/src/index.ts index 37aa4d24d4d2..cbd753ed765e 100644 --- a/packages/angular_devkit/build_angular/src/index.ts +++ b/packages/angular_devkit/build_angular/src/index.ts @@ -29,6 +29,8 @@ export { BrowserBuilderOutput, } from './builders/browser'; +export { buildApplication, ApplicationBuilderOptions } from './builders/application'; + export { executeDevServerBuilder, DevServerBuilderOptions, diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts index 6265bf3a7fd4..7ad66cf5800c 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts @@ -165,6 +165,10 @@ export function createBrowserCodeBundleOptions( ); } + if (options.plugins) { + buildOptions.plugins?.push(...options.plugins); + } + return buildOptions; } @@ -343,6 +347,10 @@ export function createServerCodeBundleOptions( ); } + if (options.plugins) { + buildOptions.plugins.push(...options.plugins); + } + return buildOptions; }