From b1cfbc340c774f383234e13d4285e52bd271c791 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:43:44 -0500 Subject: [PATCH] refactor(@angular-devkit/build-angular): add index HTML transformer to application programmatic usage Similar to the `dev-server` builder, the `application` builder's programmatic usage can now transform the index HTML that is generated during a build. As is the case for the existing builder JavaScript exports from the package, the new export (`buildApplication`) is also considered experimental and does not provide the support nor semver guarantees that the builders have when used via `angular.json` configuration. The third parameter of the `buildApplication` function can now be an extensions object with one of the fields being `indexHtmlTransformer`. This newly introduced field allows adjusting the index HTML content. Closes #26299 --- .../angular_devkit/build_angular/index.md | 11 ++-- .../src/builders/application/index.ts | 62 ++++++++++++++----- .../src/builders/application/options.ts | 11 +++- .../src/builders/browser-esbuild/index.ts | 2 +- .../src/tools/esbuild/index-html-generator.ts | 2 + 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/goldens/public-api/angular_devkit/build_angular/index.md b/goldens/public-api/angular_devkit/build_angular/index.md index 982a45d9c786..1092f0f0ceee 100644 --- a/goldens/public-api/angular_devkit/build_angular/index.md +++ b/goldens/public-api/angular_devkit/build_angular/index.md @@ -149,13 +149,10 @@ export interface Budget { } // @public -export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin_2[]): AsyncIterable; +export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin_2[]): AsyncIterable; + +// @public +export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable; // @public export enum CrossOrigin { 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 84e84533b608..ef51b87143ad 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/index.ts @@ -13,7 +13,11 @@ import { purgeStaleBuildCache } from '../../utils/purge-cache'; import { assertCompatibleAngularVersion } from '../../utils/version'; import { runEsBuildBuildAction } from './build-action'; import { executeBuild } from './execute-build'; -import { ApplicationBuilderInternalOptions, normalizeOptions } from './options'; +import { + ApplicationBuilderExtensions, + ApplicationBuilderInternalOptions, + normalizeOptions, +} from './options'; import { Schema as ApplicationBuilderOptions } from './schema'; export { ApplicationBuilderOptions }; @@ -25,13 +29,8 @@ export async function* buildApplicationInternal( infrastructureSettings?: { write?: boolean; }, - plugins?: Plugin[], -): AsyncIterable< - BuilderOutput & { - outputFiles?: BuildOutputFile[]; - assetFiles?: { source: string; destination: string }[]; - } -> { + extensions?: ApplicationBuilderExtensions, +): AsyncIterable { // Check Angular version. assertCompatibleAngularVersion(context.workspaceRoot); @@ -46,7 +45,7 @@ export async function* buildApplicationInternal( return; } - const normalizedOptions = await normalizeOptions(context, projectName, options, plugins); + const normalizedOptions = await normalizeOptions(context, projectName, options, extensions); // Setup an abort controller with a builder teardown if no signal is present let signal = context.signal; @@ -94,6 +93,11 @@ export async function* buildApplicationInternal( ); } +export interface ApplicationBuilderOutput extends BuilderOutput { + outputFiles?: BuildOutputFile[]; + assetFiles?: { source: string; destination: string }[]; +} + /** * Builds an application using the `application` builder with the provided * options. @@ -112,13 +116,41 @@ export function buildApplication( options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin[], -): AsyncIterable< - BuilderOutput & { - outputFiles?: BuildOutputFile[]; - assetFiles?: { source: string; destination: string }[]; +): AsyncIterable; + +/** + * Builds an application using the `application` builder with the provided + * options. + * + * Usage of the `extensions` 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 extensions An object contain extension points for the build. + * @returns The build output results of the build. + */ +export function buildApplication( + options: ApplicationBuilderOptions, + context: BuilderContext, + extensions?: ApplicationBuilderExtensions, +): AsyncIterable; + +export function buildApplication( + options: ApplicationBuilderOptions, + context: BuilderContext, + pluginsOrExtensions?: Plugin[] | ApplicationBuilderExtensions, +): AsyncIterable { + let extensions; + if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) { + extensions = { + codePlugins: pluginsOrExtensions, + }; } -> { - return buildApplicationInternal(options, context, undefined, plugins); + + return buildApplicationInternal(options, context, undefined, extensions); } 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 914a30299766..0f31d12f2b41 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/options.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/options.ts @@ -18,6 +18,7 @@ import { } from '../../tools/webpack/utils/helpers'; import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils'; import { I18nOptions, createI18nOptions } from '../../utils/i18n-options'; +import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { generateEntryPoints } from '../../utils/package-chunk-sort'; import { findTailwindConfigurationFile } from '../../utils/tailwind'; @@ -26,6 +27,11 @@ import { Schema as ApplicationBuilderOptions, I18NTranslation, OutputHashing } f export type NormalizedApplicationBuildOptions = Awaited>; +export interface ApplicationBuilderExtensions { + codePlugins?: Plugin[]; + indexHtmlTransformer?: IndexHtmlTransform; +} + /** Internal options hidden from builder schema but available when invoked programmatically. */ interface InternalOptions { /** @@ -82,7 +88,7 @@ export async function normalizeOptions( context: BuilderContext, projectName: string, options: ApplicationBuilderInternalOptions, - plugins?: Plugin[], + extensions?: ApplicationBuilderExtensions, ) { // If not explicitly set, default to the Node.js process argument const preserveSymlinks = @@ -217,6 +223,7 @@ export async function normalizeOptions( scripts: options.scripts ?? [], styles: options.styles ?? [], }), + transformer: extensions?.indexHtmlTransformer, }; } @@ -329,7 +336,7 @@ export async function normalizeOptions( namedChunks, budgets: budgets?.length ? budgets : undefined, publicPath: deployUrl ? deployUrl : undefined, - plugins: plugins?.length ? plugins : undefined, + plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined, loaderExtensions, }; } 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 f509fade65e0..6b5ce2ac6499 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 @@ -56,7 +56,7 @@ export async function* buildEsbuildBrowser( { write: false, }, - plugins, + plugins && { codePlugins: plugins }, )) { if (infrastructureSettings?.write !== false && result.outputFiles) { // Write output files diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/index-html-generator.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/index-html-generator.ts index d4553481d944..343ba2c6c7b9 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/index-html-generator.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/index-html-generator.ts @@ -82,6 +82,7 @@ export async function generateIndexHtml( }, crossOrigin: crossOrigin, deployUrl: buildOptions.publicPath, + postTransform: indexHtmlOptions.transformer, }); indexHtmlGenerator.readAsset = readAsset; @@ -110,6 +111,7 @@ export async function generateIndexHtml( const inlineCriticalCssProcessor = new InlineCriticalCssProcessor({ minify: false, // CSS has already been minified during the build. + deployUrl: buildOptions.publicPath, readAsset, });