Skip to content

feat(@angular-devkit/build-angular): add maximumInlineSize and excludeFromInlining options to browser build #11826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/documentation/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,21 @@ See https://github.com/angular/angular-cli/issues/7797 for details.
Run the TypeScript type checker in a forked process.
</p>
</details>
<details>
<summary>exclude-from-inlining</summary>
<p>
<code>--exclude-from-inlining</code>
</p>
<p>
Files to exclude from CSS inlining.
</p>
</details>
<details>
<summary>maximum-inline-size</summary>
<p>
<code>--maximum-inline-size</code>
</p>
<p>
Maximum resource size to inline (KiB).
</p>
</details>
12 changes: 12 additions & 0 deletions packages/angular/cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,18 @@
"$ref": "#/definitions/targetOptions/definitions/browser/definitions/budget"
},
"default": []
},
"maximumInlineSize": {
"description": "Maximum resource size to inline (KiB).",
"type": "number",
"default": 10
},
"excludeFromInlining": {
"description": "Files to exclude from CSS inlining.",
"default": [],
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export interface BuildOptions {
lazyModules: string[];
platform?: 'browser' | 'server';
fileReplacements: CurrentFileReplacement[];

excludeFromInlining?: string[];
maximumInlineSize?: number;
}

export interface WebpackTestOptions extends BuildOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { BundleBudgetPlugin } from '../../plugins/bundle-budget';
import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin';
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
import { findUp } from '../../utilities/find-up';
import { AssetPatternObject, ExtraEntryPoint } from '../../../browser/schema';
import { AssetPatternObject } from '../../../browser/schema';
import { normalizeExtraEntryPoints } from './utils';

const ProgressPlugin = require('webpack/lib/ProgressPlugin');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

import * as webpack from 'webpack';
import * as path from 'path';
import { Minimatch } from 'minimatch';
import { SuppressExtractedTextChunksWebpackPlugin } from '../../plugins/webpack';
import { getOutputHashFormat } from './utils';
import { WebpackConfigOptions } from '../build-options';
import { findUp } from '../../utilities/find-up';
import { RawCssLoader } from '../../plugins/webpack';
import { ExtraEntryPoint } from '../../../browser/schema';
import { normalizeExtraEntryPoints } from './utils';
import { RemoveHashPlugin } from '../../plugins/remove-hash-plugin';

Expand Down Expand Up @@ -52,15 +52,22 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
const entryPoints: { [key: string]: string[] } = {};
const globalStylePaths: string[] = [];
const extraPlugins: any[] = [];
const cssSourceMap = buildOptions.sourceMap;
const {
sourceMap: cssSourceMap,
excludeFromInlining = [],
maximumInlineSize = 10,

// Convert absolute resource URLs to account for base-href and deploy-url.
deployUrl = '',
baseHref = '',
outputHashing
} = buildOptions;

// normalize to support ./ paths
const ignoreMatchers = excludeFromInlining.map(pattern => new Minimatch(path.normalize(pattern), { dot: true }));

// Maximum resource size to inline (KiB)
const maximumInlineSize = 10;
// Determine hashing format.
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
// Convert absolute resource URLs to account for base-href and deploy-url.
const baseHref = wco.buildOptions.baseHref || '';
const deployUrl = wco.buildOptions.deployUrl || '';
const hashFormat = getOutputHashFormat(outputHashing !);

const postcssPluginCreator = function (loader: webpack.loader.LoaderContext) {
return [
Expand Down Expand Up @@ -139,7 +146,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
{
// TODO: inline .cur if not supporting IE (use browserslist to check)
filter: (asset: PostcssUrlAsset) => {
return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur');
const { absolutePath, hash } = asset;
const url = path.relative(root, absolutePath);
return maximumInlineSize > 0 && !hash && !url.endsWith('.cur') && !ignoreMatchers.some(matchers => matchers.match(url));
},
url: 'inline',
// NOTE: maxSize is in KB
Expand Down Expand Up @@ -181,9 +190,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {

// Add style entry points.
if (entryPoints[style.bundleName]) {
entryPoints[style.bundleName].push(resolvedPath)
entryPoints[style.bundleName].push(resolvedPath);
} else {
entryPoints[style.bundleName] = [resolvedPath]
entryPoints[style.bundleName] = [resolvedPath];
}

// Add lazy styles to the list.
Expand All @@ -197,7 +206,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {

if (chunkIds.length > 0) {
// Add plugin to remove hashes from lazy styles.
extraPlugins.push(new RemoveHashPlugin({ chunkIds, hashFormat}));
extraPlugins.push(new RemoveHashPlugin({ chunkIds, hashFormat }));
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ export interface BrowserBuilderSchema {
* Budget thresholds to ensure parts of your application stay within boundaries which you set.
*/
budgets: Budget[];

/**
* Files to exclude from CSS inlining.
*/
excludeFromInlining: string[];

/**
* Maximum resource size to inline (KiB).
*/
maximumInlineSize: number;
}

export type AssetPattern = string | AssetPatternObject;
Expand Down
12 changes: 12 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,18 @@
"$ref": "#/definitions/budget"
},
"default": []
},
"maximumInlineSize": {
"description": "Maximum resource size to inline (KiB).",
"type": "number",
"default": 10
},
"excludeFromInlining": {
"description": "Files to exclude from CSS inlining.",
"default": [],
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,122 @@ describe('Browser Builder styles', () => {
).toPromise().then(done, done.fail);
});

describe(`when 'maximumInlineSize' is configured`, () => {
beforeEach(() => {
host.writeMultipleFiles({
'src/styles.scss': `
@font-face{
font-family:'Roboto';
font-style:normal;
font-weight:400;
src:local('Roboto'),local('Roboto-Regular'),
url('./roboto-regular.woff2') format('woff2'),
url('./roboto-regular.woff') format('woff'),
url('./roboto-regular.ttf') format('truetype')
}

a {
font-family: 'Roboto';
}
`,
});
});

it(`should inline resources if they don't exceed the configured size`, (done) => {
const overrides = {
extractCss: true,
maximumInlineSize: 20,
styles: ['src/styles.scss'],
};

runTargetSpec(host, browserTargetSpec, overrides).pipe(
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
tap(() => {
const fileName = 'dist/styles.css';
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
expect(content).toContain('data:font/woff2');
expect(content).toContain('data:font/woff');
expect(content).toContain('data:font/ttf');
}),
tap(() => {
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff2'))).toBe(false);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff'))).toBe(false);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.ttf'))).toBe(false);
}),
).toPromise().then(done, done.fail);
});

it('should not inline resources that exceed the configured size', (done) => {
const overrides = {
extractCss: true,
maximumInlineSize: 5,
styles: ['src/styles.scss'],
};

runTargetSpec(host, browserTargetSpec, overrides).pipe(
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
tap(() => {
const fileName = 'dist/styles.css';
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
expect(content).toContain('roboto-regular.woff');
expect(content).toContain('roboto-regular.ttf');
expect(content).toContain('roboto-regular.woff2');
}),
tap(() => {
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff2'))).toBe(true);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff'))).toBe(true);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.ttf'))).toBe(true);
}),
).toPromise().then(done, done.fail);
});

});

