diff --git a/docs/documentation/build.md b/docs/documentation/build.md
index 6577e905f160..1c32d7fa49fa 100644
--- a/docs/documentation/build.md
+++ b/docs/documentation/build.md
@@ -404,3 +404,21 @@ See https://github.com/angular/angular-cli/issues/7797 for details.
Run the TypeScript type checker in a forked process.
+
+ exclude-from-inlining
+
+ --exclude-from-inlining
+
+
+ Files to exclude from CSS inlining.
+
+
+
+ maximum-inline-size
+
+ --maximum-inline-size
+
+
+ Maximum resource size to inline (KiB).
+
+
\ No newline at end of file
diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json
index e2b067df91a8..76a72a48c511 100644
--- a/packages/angular/cli/lib/config/schema.json
+++ b/packages/angular/cli/lib/config/schema.json
@@ -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,
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
index a81e728e45de..88b43525265b 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
@@ -64,6 +64,9 @@ export interface BuildOptions {
lazyModules: string[];
platform?: 'browser' | 'server';
fileReplacements: CurrentFileReplacement[];
+
+ excludeFromInlining?: string[];
+ maximumInlineSize?: number;
}
export interface WebpackTestOptions extends BuildOptions {
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
index cab614154b63..086c5eb4d3a7 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
@@ -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');
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
index e4eb5c7a69aa..95b2d4f75b07 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
@@ -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';
@@ -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 [
@@ -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
@@ -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.
@@ -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 }));
}
}
diff --git a/packages/angular_devkit/build_angular/src/browser/schema.d.ts b/packages/angular_devkit/build_angular/src/browser/schema.d.ts
index eb887a851f24..7cce84325cf6 100644
--- a/packages/angular_devkit/build_angular/src/browser/schema.d.ts
+++ b/packages/angular_devkit/build_angular/src/browser/schema.d.ts
@@ -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;
diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json
index d013f123e68f..e014654966df 100644
--- a/packages/angular_devkit/build_angular/src/browser/schema.json
+++ b/packages/angular_devkit/build_angular/src/browser/schema.json
@@ -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,
diff --git a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
index 091c9291129b..0958102f56c4 100644
--- a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
+++ b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
@@ -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': `
diff --git a/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.ttf b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.ttf
new file mode 100644
index 000000000000..05037ed5e53b
Binary files /dev/null and b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.ttf differ
diff --git a/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff
new file mode 100644
index 000000000000..5e353cf47a87
Binary files /dev/null and b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff differ
diff --git a/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff2 b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff2
new file mode 100644
index 000000000000..96a601550e3c
Binary files /dev/null and b/tests/angular_devkit/build_angular/hello-world-app/src/roboto-regular.woff2 differ