From 858f0f1f58e0f0e3ecca54b3ce8fa3ca3bdfc9fe Mon Sep 17 00:00:00 2001
From: Alexander Burakevych
Date: Fri, 22 Dec 2017 12:05:30 +1100
Subject: [PATCH] feat(@angular/cli): add build flag --inline-asset-max-size -
Maximum size of asset to be inlined
---
docs/documentation/build.md | 39 +++++++---
packages/@angular/cli/commands/build.ts | 5 ++
packages/@angular/cli/models/build-options.ts | 1 +
.../cli/models/webpack-configs/styles.ts | 77 ++++++++++++-------
.../build/styles/inline-asset-max-size.ts | 51 ++++++++++++
5 files changed, 134 insertions(+), 39 deletions(-)
create mode 100644 tests/e2e/tests/build/styles/inline-asset-max-size.ts
diff --git a/docs/documentation/build.md b/docs/documentation/build.md
index 19fe3371f891..83384285ae0c 100644
--- a/docs/documentation/build.md
+++ b/docs/documentation/build.md
@@ -73,15 +73,15 @@ dead code elimination via UglifyJS.
Both `--dev`/`--target=development` and `--prod`/`--target=production` are 'meta' flags, that set other flags.
If you do not specify either you will get the `--dev` defaults.
-Flag | `--dev` | `--prod`
---- | --- | ---
-`--aot` | `false` | `true`
-`--environment` | `dev` | `prod`
-`--output-hashing` | `media` | `all`
-`--sourcemaps` | `true` | `false`
-`--extract-css` | `false` | `true`
-`--named-chunks` | `true` | `false`
-`--build-optimizer` | `false` | `true` with AOT and Angular 5
+Flag | `--dev` | `--prod`
+--- | --- | ---
+`--aot` | `false` | `true`
+`--environment` | `dev` | `prod`
+`--output-hashing` | `media` | `all`
+`--sourcemaps` | `true` | `false`
+`--extract-css` | `false` | `true`
+`--named-chunks` | `true` | `false`
+`--build-optimizer` | `false` | `true` with AOT and Angular 5
`--prod` also sets the following non-flaggable settings:
- Adds service worker if configured in `.angular-cli.json`.
@@ -100,7 +100,11 @@ code.
### CSS resources
Resources in CSS, such as images and fonts, will be copied over automatically as part of a build.
-If a resource is less than 10kb it will also be inlined.
+If a resource is less than 10kb it will also be inlined by default.
+
+By using flaf `--inline-asset-max-size` it's possible to define the max size of the
+assets that have to be inlined. Negative values will result in no assets to be inlined.
+
You'll see these resources be outputted and fingerprinted at the root of `dist/`.
@@ -189,6 +193,19 @@ See https://github.com/angular/angular-cli/issues/7797 for details.
+
+ inline-asset-max-size
+
+ --inline-asset-max-size
+
+
+ Maximum size (in Kb) of assets to be inlined
+
+
+ Values: -1 - must not inline any assets, positive integer - size in Kb of assets to inline
+
+
+
i18n-file
@@ -404,4 +421,4 @@ See https://github.com/angular/angular-cli/issues/7797 for details.
Extract all licenses in a separate file, in the case of production builds only.
-
+
\ No newline at end of file
diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts
index 401a2b28289c..f2fd6b2db985 100644
--- a/packages/@angular/cli/commands/build.ts
+++ b/packages/@angular/cli/commands/build.ts
@@ -116,6 +116,11 @@ export const baseBuildCommandOptions: any = [
aliases: ['ec'],
description: 'Extract css from global styles onto css files instead of js ones.'
},
+ {
+ name: 'inline-asset-max-size',
+ type: Number,
+ description: 'Maximum size (in Kb) of assets to be inlined'
+ },
{
name: 'watch',
type: Boolean,
diff --git a/packages/@angular/cli/models/build-options.ts b/packages/@angular/cli/models/build-options.ts
index 05abcfb3ee23..fdb3fc360914 100644
--- a/packages/@angular/cli/models/build-options.ts
+++ b/packages/@angular/cli/models/build-options.ts
@@ -18,6 +18,7 @@ export interface BuildOptions {
locale?: string;
missingTranslation?: string;
extractCss?: boolean;
+ inlineAssetMaxSize?: number;
bundleDependencies?: 'none' | 'all';
watch?: boolean;
outputHashing?: string;
diff --git a/packages/@angular/cli/models/webpack-configs/styles.ts b/packages/@angular/cli/models/webpack-configs/styles.ts
index de830c0187f2..0ba05b920cd1 100644
--- a/packages/@angular/cli/models/webpack-configs/styles.ts
+++ b/packages/@angular/cli/models/webpack-configs/styles.ts
@@ -44,39 +44,60 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
const baseHref = wco.buildOptions.baseHref || '';
const deployUrl = wco.buildOptions.deployUrl || '';
+ const getPostcssUrlConfig = function() {
+ interface PostcssUrlConfig {
+ filter({ url }: { url: string}): boolean;
+ url: string | Function;
+ maxSize?: number;
+ }
+
+ let config: PostcssUrlConfig[] = [];
+
+ const convertRelativeUrlConfig: PostcssUrlConfig = {
+ // Only convert root relative URLs, which CSS-Loader won't process into require().
+ filter: ({ url }: { url: string}) => url.startsWith('/') && !url.startsWith('//'),
+ url: ({ url }: { url: string }) => {
+ if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
+ // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
+ return `${deployUrl.replace(/\/$/, '')}${url}`;
+ } else if (baseHref.match(/:\/\//)) {
+ // If baseHref contains a scheme, include it as is.
+ return baseHref.replace(/\/$/, '') +
+ `/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
+ } else {
+ // Join together base-href, deploy-url and the original URL.
+ // Also dedupe multiple slashes into single ones.
+ return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
+ }
+ }
+ };
+
+ config.push(convertRelativeUrlConfig);
+
+ // Do not inline any assets if the inline-asset-max-size option is negative
+ if (!buildOptions.inlineAssetMaxSize || buildOptions.inlineAssetMaxSize >= 0) {
+ const inlineAssetsConfig: PostcssUrlConfig = {
+ // TODO: inline .cur if not supporting IE (use browserslist to check)
+ filter: (asset: any) => !asset.hash && !asset.absolutePath.endsWith('.cur'),
+ url: 'inline',
+ // NOTE: maxSize is in KB
+ maxSize: Number.isInteger(buildOptions.inlineAssetMaxSize) ?
+ buildOptions.inlineAssetMaxSize : 10,
+ };
+
+ config.push(inlineAssetsConfig);
+ }
+
+ return config;
+ };
+
const postcssPluginCreator = function() {
return [
postcssUrl({
filter: ({ url }: { url: string }) => url.startsWith('~'),
url: ({ url }: { url: string }) => path.join(projectRoot, 'node_modules', url.substr(1)),
}),
- postcssUrl([
- {
- // Only convert root relative URLs, which CSS-Loader won't process into require().
- filter: ({ url }: { url: string }) => url.startsWith('/') && !url.startsWith('//'),
- url: ({ url }: { url: string }) => {
- if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
- // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
- return `${deployUrl.replace(/\/$/, '')}${url}`;
- } else if (baseHref.match(/:\/\//)) {
- // If baseHref contains a scheme, include it as is.
- return baseHref.replace(/\/$/, '') +
- `/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
- } else {
- // Join together base-href, deploy-url and the original URL.
- // Also dedupe multiple slashes into single ones.
- return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
- }
- }
- },
- {
- // TODO: inline .cur if not supporting IE (use browserslist to check)
- filter: (asset: any) => !asset.hash && !asset.absolutePath.endsWith('.cur'),
- url: 'inline',
- // NOTE: maxSize is in KB
- maxSize: 10
- }
- ]),
+ postcssUrl(getPostcssUrlConfig()),
autoprefixer(),
customProperties({ preserve: true })
];
@@ -195,7 +216,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
const ret: any = {
include: globalStylePaths,
test,
- use: buildOptions.extractCss ? ExtractTextPlugin.extract(extractTextPlugin)
+ use: buildOptions.extractCss ? ExtractTextPlugin.extract(extractTextPlugin)
: ['style-loader', ...extractTextPlugin.use]
};
// Save the original options as arguments for eject.
diff --git a/tests/e2e/tests/build/styles/inline-asset-max-size.ts b/tests/e2e/tests/build/styles/inline-asset-max-size.ts
new file mode 100644
index 000000000000..d423dbc7227d
--- /dev/null
+++ b/tests/e2e/tests/build/styles/inline-asset-max-size.ts
@@ -0,0 +1,51 @@
+import { ng } from '../../../utils/process';
+import {
+ expectFileToMatch,
+ expectFileMatchToExist,
+ writeMultipleFiles
+} from '../../../utils/fs';
+import { copyProjectAsset } from '../../../utils/assets';
+import { expectToFail } from '../../../utils/utils';
+
+const imgSvg = `
+
+`;
+
+export default function () {
+ return Promise.resolve()
+ .then(() => writeMultipleFiles({
+ 'src/styles.css': `
+ h1 { background: url('./assets/large.png'); }
+ h2 { background: url('./assets/small.svg'); }
+ p { background: url('./assets/small-id.svg#testID'); }
+ `,
+ 'src/app/app.component.css': `
+ h3 { background: url('../assets/small.svg'); }
+ h4 { background: url('../assets/large.png'); }
+ `,
+ 'src/assets/small.svg': imgSvg,
+ 'src/assets/small-id.svg': imgSvg
+ }))
+ .then(() => copyProjectAsset('images/spectrum.png', './assets/large.png'))
+ .then(() => ng('build', '--extract-css', '--inline-asset-max-size=-1', '--aot'))
+ // Check paths are correctly generated.
+ .then(() => expectFileToMatch('dist/styles.bundle.css',
+ /url\([\'"]?large\.[0-9a-f]{20}\.png[\'"]?\)/))
+ .then(() => expectFileToMatch('dist/styles.bundle.css',
+ /url\([\'"]?small\.[0-9a-f]{20}\.svg[\'"]?\)/))
+ .then(() => expectFileToMatch('dist/styles.bundle.css',
+ /url\([\'"]?small-id\.[0-9a-f]{20}\.svg#testID[\'"]?\)/))
+ .then(() => expectFileToMatch('dist/main.bundle.js',
+ /url\([\'"]?small\.[0-9a-f]{20}\.svg[\'"]?\)/))
+ .then(() => expectFileToMatch('dist/main.bundle.js',
+ /url\([\'"]?large\.[0-9a-f]{20}\.png[\'"]?\)/))
+ // Check if no inline images have been generated
+ .then(() => expectToFail(() => expectFileToMatch('dist/styles.bundle.css',
+ /url\(\\?[\'"]data:image\/svg\+xml/)))
+ // Check files are correctly created.
+ .then(() => expectFileMatchToExist('./dist', /large\.[0-9a-f]{20}\.png/))
+ .then(() => expectFileMatchToExist('./dist', /small\.[0-9a-f]{20}\.svg/))
+ .then(() => expectFileMatchToExist('./dist', /small-id\.[0-9a-f]{20}\.svg/));
+}