it(`should not inline resources that are listed in 'excludeFromInlining'`, (done) => {
host.writeMultipleFiles({
'src/styles.scss': `
@font-face{
font-family:'Roboto';
font-style:normal;
font-weight:400;
src:local('Roboto'),local('Roboto-Regular'),
url('./roboto-regular.woff2') format('woff2'),
url('./roboto-regular.woff') format('woff'),
url('./roboto-regular.ttf') format('truetype')
}

a {
font-family: 'Roboto';
}
`,
});

const overrides = {
extractCss: true,
maximumInlineSize: 100,
excludeFromInlining: ['**/*.woff2'],
styles: ['src/styles.scss'],
};

runTargetSpec(host, browserTargetSpec, overrides).pipe(
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
tap(() => {
const fileName = 'dist/styles.css';
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
expect(content).toContain('roboto-regular.woff2');

expect(content).not.toContain('data:font/woff2');
expect(content).toContain('data:font/woff');
expect(content).toContain('data:font/ttf');
}),
tap(() => {
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff2'))).toBe(true);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.woff'))).toBe(false);
expect(host.scopedSync().exists(normalize('dist/roboto-regular.ttf'))).toBe(false);
}),
).toPromise().then(done, done.fail);
});

it(`supports font-awesome imports`, (done) => {
host.writeMultipleFiles({
'src/styles.scss': `
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.