Skip to content

Commit 6d481c8

Browse files
committed
refactor(@angular-devkit/build-angular): move common steps in a shared function
With this change common steps that are executed when running an i18n build and not are moved into a shared file.
1 parent ac7caa4 commit 6d481c8

File tree

6 files changed

+210
-179
lines changed

6 files changed

+210
-179
lines changed

packages/angular_devkit/build_angular/src/builders/application/execute-build.ts

+23-102
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import { BuilderContext } from '@angular-devkit/architect';
10-
import assert from 'node:assert';
1110
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
1211
import {
1312
createBrowserCodeBundleOptions,
@@ -19,7 +18,6 @@ import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execu
1918
import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
2019
import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts';
2120
import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles';
22-
import { generateIndexHtml } from '../../tools/esbuild/index-html-generator';
2321
import { extractLicenses } from '../../tools/esbuild/license-extractor';
2422
import {
2523
calculateEstimatedTransferSizes,
@@ -30,10 +28,8 @@ import {
3028
} from '../../tools/esbuild/utils';
3129
import { checkBudgets } from '../../utils/bundle-calculator';
3230
import { copyAssets } from '../../utils/copy-assets';
33-
import { maxWorkers } from '../../utils/environment-options';
34-
import { prerenderPages } from '../../utils/server-rendering/prerender';
35-
import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker';
3631
import { getSupportedBrowsers } from '../../utils/supported-browsers';
32+
import { executePostBundleSteps } from './execute-post-bundle';
3733
import { inlineI18n, loadActiveTranslations } from './i18n';
3834
import { NormalizedApplicationBuildOptions } from './options';
3935

@@ -48,25 +44,23 @@ export async function executeBuild(
4844
const {
4945
projectRoot,
5046
workspaceRoot,
51-
serviceWorker,
47+
i18nOptions,
5248
optimizationOptions,
5349
serverEntryPoint,
5450
assets,
55-
indexHtmlOptions,
5651
cacheOptions,
5752
prerenderOptions,
5853
appShellOptions,
5954
ssrOptions,
60-
verbose,
6155
} = options;
6256

6357
const browsers = getSupportedBrowsers(projectRoot, context.logger);
6458
const target = transformSupportedBrowsersToTargets(browsers);
6559

6660
// Load active translations if inlining
6761
// TODO: Integrate into watch mode and only load changed translations
68-
if (options.i18nOptions.shouldInline) {
69-
await loadActiveTranslations(context, options.i18nOptions);
62+
if (i18nOptions.shouldInline) {
63+
await loadActiveTranslations(context, i18nOptions);
7064
}
7165

7266
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
@@ -152,68 +146,6 @@ export async function executeBuild(
152146
await logMessages(context, { warnings: messages });
153147
}
154148

155-
/**
156-
* Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
157-
*
158-
* NOTE: we don't perform critical CSS inlining as this will be done during server rendering.
159-
*/
160-
let indexContentOutputNoCssInlining: string | undefined;
161-
162-
// Generate index HTML file
163-
// If localization is enabled, index generation is handled in the inlining process.
164-
// NOTE: Localization with SSR is not currently supported.
165-
if (indexHtmlOptions && !options.i18nOptions.shouldInline) {
166-
const { content, contentWithoutCriticalCssInlined, errors, warnings } = await generateIndexHtml(
167-
initialFiles,
168-
executionResult.outputFiles,
169-
{
170-
...options,
171-
optimizationOptions,
172-
},
173-
// Set lang attribute to the defined source locale if present
174-
options.i18nOptions.hasDefinedSourceLocale ? options.i18nOptions.sourceLocale : undefined,
175-
);
176-
177-
indexContentOutputNoCssInlining = contentWithoutCriticalCssInlined;
178-
printWarningsAndErrorsToConsole(context, warnings, errors);
179-
180-
executionResult.addOutputFile(indexHtmlOptions.output, content, BuildOutputFileType.Browser);
181-
182-
if (ssrOptions) {
183-
executionResult.addOutputFile(
184-
'index.server.html',
185-
contentWithoutCriticalCssInlined,
186-
BuildOutputFileType.Server,
187-
);
188-
}
189-
}
190-
191-
// Pre-render (SSG) and App-shell
192-
// If localization is enabled, prerendering is handled in the inlining process.
193-
if ((prerenderOptions || appShellOptions) && !options.i18nOptions.shouldInline) {
194-
assert(
195-
indexContentOutputNoCssInlining,
196-
'The "index" option is required when using the "ssg" or "appShell" options.',
197-
);
198-
199-
const { output, warnings, errors } = await prerenderPages(
200-
workspaceRoot,
201-
appShellOptions,
202-
prerenderOptions,
203-
executionResult.outputFiles,
204-
indexContentOutputNoCssInlining,
205-
optimizationOptions.styles.inlineCritical,
206-
maxWorkers,
207-
verbose,
208-
);
209-
210-
printWarningsAndErrorsToConsole(context, warnings, errors);
211-
212-
for (const [path, content] of Object.entries(output)) {
213-
executionResult.addOutputFile(path, content, BuildOutputFileType.Browser);
214-
}
215-
}
216-
217149
// Copy assets
218150
if (assets) {
219151
// The webpack copy assets helper is used with no base paths defined. This prevents the helper
@@ -230,30 +162,6 @@ export async function executeBuild(
230162
);
231163
}
232164

233-
// Augment the application with service worker support
234-
// If localization is enabled, service worker is handled in the inlining process.
235-
if (serviceWorker && !options.i18nOptions.shouldInline) {
236-
try {
237-
const serviceWorkerResult = await augmentAppWithServiceWorkerEsbuild(
238-
workspaceRoot,
239-
serviceWorker,
240-
options.baseHref || '/',
241-
executionResult.outputFiles,
242-
executionResult.assetFiles,
243-
);
244-
executionResult.addOutputFile(
245-
'ngsw.json',
246-
serviceWorkerResult.manifest,
247-
BuildOutputFileType.Browser,
248-
);
249-
executionResult.addAssets(serviceWorkerResult.assetFiles);
250-
} catch (error) {
251-
context.logger.error(error instanceof Error ? error.message : `${error}`);
252-
253-
return executionResult;
254-
}
255-
}
256-
257165
// Analyze files for bundle budget failures if present
258166
let budgetFailures;
259167
if (options.budgets) {
@@ -274,17 +182,30 @@ export async function executeBuild(
274182
estimatedTransferSizes = await calculateEstimatedTransferSizes(executionResult.outputFiles);
275183
}
276184

277-
logBuildStats(context, metafile, initialFiles, budgetFailures, estimatedTransferSizes);
278-
279-
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
280-
context.logger.info(`Application bundle generation complete. [${buildTime.toFixed(3)} seconds]`);
281-
282185
// Perform i18n translation inlining if enabled
283-
if (options.i18nOptions.shouldInline) {
186+
if (i18nOptions.shouldInline) {
284187
const { errors, warnings } = await inlineI18n(options, executionResult, initialFiles);
285188
printWarningsAndErrorsToConsole(context, warnings, errors);
189+
} else {
190+
const { errors, warnings, additionalAssets, additionalOutputFiles } =
191+
await executePostBundleSteps(
192+
options,
193+
executionResult.outputFiles,
194+
executionResult.assetFiles,
195+
initialFiles,
196+
// Set lang attribute to the defined source locale if present
197+
i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined,
198+
);
199+
200+
executionResult.outputFiles.push(...additionalOutputFiles);
201+
executionResult.assetFiles.push(...additionalAssets);
202+
printWarningsAndErrorsToConsole(context, warnings, errors);
286203
}
287204

205+
logBuildStats(context, metafile, initialFiles, budgetFailures, estimatedTransferSizes);
206+
207+
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
208+
context.logger.info(`Application bundle generation complete. [${buildTime.toFixed(3)} seconds]`);
288209
// Write metafile if stats option is enabled
289210
if (options.stats) {
290211
executionResult.addOutputFile(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import assert from 'node:assert';
10+
import {
11+
BuildOutputFile,
12+
BuildOutputFileType,
13+
InitialFileRecord,
14+
} from '../../tools/esbuild/bundler-context';
15+
import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
16+
import { generateIndexHtml } from '../../tools/esbuild/index-html-generator';
17+
import { createOutputFileFromText } from '../../tools/esbuild/utils';
18+
import { maxWorkers } from '../../utils/environment-options';
19+
import { prerenderPages } from '../../utils/server-rendering/prerender';
20+
import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker';
21+
import { NormalizedApplicationBuildOptions } from './options';
22+
23+
/**
24+
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
25+
* @param options The normalized application builder options used to create the build.
26+
* @param outputFiles The output files of an executed build.
27+
* @param assetFiles The assets of an executed build.
28+
* @param initialFiles A map containing initial file information for the executed build.
29+
* @param locale A language locale to insert in the index.html.
30+
*/
31+
export async function executePostBundleSteps(
32+
options: NormalizedApplicationBuildOptions,
33+
outputFiles: BuildOutputFile[],
34+
assetFiles: BuildOutputAsset[],
35+
initialFiles: Map<string, InitialFileRecord>,
36+
locale: string | undefined,
37+
): Promise<{
38+
errors: string[];
39+
warnings: string[];
40+
additionalOutputFiles: BuildOutputFile[];
41+
additionalAssets: BuildOutputAsset[];
42+
}> {
43+
const additionalAssets: BuildOutputAsset[] = [];
44+
const additionalOutputFiles: BuildOutputFile[] = [];
45+
const allErrors: string[] = [];
46+
const allWarnings: string[] = [];
47+
48+
const {
49+
serviceWorker,
50+
indexHtmlOptions,
51+
optimizationOptions,
52+
ssrOptions,
53+
prerenderOptions,
54+
appShellOptions,
55+
workspaceRoot,
56+
verbose,
57+
} = options;
58+
59+
/**
60+
* Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
61+
*
62+
* NOTE: we don't perform critical CSS inlining as this will be done during server rendering.
63+
*/
64+
let indexContentOutputNoCssInlining: string | undefined;
65+
66+
// Generate index HTML file
67+
// If localization is enabled, index generation is handled in the inlining process.
68+
// NOTE: Localization with SSR is not currently supported.
69+
if (indexHtmlOptions) {
70+
const { content, contentWithoutCriticalCssInlined, errors, warnings } = await generateIndexHtml(
71+
initialFiles,
72+
outputFiles,
73+
{
74+
...options,
75+
optimizationOptions,
76+
},
77+
locale,
78+
);
79+
80+
indexContentOutputNoCssInlining = contentWithoutCriticalCssInlined;
81+
allErrors.push(...errors);
82+
allWarnings.push(...warnings);
83+
84+
additionalOutputFiles.push(
85+
createOutputFileFromText(indexHtmlOptions.output, content, BuildOutputFileType.Browser),
86+
);
87+
88+
if (ssrOptions) {
89+
additionalOutputFiles.push(
90+
createOutputFileFromText(
91+
'index.server.html',
92+
contentWithoutCriticalCssInlined,
93+
BuildOutputFileType.Server,
94+
),
95+
);
96+
}
97+
}
98+
99+
// Pre-render (SSG) and App-shell
100+
// If localization is enabled, prerendering is handled in the inlining process.
101+
if (prerenderOptions || appShellOptions) {
102+
assert(
103+
indexContentOutputNoCssInlining,
104+
'The "index" option is required when using the "ssg" or "appShell" options.',
105+
);
106+
107+
const { output, warnings, errors } = await prerenderPages(
108+
workspaceRoot,
109+
appShellOptions,
110+
prerenderOptions,
111+
outputFiles,
112+
indexContentOutputNoCssInlining,
113+
optimizationOptions.styles.inlineCritical,
114+
maxWorkers,
115+
verbose,
116+
);
117+
118+
allErrors.push(...errors);
119+
allWarnings.push(...warnings);
120+
121+
for (const [path, content] of Object.entries(output)) {
122+
additionalOutputFiles.push(
123+
createOutputFileFromText(path, content, BuildOutputFileType.Browser),
124+
);
125+
}
126+
}
127+
128+
// Augment the application with service worker support
129+
// If localization is enabled, service worker is handled in the inlining process.
130+
if (serviceWorker) {
131+
try {
132+
const serviceWorkerResult = await augmentAppWithServiceWorkerEsbuild(
133+
workspaceRoot,
134+
serviceWorker,
135+
options.baseHref || '/',
136+
outputFiles,
137+
assetFiles,
138+
);
139+
additionalOutputFiles.push(
140+
createOutputFileFromText(
141+
'ngsw.json',
142+
serviceWorkerResult.manifest,
143+
BuildOutputFileType.Browser,
144+
),
145+
);
146+
additionalAssets.push(...serviceWorkerResult.assetFiles);
147+
} catch (error) {
148+
allErrors.push(error instanceof Error ? error.message : `${error}`);
149+
}
150+
}
151+
152+
return {
153+
errors: allErrors,
154+
warnings: allWarnings,
155+
additionalAssets,
156+
additionalOutputFiles,
157+
};
158+
}

0 commit comments

Comments
 (0)