Skip to content

Commit e0b274b

Browse files
committed
feat(@angular-devkit/build-angular): add option to retain CSS special comments in global styles
Prior to this change special CSS comments `/*! comment */` were being removed during minification when using the application builder. This caused tools that ran post build that rely on such comments such as purgeCSS and critters not to function properly. We now provide a `removeSpecialComments` option to enable retention of these comments in global CSS files. Usage example: ```json { "projects": { "my-app": { "architect": { "build": { "builder": "@angular-devkit/build-angular:application", "configurations": { "production": { "optimization": { "styles": { "removeSpecialComments": false } } } } } } } } } ``` Closes: #26432
1 parent adba8be commit e0b274b

File tree

5 files changed

+98
-4
lines changed

5 files changed

+98
-4
lines changed

packages/angular_devkit/build_angular/src/builders/application/schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@
162162
"type": "boolean",
163163
"description": "Extract and inline critical CSS definitions to improve first paint time.",
164164
"default": true
165+
},
166+
"removeSpecialComments": {
167+
"type": "boolean",
168+
"description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
169+
"default": true
165170
}
166171
},
167172
"additionalProperties": false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 { buildApplication } from '../../index';
10+
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
11+
12+
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
13+
describe('Behavior: "removeSpecialComments"', () => {
14+
beforeEach(async () => {
15+
await harness.writeFile(
16+
'src/styles.css',
17+
`
18+
/* normal-comment */
19+
/*! important-comment */
20+
div { flex: 1 }
21+
`,
22+
);
23+
});
24+
25+
it(`should retain special comments when 'removeSpecialComments' is set to 'false'`, async () => {
26+
harness.useTarget('build', {
27+
...BASE_OPTIONS,
28+
extractLicenses: true,
29+
styles: ['src/styles.css'],
30+
optimization: {
31+
styles: {
32+
removeSpecialComments: false,
33+
},
34+
},
35+
});
36+
37+
const { result } = await harness.executeOnce();
38+
expect(result?.success).toBeTrue();
39+
40+
harness
41+
.expectFile('dist/browser/styles.css')
42+
.content.toMatch(/\/\*! important-comment \*\/[\s\S]*div{flex:1}/);
43+
});
44+
45+
it(`should not retain special comments when 'removeSpecialComments' is set to 'true'`, async () => {
46+
harness.useTarget('build', {
47+
...BASE_OPTIONS,
48+
extractLicenses: true,
49+
styles: ['src/styles.css'],
50+
optimization: {
51+
styles: {
52+
removeSpecialComments: true,
53+
},
54+
},
55+
});
56+
57+
const { result } = await harness.executeOnce();
58+
expect(result?.success).toBeTrue();
59+
60+
harness.expectFile('dist/browser/styles.css').content.not.toContain('important-comment');
61+
});
62+
63+
it(`should not retain special comments when 'removeSpecialComments' is not set`, async () => {
64+
harness.useTarget('build', {
65+
...BASE_OPTIONS,
66+
extractLicenses: true,
67+
styles: ['src/styles.css'],
68+
optimization: {
69+
styles: {},
70+
},
71+
});
72+
73+
const { result } = await harness.executeOnce();
74+
expect(result?.success).toBeTrue();
75+
76+
harness.expectFile('dist/browser/styles.css').content.not.toContain('important-comment');
77+
});
78+
});
79+
});

packages/angular_devkit/build_angular/src/tools/esbuild/global-styles.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ export function createGlobalStylesBundleOptions(
6565
},
6666
loadCache,
6767
);
68-
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';
68+
69+
// Keep special CSS comments `/*! comment */` in place when `removeSpecialComments` is disabled.
70+
// These comments are special for a number of CSS tools such as Critters and PurgeCSS.
71+
buildOptions.legalComments = optimizationOptions.styles?.removeSpecialComments
72+
? 'none'
73+
: 'inline';
74+
6975
buildOptions.entryPoints = entryPoints;
7076

7177
buildOptions.plugins.unshift(

packages/angular_devkit/build_angular/src/utils/normalize-optimization.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
OptimizationClass,
1212
OptimizationUnion,
1313
StylesClass,
14-
} from '../builders/browser/schema';
14+
} from '../builders/application/schema';
1515

1616
export type NormalizedOptimizationOptions = Required<
1717
Omit<OptimizationClass, 'fonts' | 'styles'>
@@ -24,14 +24,17 @@ export function normalizeOptimization(
2424
optimization: OptimizationUnion = true,
2525
): NormalizedOptimizationOptions {
2626
if (typeof optimization === 'object') {
27+
const styleOptimization = !!optimization.styles;
28+
2729
return {
2830
scripts: !!optimization.scripts,
2931
styles:
3032
typeof optimization.styles === 'object'
3133
? optimization.styles
3234
: {
33-
minify: !!optimization.styles,
34-
inlineCritical: !!optimization.styles,
35+
minify: styleOptimization,
36+
removeSpecialComments: styleOptimization,
37+
inlineCritical: styleOptimization,
3538
},
3639
fonts:
3740
typeof optimization.fonts === 'object'
@@ -47,6 +50,7 @@ export function normalizeOptimization(
4750
styles: {
4851
minify: optimization,
4952
inlineCritical: optimization,
53+
removeSpecialComments: optimization,
5054
},
5155
fonts: {
5256
inline: optimization,

0 commit comments

Comments
 (0)