Skip to content

Commit 3083c4e

Browse files
clydindgp1130
authored andcommitted
fix(@angular-devkit/build-angular): avoid hash filenames for non-injected global styles/scripts
When using the esbuild-based browser application builder, non-injected global styles and scripts were unintentionally being output with filenames that contain a hash. This can prevent the filenames from being discoverable and therefore usable at runtime. The output filenames will now no longer contain a hash component which matches the behavior of the Webpack-based builder. (cherry picked from commit 2a2817d)
1 parent 95e1b40 commit 3083c4e

File tree

3 files changed

+152
-118
lines changed

3 files changed

+152
-118
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/global-scripts.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
1919
* @param options The builder's user-provider normalized options.
2020
* @returns An esbuild BuildOptions object.
2121
*/
22-
export function createGlobalScriptsBundleOptions(options: NormalizedBrowserOptions): BuildOptions {
22+
export function createGlobalScriptsBundleOptions(
23+
options: NormalizedBrowserOptions,
24+
initial: boolean,
25+
): BuildOptions | undefined {
2326
const {
2427
globalScripts,
2528
optimizationOptions,
@@ -31,16 +34,25 @@ export function createGlobalScriptsBundleOptions(options: NormalizedBrowserOptio
3134

3235
const namespace = 'angular:script/global';
3336
const entryPoints: Record<string, string> = {};
34-
for (const { name } of globalScripts) {
35-
entryPoints[name] = `${namespace}:${name}`;
37+
let found = false;
38+
for (const script of globalScripts) {
39+
if (script.initial === initial) {
40+
found = true;
41+
entryPoints[script.name] = `${namespace}:${script.name}`;
42+
}
43+
}
44+
45+
// Skip if there are no entry points for the style loading type
46+
if (found === false) {
47+
return;
3648
}
3749

3850
return {
3951
absWorkingDir: workspaceRoot,
4052
bundle: false,
4153
splitting: false,
4254
entryPoints,
43-
entryNames: outputNames.bundles,
55+
entryNames: initial ? outputNames.bundles : '[name]',
4456
assetNames: outputNames.media,
4557
mainFields: ['script', 'browser', 'main'],
4658
conditions: ['script'],
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 type { BuildOptions } from 'esbuild';
10+
import assert from 'node:assert';
11+
import { LoadResultCache } from './load-result-cache';
12+
import { NormalizedBrowserOptions } from './options';
13+
import { createStylesheetBundleOptions } from './stylesheets/bundle-options';
14+
15+
export function createGlobalStylesBundleOptions(
16+
options: NormalizedBrowserOptions,
17+
target: string[],
18+
browsers: string[],
19+
initial: boolean,
20+
cache?: LoadResultCache,
21+
): BuildOptions | undefined {
22+
const {
23+
workspaceRoot,
24+
optimizationOptions,
25+
sourcemapOptions,
26+
outputNames,
27+
globalStyles,
28+
preserveSymlinks,
29+
externalDependencies,
30+
stylePreprocessorOptions,
31+
tailwindConfiguration,
32+
} = options;
33+
34+
const namespace = 'angular:styles/global';
35+
const entryPoints: Record<string, string> = {};
36+
let found = false;
37+
for (const style of globalStyles) {
38+
if (style.initial === initial) {
39+
found = true;
40+
entryPoints[style.name] = `${namespace};${style.name}`;
41+
}
42+
}
43+
44+
// Skip if there are no entry points for the style loading type
45+
if (found === false) {
46+
return;
47+
}
48+
49+
const buildOptions = createStylesheetBundleOptions(
50+
{
51+
workspaceRoot,
52+
optimization: !!optimizationOptions.styles.minify,
53+
sourcemap: !!sourcemapOptions.styles,
54+
preserveSymlinks,
55+
target,
56+
externalDependencies,
57+
outputNames: initial
58+
? outputNames
59+
: {
60+
...outputNames,
61+
bundles: '[name]',
62+
},
63+
includePaths: stylePreprocessorOptions?.includePaths,
64+
browsers,
65+
tailwindConfiguration,
66+
},
67+
cache,
68+
);
69+
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';
70+
buildOptions.entryPoints = entryPoints;
71+
72+
buildOptions.plugins.unshift({
73+
name: 'angular-global-styles',
74+
setup(build) {
75+
build.onResolve({ filter: /^angular:styles\/global;/ }, (args) => {
76+
if (args.kind !== 'entry-point') {
77+
return null;
78+
}
79+
80+
return {
81+
path: args.path.split(';', 2)[1],
82+
namespace,
83+
};
84+
});
85+
build.onLoad({ filter: /./, namespace }, (args) => {
86+
const files = globalStyles.find(({ name }) => name === args.path)?.files;
87+
assert(files, `global style name should always be found [${args.path}]`);
88+
89+
return {
90+
contents: files.map((file) => `@import '${file.replace(/\\/g, '/')}';`).join('\n'),
91+
loader: 'css',
92+
resolveDir: workspaceRoot,
93+
};
94+
});
95+
},
96+
});
97+
98+
return buildOptions;
99+
}

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 37 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1010
import type { BuildOptions, Metafile, OutputFile } from 'esbuild';
11-
import assert from 'node:assert';
1211
import { constants as fsConstants } from 'node:fs';
1312
import fs from 'node:fs/promises';
1413
import path from 'node:path';
@@ -26,18 +25,16 @@ import { logBuilderStatusWarnings } from './builder-status-warnings';
2625
import { checkCommonJSModules } from './commonjs-checker';
2726
import { BundlerContext, logMessages } from './esbuild';
2827
import { createGlobalScriptsBundleOptions } from './global-scripts';
28+
import { createGlobalStylesBundleOptions } from './global-styles';
2929
import { extractLicenses } from './license-extractor';
30-
import { LoadResultCache } from './load-result-cache';
3130
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
3231
import { Schema as BrowserBuilderOptions } from './schema';
3332
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
34-
import { createStylesheetBundleOptions } from './stylesheets/bundle-options';
3533
import { shutdownSassWorkerPool } from './stylesheets/sass-plugin';
3634
import type { ChangedFiles } from './watcher';
3735

3836
interface RebuildState {
39-
codeRebuild?: BundlerContext;
40-
globalStylesRebuild?: BundlerContext;
37+
rebuildContexts: BundlerContext[];
4138
codeBundleCache?: SourceFileCache;
4239
fileChanges: ChangedFiles;
4340
}
@@ -50,8 +47,7 @@ class ExecutionResult {
5047
readonly assetFiles: { source: string; destination: string }[] = [];
5148

5249
constructor(
53-
private codeRebuild?: BundlerContext,
54-
private globalStylesRebuild?: BundlerContext,
50+
private rebuildContexts: BundlerContext[],
5551
private codeBundleCache?: SourceFileCache,
5652
) {}
5753

@@ -77,15 +73,14 @@ class ExecutionResult {
7773
this.codeBundleCache?.invalidate([...fileChanges.modified, ...fileChanges.removed]);
7874

7975
return {
80-
codeRebuild: this.codeRebuild,
81-
globalStylesRebuild: this.globalStylesRebuild,
76+
rebuildContexts: this.rebuildContexts,
8277
codeBundleCache: this.codeBundleCache,
8378
fileChanges,
8479
};
8580
}
8681

8782
async dispose(): Promise<void> {
88-
await Promise.allSettled([this.codeRebuild?.dispose(), this.globalStylesRebuild?.dispose()]);
83+
await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose()));
8984
}
9085
}
9186

@@ -109,57 +104,55 @@ async function execute(
109104
const target = transformSupportedBrowsersToTargets(browsers);
110105

111106
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
112-
const bundlerContexts = [];
113-
114-
// Application code
107+
let bundlerContexts = rebuildState?.rebuildContexts;
115108
const codeBundleCache = options.watch
116109
? rebuildState?.codeBundleCache ?? new SourceFileCache()
117110
: undefined;
118-
const codeBundleContext =
119-
rebuildState?.codeRebuild ??
120-
new BundlerContext(
121-
workspaceRoot,
122-
!!options.watch,
123-
createCodeBundleOptions(options, target, browsers, codeBundleCache),
124-
);
125-
bundlerContexts.push(codeBundleContext);
126-
// Global Stylesheets
127-
let globalStylesBundleContext;
128-
if (options.globalStyles.length > 0) {
129-
globalStylesBundleContext =
130-
rebuildState?.globalStylesRebuild ??
111+
if (bundlerContexts === undefined) {
112+
bundlerContexts = [];
113+
114+
// Application code
115+
bundlerContexts.push(
131116
new BundlerContext(
132117
workspaceRoot,
133118
!!options.watch,
134-
createGlobalStylesBundleOptions(
119+
createCodeBundleOptions(options, target, browsers, codeBundleCache),
120+
),
121+
);
122+
123+
// Global Stylesheets
124+
if (options.globalStyles.length > 0) {
125+
for (const initial of [true, false]) {
126+
const bundleOptions = createGlobalStylesBundleOptions(
135127
options,
136128
target,
137129
browsers,
130+
initial,
138131
codeBundleCache?.loadResultCache,
139-
),
140-
);
141-
bundlerContexts.push(globalStylesBundleContext);
142-
}
143-
// Global Scripts
144-
if (options.globalScripts.length > 0) {
145-
const globalScriptsBundleContext = new BundlerContext(
146-
workspaceRoot,
147-
!!options.watch,
148-
createGlobalScriptsBundleOptions(options),
149-
);
150-
bundlerContexts.push(globalScriptsBundleContext);
132+
);
133+
if (bundleOptions) {
134+
bundlerContexts.push(new BundlerContext(workspaceRoot, !!options.watch, bundleOptions));
135+
}
136+
}
137+
}
138+
139+
// Global Scripts
140+
if (options.globalScripts.length > 0) {
141+
for (const initial of [true, false]) {
142+
const bundleOptions = createGlobalScriptsBundleOptions(options, initial);
143+
if (bundleOptions) {
144+
bundlerContexts.push(new BundlerContext(workspaceRoot, !!options.watch, bundleOptions));
145+
}
146+
}
147+
}
151148
}
152149

153150
const bundlingResult = await BundlerContext.bundleAll(bundlerContexts);
154151

155152
// Log all warnings and errors generated during bundling
156153
await logMessages(context, bundlingResult);
157154

158-
const executionResult = new ExecutionResult(
159-
codeBundleContext,
160-
globalStylesBundleContext,
161-
codeBundleCache,
162-
);
155+
const executionResult = new ExecutionResult(bundlerContexts, codeBundleCache);
163156

164157
// Return if the bundling has errors
165158
if (bundlingResult.errors) {
@@ -501,76 +494,6 @@ function getFeatureSupport(target: string[]): BuildOptions['supported'] {
501494
return supported;
502495
}
503496

504-
function createGlobalStylesBundleOptions(
505-
options: NormalizedBrowserOptions,
506-
target: string[],
507-
browsers: string[],
508-
cache?: LoadResultCache,
509-
): BuildOptions {
510-
const {
511-
workspaceRoot,
512-
optimizationOptions,
513-
sourcemapOptions,
514-
outputNames,
515-
globalStyles,
516-
preserveSymlinks,
517-
externalDependencies,
518-
stylePreprocessorOptions,
519-
tailwindConfiguration,
520-
} = options;
521-
522-
const buildOptions = createStylesheetBundleOptions(
523-
{
524-
workspaceRoot,
525-
optimization: !!optimizationOptions.styles.minify,
526-
sourcemap: !!sourcemapOptions.styles,
527-
preserveSymlinks,
528-
target,
529-
externalDependencies,
530-
outputNames,
531-
includePaths: stylePreprocessorOptions?.includePaths,
532-
browsers,
533-
tailwindConfiguration,
534-
},
535-
cache,
536-
);
537-
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';
538-
539-
const namespace = 'angular:styles/global';
540-
buildOptions.entryPoints = {};
541-
for (const { name } of globalStyles) {
542-
buildOptions.entryPoints[name] = `${namespace};${name}`;
543-
}
544-
545-
buildOptions.plugins.unshift({
546-
name: 'angular-global-styles',
547-
setup(build) {
548-
build.onResolve({ filter: /^angular:styles\/global;/ }, (args) => {
549-
if (args.kind !== 'entry-point') {
550-
return null;
551-
}
552-
553-
return {
554-
path: args.path.split(';', 2)[1],
555-
namespace,
556-
};
557-
});
558-
build.onLoad({ filter: /./, namespace }, (args) => {
559-
const files = globalStyles.find(({ name }) => name === args.path)?.files;
560-
assert(files, `global style name should always be found [${args.path}]`);
561-
562-
return {
563-
contents: files.map((file) => `@import '${file.replace(/\\/g, '/')}';`).join('\n'),
564-
loader: 'css',
565-
resolveDir: workspaceRoot,
566-
};
567-
});
568-
},
569-
});
570-
571-
return buildOptions;
572-
}
573-
574497
async function withSpinner<T>(text: string, action: () => T | Promise<T>): Promise<T> {
575498
const spinner = new Spinner(text);
576499
spinner.start();

0 commit comments

Comments
 (0)