Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): disable by default stylesheet roo…
Browse files Browse the repository at this point in the history
…t relative URL rebasing

BREAKING CHANGE:
Root relative URLs are a standardized method to reference a resource path from the root of a host.  The previous behavior of the Angular CLI prevented this from occuring and resulted in an inability to reference stylesheet assets in this manner.  The initial reason for this behavior is no longer present in the internal implementation of the Angular CLI.  Therefore, this now unnecessary and non-standard behavior is being phased out.  If an application currently relies on this behavior, a compatibility option `rebaseRootRelativeCssUrls` has been provided for the 8.x release cycle to facilitate transition away from this non-standard and limiting behavior.  The recommended method to transition is to use relative paths within the source stylesheet.  This allows the build system to process and generate a full URL for the asset.
  • Loading branch information
clydin authored and Keen Yee Liau committed Feb 19, 2019
1 parent 9f33253 commit 94b087c
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 26 deletions.
6 changes: 6 additions & 0 deletions packages/angular/cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,12 @@
"description": "Enables conditionally loaded ES2015 polyfills.",
"type": "boolean",
"default": false
},
"rebaseRootRelativeCssUrls": {
"description": "Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.",
"type": "boolean",
"default": false,
"x-deprecated": true
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export interface BuildOptions {
lazyModules: string[];
platform?: 'browser' | 'server';
fileReplacements: CurrentFileReplacement[];
/** @deprecated use only for compatibility in 8.x; will be removed in 9.0 */
rebaseRootRelativeCssUrls?: boolean;
}

export interface WebpackTestOptions extends BuildOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ export function getStylesConfig(wco: WebpackConfigOptions) {

// Determine hashing format.
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
// Convert absolute resource URLs to account for base-href and deploy-url.
const baseHref = buildOptions.baseHref || '';
const deployUrl = buildOptions.deployUrl || '';
const resourcesOutputPath = buildOptions.resourcesOutputPath || '';

const postcssPluginCreator = function (loader: webpack.loader.LoaderContext) {
return [
Expand All @@ -70,10 +66,11 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
},
}),
PostcssCliResources({
baseHref,
deployUrl,
resourcesOutputPath,
baseHref: buildOptions.baseHref,
deployUrl: buildOptions.deployUrl,
resourcesOutputPath: buildOptions.resourcesOutputPath,
loader,
rebaseRootRelative: buildOptions.rebaseRootRelativeCssUrls,
filename: `[name]${hashFormat.file}.[ext]`,
}),
autoprefixer(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface PostcssCliResourcesOptions {
baseHref?: string;
deployUrl?: string;
resourcesOutputPath?: string;
rebaseRootRelative?: boolean;
filename: string;
loader: webpack.loader.LoaderContext;
}
Expand All @@ -49,6 +50,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
deployUrl = '',
baseHref = '',
resourcesOutputPath = '',
rebaseRootRelative = false,
filename,
loader,
} = options;
Expand All @@ -61,6 +63,10 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
return inputUrl;
}

if (!rebaseRootRelative && /^\//.test(inputUrl)) {
return inputUrl;
}

