Skip to content

Commit 856d36d

Browse files
committed
fix(@angular/cli): correctly compose absolute urls
Fixing component css in #4667 uncovered errors in CSS url processing. This PR correctly composes absolute urls when using `--base-href` and/or `--deploy-url`. Fix #4778 Fix #4782
1 parent d2bef98 commit 856d36d

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"opn": "4.0.2",
8282
"portfinder": "~1.0.12",
8383
"postcss-loader": "^0.13.0",
84+
"postcss-url": "^5.1.2",
8485
"raw-loader": "^0.5.1",
8586
"resolve": "^1.1.7",
8687
"rimraf": "^2.5.3",

packages/@angular/cli/models/webpack-configs/styles.ts

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as webpack from 'webpack';
22
import * as path from 'path';
3+
import { oneLineTrim } from 'common-tags';
34
import {
45
SuppressExtractedTextChunksWebpackPlugin
56
} from '../../plugins/suppress-entry-chunks-webpack-plugin';
@@ -8,6 +9,7 @@ import { WebpackConfigOptions } from '../webpack-config';
89
import { pluginArgs } from '../../tasks/eject';
910

1011
const cssnano = require('cssnano');
12+
const postcssUrl = require('postcss-url');
1113
const autoprefixer = require('autoprefixer');
1214
const ExtractTextPlugin = require('extract-text-webpack-plugin');
1315

@@ -39,11 +41,30 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
3941
// https://github.com/webpack-contrib/style-loader#recommended-configuration
4042
const cssSourceMap = buildOptions.extractCss && buildOptions.sourcemap;
4143

42-
// minify/optimize css in production
43-
// autoprefixer is always run separately so disable here
44-
const extraPostCssPlugins = buildOptions.target === 'production'
45-
? [cssnano({ safe: true, autoprefixer: false })]
46-
: [];
44+
// Minify/optimize css in production.
45+
const cssnanoPlugin = cssnano({ safe: true, autoprefixer: false });
46+
47+
// Convert absolute resource URLs to account for base-href and deploy-url.
48+
const urlPlugin = postcssUrl({
49+
url: (URL: string) => {
50+
// Only convert absolute URLs, which CSS-Loader won't process into require().
51+
if (!URL.startsWith('/')) {
52+
return URL;
53+
}
54+
// Join together base-href, deploy-url and the original URL.
55+
// Also dedupe multiple slashes into single ones.
56+
return oneLineTrim`
57+
/${wco.buildOptions.baseHref || ''}
58+
/${wco.buildOptions.deployUrl || ''}
59+
/${URL}
60+
`.replace(/\/\/+/g, '/');
61+
}
62+
});
63+
64+
// PostCSS plugins.
65+
const postCssPlugins = [autoprefixer(), urlPlugin].concat(
66+
buildOptions.target === 'production' ? [cssnanoPlugin] : []
67+
);
4768

4869
// determine hashing format
4970
const hashFormat = getOutputHashFormat(buildOptions.outputHashing);
@@ -141,7 +162,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
141162
new webpack.LoaderOptionsPlugin({
142163
sourceMap: cssSourceMap,
143164
options: {
144-
postcss: [autoprefixer()].concat(extraPostCssPlugins),
165+
postcss: postCssPlugins,
145166
// css-loader, stylus-loader don't support LoaderOptionsPlugin properly
146167
// options are in query instead
147168
sassLoader: { sourceMap: cssSourceMap, includePaths },

packages/@angular/cli/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"opn": "4.0.2",
6767
"portfinder": "~1.0.12",
6868
"postcss-loader": "^0.13.0",
69+
"postcss-url": "^5.1.2",
6970
"raw-loader": "^0.5.1",
7071
"resolve": "^1.1.7",
7172
"rimraf": "^2.5.3",

tests/e2e/tests/build/css-urls.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ng } from '../../utils/process';
2+
import { expectFileToMatch, writeMultipleFiles } from '../../utils/fs';
3+
import { stripIndents } from 'common-tags';
4+
5+
6+
export default function () {
7+
return Promise.resolve()
8+
// Verify absolute/relative paths in global/component css.
9+
.then(() => writeMultipleFiles({
10+
'src/styles.css': `
11+
h1 { background: url('/assets/img-absolute.svg'); }
12+
h2 { background: url('./assets/img-relative.svg'); }
13+
`,
14+
'src/app/app.component.css': `
15+
h3 { background: url('/assets/img-absolute.svg'); }
16+
h4 { background: url('../assets/img-relative.svg'); }
17+
`,
18+
// Using SVGs because they are loaded via file-loader and thus never inlined.
19+
'src/assets/img-absolute.svg': stripIndents`
20+
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
21+
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
22+
</svg>
23+
`,
24+
'src/assets/img-relative.svg': stripIndents`
25+
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
26+
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
27+
</svg>
28+
`}))
29+
.then(() => ng('build', '--extract-css'))
30+
.then(() => expectFileToMatch('dist/styles.bundle.css', `url\('\/assets\/img-absolute\.svg'\)`))
31+
.then(() => expectFileToMatch('dist/styles.bundle.css', 'url\(img-relative.svg\)'))
32+
.then(() => expectFileToMatch('dist/main.bundle.js', `url\('\/assets\/img-absolute\.svg'\)`))
33+
.then(() => expectFileToMatch('dist/main.bundle.js', 'background: url\(" \+ __webpack_require'))
34+
// Also check with base-href and deploy-url flags.
35+
.then(() => ng('build', '--base-href=/base/', '--deploy-url=/deploy/', '--extract-css'))
36+
.then(() => expectFileToMatch('dist/styles.bundle.css',
37+
`url\('\/deploy\/public\/assets\/img-absolute\.svg'\)`))
38+
.then(() => expectFileToMatch('dist/styles.bundle.css', 'url\(img-relative.svg\)'))
39+
.then(() => expectFileToMatch('dist/main.bundle.js',
40+
`url\('\/deploy\/public\/assets\/img-absolute\.svg'\)`))
41+
.then(() => expectFileToMatch('dist/main.bundle.js',
42+
'background: url\(" \+ __webpack_require'));
43+
}

0 commit comments

Comments
 (0)