mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 11:03:53 +08:00
feat(@angular-devkit/build-angular): remove inlining of assets in css (#12027)
BREAKING CHANGE: Assets under 10Kib are not longer inlined in css
This commit is contained in:
parent
67e32a8f25
commit
a2342b6a75
@ -53,8 +53,6 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
const extraPlugins: any[] = [];
|
const extraPlugins: any[] = [];
|
||||||
const cssSourceMap = buildOptions.sourceMap;
|
const cssSourceMap = buildOptions.sourceMap;
|
||||||
|
|
||||||
// Maximum resource size to inline (KiB)
|
|
||||||
const maximumInlineSize = 10;
|
|
||||||
// Determine hashing format.
|
// Determine hashing format.
|
||||||
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
|
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
|
||||||
// Convert absolute resource URLs to account for base-href and deploy-url.
|
// Convert absolute resource URLs to account for base-href and deploy-url.
|
||||||
@ -134,18 +132,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
|
return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
// TODO: inline .cur if not supporting IE (use browserslist to check)
|
|
||||||
filter: (asset: PostcssUrlAsset) => {
|
|
||||||
return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur');
|
|
||||||
},
|
|
||||||
url: 'inline',
|
|
||||||
// NOTE: maxSize is in KB
|
|
||||||
maxSize: maximumInlineSize,
|
|
||||||
fallback: 'rebase',
|
|
||||||
},
|
|
||||||
{ url: 'rebase' },
|
|
||||||
]),
|
]),
|
||||||
PostcssCliResources({
|
PostcssCliResources({
|
||||||
deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl,
|
deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl,
|
||||||
@ -196,7 +183,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
|
|
||||||
if (chunkNames.length > 0) {
|
if (chunkNames.length > 0) {
|
||||||
// Add plugin to remove hashes from lazy styles.
|
// Add plugin to remove hashes from lazy styles.
|
||||||
extraPlugins.push(new RemoveHashPlugin({ chunkNames, hashFormat}));
|
extraPlugins.push(new RemoveHashPlugin({ chunkNames, hashFormat }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +203,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
const baseRules: webpack.Rule[] = [
|
const baseRules: webpack.Rule[] = [
|
||||||
{ test: /\.css$/, use: [] },
|
{ test: /\.css$/, use: [] },
|
||||||
{
|
{
|
||||||
test: /\.scss$|\.sass$/, use: [{
|
test: /\.scss$|\.sass$/,
|
||||||
|
use: [{
|
||||||
loader: 'sass-loader',
|
loader: 'sass-loader',
|
||||||
options: {
|
options: {
|
||||||
implementation: dartSass,
|
implementation: dartSass,
|
||||||
@ -229,7 +217,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.less$/, use: [{
|
test: /\.less$/,
|
||||||
|
use: [{
|
||||||
loader: 'less-loader',
|
loader: 'less-loader',
|
||||||
options: {
|
options: {
|
||||||
sourceMap: cssSourceMap,
|
sourceMap: cssSourceMap,
|
||||||
@ -239,7 +228,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.styl$/, use: [{
|
test: /\.styl$/,
|
||||||
|
use: [{
|
||||||
loader: 'stylus-loader',
|
loader: 'stylus-loader',
|
||||||
options: {
|
options: {
|
||||||
sourceMap: cssSourceMap,
|
sourceMap: cssSourceMap,
|
||||||
@ -251,7 +241,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
|
|
||||||
// load component css as raw strings
|
// load component css as raw strings
|
||||||
const rules: webpack.Rule[] = baseRules.map(({ test, use }) => ({
|
const rules: webpack.Rule[] = baseRules.map(({ test, use }) => ({
|
||||||
exclude: globalStylePaths, test, use: [
|
exclude: globalStylePaths,
|
||||||
|
test,
|
||||||
|
use: [
|
||||||
{ loader: 'raw-loader' },
|
{ loader: 'raw-loader' },
|
||||||
{
|
{
|
||||||
loader: 'postcss-loader',
|
loader: 'postcss-loader',
|
||||||
@ -306,16 +298,17 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (buildOptions.extractCss) {
|
if (buildOptions.extractCss) {
|
||||||
// extract global css from js files into own css file
|
|
||||||
extraPlugins.push(
|
extraPlugins.push(
|
||||||
new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }));
|
// extract global css from js files into own css file
|
||||||
|
new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }),
|
||||||
// suppress empty .js files in css only entry points
|
// suppress empty .js files in css only entry points
|
||||||
extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin());
|
new SuppressExtractedTextChunksWebpackPlugin(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entry: entryPoints,
|
entry: entryPoints,
|
||||||
module: { rules },
|
module: { rules },
|
||||||
plugins: [].concat(extraPlugins as any)
|
plugins: extraPlugins
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -278,62 +278,6 @@ describe('Browser Builder styles', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inlines resources', (done) => {
|
|
||||||
host.copyFile('src/spectrum.png', 'src/large.png');
|
|
||||||
host.writeMultipleFiles({
|
|
||||||
'src/styles.scss': `
|
|
||||||
h1 { background: url('./large.png'),
|
|
||||||
linear-gradient(to bottom, #0e40fa 25%, #0654f4 75%); }
|
|
||||||
h2 { background: url('./small.svg'); }
|
|
||||||
p { background: url(./small-id.svg#testID); }
|
|
||||||
`,
|
|
||||||
'src/app/app.component.css': `
|
|
||||||
h3 { background: url('../small.svg'); }
|
|
||||||
h4 { background: url("../large.png"); }
|
|
||||||
`,
|
|
||||||
'src/small.svg': imgSvg,
|
|
||||||
'src/small-id.svg': imgSvg,
|
|
||||||
});
|
|
||||||
|
|
||||||
const overrides = {
|
|
||||||
aot: true,
|
|
||||||
extractCss: true,
|
|
||||||
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)));
|
|
||||||
// Large image should not be inlined, and gradient should be there.
|
|
||||||
expect(content).toMatch(
|
|
||||||
/url\(['"]?large\.png['"]?\),\s+linear-gradient\(to bottom, #0e40fa 25%, #0654f4 75%\);/);
|
|
||||||
// Small image should be inlined.
|
|
||||||
expect(content).toMatch(/url\(\\?['"]data:image\/svg\+xml/);
|
|
||||||
// Small image with param should not be inlined.
|
|
||||||
expect(content).toMatch(/url\(['"]?small-id\.svg#testID['"]?\)/);
|
|
||||||
}),
|
|
||||||
tap(() => {
|
|
||||||
const fileName = 'dist/main.js';
|
|
||||||
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
|
|
||||||
// Large image should not be inlined.
|
|
||||||
expect(content).toMatch(/url\((?:['"]|\\')?large\.png(?:['"]|\\')?\)/);
|
|
||||||
// Small image should be inlined.
|
|
||||||
expect(content).toMatch(/url\(\\?['"]data:image\/svg\+xml/);
|
|
||||||
}),
|
|
||||||
tap(() => {
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/small.svg'))).toBe(false);
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/large.png'))).toBe(true);
|
|
||||||
expect(host.scopedSync().exists(normalize('dist/small-id.svg'))).toBe(true);
|
|
||||||
}),
|
|
||||||
// TODO: find a way to check logger/output for warnings.
|
|
||||||
// if (stdout.match(/postcss-url: \.+: Can't read file '\.+', ignoring/)) {
|
|
||||||
// throw new Error('Expected no postcss-url file read warnings.');
|
|
||||||
// }
|
|
||||||
).toPromise().then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`supports font-awesome imports`, (done) => {
|
it(`supports font-awesome imports`, (done) => {
|
||||||
host.writeMultipleFiles({
|
host.writeMultipleFiles({
|
||||||
'src/styles.scss': `
|
'src/styles.scss': `
|
||||||
@ -407,8 +351,6 @@ describe('Browser Builder styles', () => {
|
|||||||
h3 { background: url('/assets/component-img-absolute.svg'); }
|
h3 { background: url('/assets/component-img-absolute.svg'); }
|
||||||
h4 { background: url('../assets/component-img-relative.png'); }
|
h4 { background: url('../assets/component-img-relative.png'); }
|
||||||
`,
|
`,
|
||||||
// Use a small SVG for the absolute image to help validate that it is being referenced,
|
|
||||||
// because it is so small it would be inlined usually.
|
|
||||||
'src/assets/global-img-absolute.svg': imgSvg,
|
'src/assets/global-img-absolute.svg': imgSvg,
|
||||||
'src/assets/component-img-absolute.svg': imgSvg,
|
'src/assets/component-img-absolute.svg': imgSvg,
|
||||||
});
|
});
|
||||||
@ -427,12 +369,12 @@ describe('Browser Builder styles', () => {
|
|||||||
expect(styles).toContain(`url('global-img-relative.png')`);
|
expect(styles).toContain(`url('global-img-relative.png')`);
|
||||||
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
|
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
|
||||||
expect(main).toContain(`url('component-img-relative.png')`);
|
expect(main).toContain(`url('component-img-relative.png')`);
|
||||||
expect(host.scopedSync().exists(normalize('dist/global-img-absolute.svg')))
|
expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg')))
|
||||||
.toBe(false);
|
.toBe(true);
|
||||||
expect(host.scopedSync().exists(normalize('dist/global-img-relative.png')))
|
expect(host.scopedSync().exists(normalize('dist/global-img-relative.png')))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
expect(host.scopedSync().exists(normalize('dist/component-img-absolute.svg')))
|
expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg')))
|
||||||
.toBe(false);
|
.toBe(true);
|
||||||
expect(host.scopedSync().exists(normalize('dist/component-img-relative.png')))
|
expect(host.scopedSync().exists(normalize('dist/component-img-relative.png')))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
}),
|
}),
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
import { ng, silentNpm } from '../../../utils/process';
|
|
||||||
import {
|
|
||||||
expectFileToMatch,
|
|
||||||
expectFileToExist,
|
|
||||||
expectFileMatchToExist,
|
|
||||||
writeMultipleFiles
|
|
||||||
} from '../../../utils/fs';
|
|
||||||
import { copyProjectAsset } from '../../../utils/assets';
|
|
||||||
import { expectToFail } from '../../../utils/utils';
|
|
||||||
import { updateJsonFile } from '../../../utils/project';
|
|
||||||
|
|
||||||
const imgSvg = `
|
|
||||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
// TODO(architect): Delete this test. It is now in devkit/build-angular.
|
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(() => silentNpm('install', 'font-awesome@4.7.0'))
|
|
||||||
.then(() => writeMultipleFiles({
|
|
||||||
'src/styles.scss': `
|
|
||||||
$fa-font-path: "~font-awesome/fonts";
|
|
||||||
@import "~font-awesome/scss/font-awesome";
|
|
||||||
h1 { background: url('./assets/large.png'),
|
|
||||||
linear-gradient(to bottom, #0e40fa 25%, #0654f4 75%); }
|
|
||||||
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', './src/assets/large.png'))
|
|
||||||
.then(() => updateJsonFile('angular.json', workspaceJson => {
|
|
||||||
const appArchitect = workspaceJson.projects['test-project'].targets;
|
|
||||||
appArchitect.build.options.styles = [
|
|
||||||
{ input: 'src/styles.scss' }
|
|
||||||
];
|
|
||||||
}))
|
|
||||||
.then(() => ng('build', '--extract-css', '--aot'))
|
|
||||||
.then(({ stdout }) => {
|
|
||||||
if (stdout.match(/postcss-url: \.+: Can't read file '\.+', ignoring/)) {
|
|
||||||
throw new Error('Expected no postcss-url file read warnings.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Check paths are correctly generated.
|
|
||||||
.then(() => expectFileToMatch('dist/test-project/styles.css',
|
|
||||||
/url\(['"]?large\.png['"]?\),\s+linear-gradient\(to bottom, #0e40fa 25%, #0654f4 75%\);/))
|
|
||||||
.then(() => expectFileToMatch('dist/test-project/styles.css',
|
|
||||||
/url\(\\?['"]data:image\/svg\+xml/))
|
|
||||||
.then(() => expectFileToMatch('dist/test-project/styles.css',
|
|
||||||
/url\(['"]?small-id\.svg#testID['"]?\)/))
|
|
||||||
.then(() => expectFileToMatch('dist/test-project/main.js',
|
|
||||||
/url\(\\?['"]data:image\/svg\+xml/))
|
|
||||||
.then(() => expectFileToMatch('dist/test-project/main.js',
|
|
||||||
/url\((?:['"]|\\')?large\.png(?:['"]|\\')?\)/))
|
|
||||||
// Check files are correctly created.
|
|
||||||
.then(() => expectToFail(() => expectFileToExist('dist/test-project/small.svg')))
|
|
||||||
.then(() => expectFileMatchToExist('./dist/test-project', /large\.png/))
|
|
||||||
.then(() => expectFileMatchToExist('./dist/test-project', /small-id\.svg/));
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user