// If starts with a caret, remove and return remainder
// this supports bypassing asset processing
if (inputUrl.startsWith('^')) {
Expand Down
8 changes: 8 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 @@ -241,6 +241,14 @@ export interface BrowserBuilderSchema {
* Enables conditionally loaded IE9-11 polyfills.
*/
es5BrowserSupport: boolean;

/**
* @deprecated
* Change root relative URLs in stylesheets to include base HREF and deploy URL.
* Use only for compatibility and transition.
* The behavior of this option is non-standard and will be removed in the next major release.
*/
rebaseRootRelativeCssUrls: boolean;
}

export type OptimizationOptions = boolean | OptimizationObject;
Expand Down
6 changes: 6 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@
"description": "Enables conditionally loaded ES2015 polyfills.",
"type": "boolean",
"default": false
},
"rebaseRootRelativeCssUrls": {
"description": "Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.",
"type": "boolean",
"default": false,
"x-deprecated": true
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ describe('Browser Builder styles', () => {
});

// TODO: consider making this a unit test in the url processing plugins.
it(`supports baseHref and deployUrl in resource urls`, (done) => {
it(`supports baseHref/deployUrl in resource urls without rebaseRootRelativeCssUrls`, (done) => {
// Use a large image for the relative ref so it cannot be inlined.
host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png');
host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png');
Expand Down Expand Up @@ -396,6 +396,132 @@ describe('Browser Builder styles', () => {
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/', deployUrl: 'http://deploy.url/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles)
.toContain(`url('/assets/global-img-absolute.svg')`);
expect(main)
.toContain(`url('/assets/component-img-absolute.svg')`);
}),
// Check urls with base-href scheme are used as is (with deploy-url).
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: 'http://base.url/', deployUrl: 'deploy/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles)
.toContain(`url('/assets/global-img-absolute.svg')`);
expect(main)
.toContain(`url('/assets/component-img-absolute.svg')`);
}),
// Check urls with deploy-url and base-href scheme only use deploy-url.
concatMap(() => runTargetSpec(host, browserTargetSpec, {
extractCss: true,
baseHref: 'http://base.url/',
deployUrl: 'http://deploy.url/',
},
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles).toContain(`url('/assets/global-img-absolute.svg')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
}),
// Check with schemeless base-href and deploy-url flags.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/', deployUrl: 'deploy/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles).toContain(`url('/assets/global-img-absolute.svg')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
}),
// Check with identical base-href and deploy-url flags.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/', deployUrl: '/base/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles).toContain(`url('/assets/global-img-absolute.svg')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
}),
// Check with only base-href flag.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles).toContain(`url('/assets/global-img-absolute.svg')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
}),
).toPromise().then(done, done.fail);
}, 90000);

