Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): add option to retain CSS special…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
alan-agius4 committed Nov 29, 2023
1 parent adba8be commit e0b274b
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@
"type": "boolean",
"description": "Extract and inline critical CSS definitions to improve first paint time.",
"default": true
},
"removeSpecialComments": {
"type": "boolean",
"description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
"default": true
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { buildApplication } from '../../index';
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';

describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
describe('Behavior: "removeSpecialComments"', () => {
beforeEach(async () => {
await harness.writeFile(
'src/styles.css',
`
/* normal-comment */
/*! important-comment */
div { flex: 1 }
`,
);
});

it(`should retain special comments when 'removeSpecialComments' is set to 'false'`, async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
extractLicenses: true,
styles: ['src/styles.css'],
optimization: {
styles: {
removeSpecialComments: false,
},
},
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();

harness
.expectFile('dist/browser/styles.css')
.content.toMatch(/\/\*! important-comment \*\/[\s\S]*div{flex:1}/);
});

it(`should not retain special comments when 'removeSpecialComments' is set to 'true'`, async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
extractLicenses: true,
styles: ['src/styles.css'],
optimization: {
styles: {
removeSpecialComments: true,
},
},
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();

harness.expectFile('dist/browser/styles.css').content.not.toContain('important-comment');
});

it(`should not retain special comments when 'removeSpecialComments' is not set`, async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
extractLicenses: true,
styles: ['src/styles.css'],
optimization: {
styles: {},
},
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();

harness.expectFile('dist/browser/styles.css').content.not.toContain('important-comment');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ export function createGlobalStylesBundleOptions(
},
loadCache,
);
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';

// Keep special CSS comments `/*! comment */` in place when `removeSpecialComments` is disabled.
// These comments are special for a number of CSS tools such as Critters and PurgeCSS.
buildOptions.legalComments = optimizationOptions.styles?.removeSpecialComments
? 'none'
: 'inline';

buildOptions.entryPoints = entryPoints;

buildOptions.plugins.unshift(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
OptimizationClass,
OptimizationUnion,
StylesClass,
} from '../builders/browser/schema';
} from '../builders/application/schema';

export type NormalizedOptimizationOptions = Required<
Omit<OptimizationClass, 'fonts' | 'styles'>
Expand All @@ -24,14 +24,17 @@ export function normalizeOptimization(
optimization: OptimizationUnion = true,
): NormalizedOptimizationOptions {
if (typeof optimization === 'object') {
const styleOptimization = !!optimization.styles;

return {
scripts: !!optimization.scripts,
styles:
typeof optimization.styles === 'object'
? optimization.styles
: {
minify: !!optimization.styles,
inlineCritical: !!optimization.styles,
minify: styleOptimization,
removeSpecialComments: styleOptimization,
inlineCritical: styleOptimization,
},
fonts:
typeof optimization.fonts === 'object'
Expand All @@ -47,6 +50,7 @@ export function normalizeOptimization(
styles: {
minify: optimization,
inlineCritical: optimization,
removeSpecialComments: optimization,
},
fonts: {
inline: optimization,
Expand Down

0 comments on commit e0b274b

Please sign in to comment.