it(`supports baseHref/deployUrl in resource urls with rebaseRootRelativeCssUrls`, (done) => {
// Use a large image for the relative ref so it cannot be inlined.
host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png');
host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png');
host.writeMultipleFiles({
'src/styles.css': `
h1 { background: url('/assets/global-img-absolute.svg'); }
h2 { background: url('./assets/global-img-relative.png'); }
`,
'src/app/app.component.css': `
h3 { background: url('/assets/component-img-absolute.svg'); }
h4 { background: url('../assets/component-img-relative.png'); }
`,
'src/assets/global-img-absolute.svg': imgSvg,
'src/assets/component-img-absolute.svg': imgSvg,
});

const stylesBundle = 'dist/styles.css';
const mainBundle = 'dist/main.js';

// Check base paths are correctly generated.
const overrides = {
extractCss: true,
rebaseRootRelativeCssUrls: true,
};
runTargetSpec(host, browserTargetSpec, { ...overrides, aot: true }).pipe(
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
);
const main = virtualFs.fileBufferToString(host.scopedSync().read(normalize(mainBundle)));
expect(styles).toContain(`url('/assets/global-img-absolute.svg')`);
expect(styles).toContain(`url('global-img-relative.png')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
expect(main).toContain(`url('component-img-relative.png')`);
expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg')))
.toBe(true);
expect(host.scopedSync().exists(normalize('dist/global-img-relative.png')))
.toBe(true);
expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg')))
.toBe(true);
expect(host.scopedSync().exists(normalize('dist/component-img-relative.png')))
.toBe(true);
}),
// Check urls with deploy-url scheme are used as is.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ ...overrides, baseHref: '/base/', deployUrl: 'http://deploy.url/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
host.scopedSync().read(normalize(stylesBundle)),
Expand All @@ -408,7 +534,7 @@ describe('Browser Builder styles', () => {
}),
// Check urls with base-href scheme are used as is (with deploy-url).
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: 'http://base.url/', deployUrl: 'deploy/' },
{ ...overrides, baseHref: 'http://base.url/', deployUrl: 'deploy/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
Expand All @@ -422,7 +548,7 @@ describe('Browser Builder styles', () => {
}),
// Check urls with deploy-url and base-href scheme only use deploy-url.
concatMap(() => runTargetSpec(host, browserTargetSpec, {
extractCss: true,
...overrides,
baseHref: 'http://base.url/',
deployUrl: 'http://deploy.url/',
},
Expand All @@ -437,7 +563,7 @@ describe('Browser Builder styles', () => {
}),
// Check with schemeless base-href and deploy-url flags.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/', deployUrl: 'deploy/' },
{ ...overrides, baseHref: '/base/', deployUrl: 'deploy/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
Expand All @@ -449,7 +575,7 @@ describe('Browser Builder styles', () => {
}),
// Check with identical base-href and deploy-url flags.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/', deployUrl: '/base/' },
{ ...overrides, baseHref: '/base/', deployUrl: '/base/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
Expand All @@ -461,7 +587,7 @@ describe('Browser Builder styles', () => {
}),
// Check with only base-href flag.
concatMap(() => runTargetSpec(host, browserTargetSpec,
{ extractCss: true, baseHref: '/base/' },
{ ...overrides, baseHref: '/base/' },
)),
tap(() => {
const styles = virtualFs.fileBufferToString(
Expand Down
25 changes: 12 additions & 13 deletions tests/legacy-cli/e2e/tests/build/css-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export default function () {
'src/assets/global-img-absolute.svg': imgSvg,
'src/assets/component-img-absolute.svg': imgSvg
}))
// use image with file size >10KB to prevent inlining
.then(() => copyProjectAsset('images/spectrum.png', './src/assets/global-img-relative.png'))
.then(() => copyProjectAsset('images/spectrum.png', './src/assets/component-img-relative.png'))
.then(() => ng('build', '--extract-css', '--aot'))
Expand All @@ -52,54 +51,54 @@ export default function () {
.then(() => ng('build', '--base-href=/base/', '--deploy-url=http://deploy.url/',
'--extract-css'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/url\(\'http:\/\/deploy\.url\/assets\/global-img-absolute\.svg\'\)/))
/url\(\'\/assets\/global-img-absolute\.svg\'\)/))
.then(() => expectFileToMatch('dist/test-project/main.js',
/url\(\'http:\/\/deploy\.url\/assets\/component-img-absolute\.svg\'\)/))
/url\(\'\/assets\/component-img-absolute\.svg\'\)/))
// Check urls with base-href scheme are used as is (with deploy-url).
.then(() => ng('build', '--base-href=http://base.url/', '--deploy-url=deploy/',
'--extract-css'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/url\(\'http:\/\/base\.url\/deploy\/assets\/global-img-absolute\.svg\'\)/))
/url\(\'\/assets\/global-img-absolute\.svg\'\)/))
.then(() => expectFileToMatch('dist/test-project/main.js',
/url\(\'http:\/\/base\.url\/deploy\/assets\/component-img-absolute\.svg\'\)/))
/url\(\'\/assets\/component-img-absolute\.svg\'\)/))
// Check urls with deploy-url and base-href scheme only use deploy-url.
.then(() => ng('build', '--base-href=http://base.url/', '--deploy-url=http://deploy.url/',
'--extract-css'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/url\(\'http:\/\/deploy\.url\/assets\/global-img-absolute\.svg\'\)/))
/url\(\'\/assets\/global-img-absolute\.svg\'\)/))
.then(() => expectFileToMatch('dist/test-project/main.js',
/url\(\'http:\/\/deploy\.url\/assets\/component-img-absolute\.svg\'\)/))
/url\(\'\/assets\/component-img-absolute\.svg\'\)/))
// Check with base-href and deploy-url flags.
.then(() => ng('build', '--base-href=/base/', '--deploy-url=deploy/',
'--extract-css', '--aot'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
'/base/deploy/assets/global-img-absolute.svg'))
'/assets/global-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/global-img-relative\.png/))
.then(() => expectFileToMatch('dist/test-project/main.js',
'/base/deploy/assets/component-img-absolute.svg'))
'/assets/component-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/main.js',
/deploy\/component-img-relative\.png/))
// Check with identical base-href and deploy-url flags.
.then(() => ng('build', '--base-href=/base/', '--deploy-url=/base/',
'--extract-css', '--aot'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
'/base/assets/global-img-absolute.svg'))
'/assets/global-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/global-img-relative\.png/))
.then(() => expectFileToMatch('dist/test-project/main.js',
'/base/assets/component-img-absolute.svg'))
'/assets/component-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/main.js',
/\/base\/component-img-relative\.png/))
// Check with only base-href flag.
.then(() => ng('build', '--base-href=/base/',
'--extract-css', '--aot'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
'/base/assets/global-img-absolute.svg'))
'/assets/global-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/styles.css',
/global-img-relative\.png/))
.then(() => expectFileToMatch('dist/test-project/main.js',
'/base/assets/component-img-absolute.svg'))
'/assets/component-img-absolute.svg'))
.then(() => expectFileToMatch('dist/test-project/main.js',
/component-img-relative\.png/));
}

0 comments on commit 94b087c

Please sign in to comment.