Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): add option to allow outputting c…
Browse files Browse the repository at this point in the history
…ss resources to a different folder

Added `resourcesOutputPath` option so that CSS assets such as images and fonts are outputted to a subfolder in `dist`
  • Loading branch information
alan-agius4 authored and hansl committed Nov 16, 2018
1 parent 5e7f995 commit a38566f
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 1 deletion.
Expand Up @@ -21,6 +21,7 @@ export interface BuildOptions {
optimization: boolean;
environment?: string;
outputPath: string;
resourcesOutputPath?: string;
aot?: boolean;
sourceMap?: boolean;
vendorSourceMap?: boolean;
Expand Down
Expand Up @@ -47,6 +47,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
// Convert absolute resource URLs to account for base-href and deploy-url.
const baseHref = wco.buildOptions.baseHref || '';
const deployUrl = wco.buildOptions.deployUrl || '';
const resourcesOutputPath = buildOptions.resourcesOutputPath || '';

const postcssPluginCreator = function (loader: webpack.loader.LoaderContext) {
return [
Expand All @@ -70,6 +71,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
PostcssCliResources({
baseHref,
deployUrl,
resourcesOutputPath,
loader,
filename: `[name]${hashFormat.file}.[ext]`,
}),
Expand Down
Expand Up @@ -27,6 +27,7 @@ function wrapUrl(url: string): string {
export interface PostcssCliResourcesOptions {
baseHref?: string;
deployUrl?: string;
resourcesOutputPath?: string;
filename: string;
loader: webpack.loader.LoaderContext;
}
Expand All @@ -47,6 +48,7 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
const {
deployUrl = '',
baseHref = '',
resourcesOutputPath = '',
filename,
loader,
} = options;
Expand Down Expand Up @@ -115,12 +117,16 @@ export default postcss.plugin('postcss-cli-resources', (options: PostcssCliResou
return;
}

const outputPath = interpolateName(
let outputPath = interpolateName(
{ resourcePath: result } as webpack.loader.LoaderContext,
filename,
{ content },
);

if (resourcesOutputPath) {
outputPath = path.posix.join(resourcesOutputPath, outputPath);
}

loader.addDependency(result);
loader.emitFile(outputPath, content, undefined);

Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.d.ts
Expand Up @@ -56,6 +56,11 @@ export interface BrowserBuilderSchema {
*/
outputPath: string;

/**
* Path where style resources will be placed (Relative to outputPath).
*/
resourcesOutputPath: string;

/**
* Build using Ahead of Time compilation.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.json
Expand Up @@ -71,6 +71,11 @@
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace.\n\nBy default, writes output to a folder named dist/ in the current project."
},
"resourcesOutputPath": {
"type": "string",
"description": "The path where style resources will be placed, relative to outputPath.",
"default": ""
},
"aot": {
"type": "boolean",
"description": "Build using Ahead of Time compilation.",
Expand Down
4 changes: 4 additions & 0 deletions packages/angular_devkit/build_angular/src/server/schema.d.ts
Expand Up @@ -49,6 +49,10 @@ export interface BuildWebpackServerSchema {
* Path where output will be placed.
*/
outputPath: string;
/**
* Path where style resources will be placed (Relative to outputPath).
*/
resourcesOutputPath: string;
/**
* Generates a 'stats.json' file which can be analyzed using tools such as:
* #webpack-bundle-analyzer' or https: //webpack.github.io/analyse.
Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/src/server/schema.json
Expand Up @@ -44,6 +44,11 @@
"type": "string",
"description": "Path where output will be placed."
},
"resourcesOutputPath": {
"type": "string",
"description": "The path where style resources will be placed, relative to outputPath.",
"default": ""
},
"sourceMap": {
"type": "boolean",
"description": "Output sourcemaps.",
Expand Down
@@ -0,0 +1,108 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// tslint:disable:no-big-function

import { runTargetSpec } from '@angular-devkit/architect/testing';
import { normalize, virtualFs } from '@angular-devkit/core';
import { tap } from 'rxjs/operators';
import { browserTargetSpec, host } from '../utils';


describe('Browser Builder styles resources output path', () => {
const stylesBundle = 'dist/styles.css';
const mainBundle = 'dist/main.js';

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>
`;

function writeFiles() {
// 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,
});
}

beforeEach(done => host.initialize().toPromise().then(done, done.fail));
afterEach(done => host.restore().toPromise().then(done, done.fail));

it(`supports resourcesOutputPath in resource urls`, (done) => {
writeFiles();
// Check base paths are correctly generated.
const overrides = {
aot: true,
extractCss: true,
resourcesOutputPath: 'out-assets',
};

runTargetSpec(host, browserTargetSpec, overrides, undefined, undefined).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('out-assets/global-img-relative.png')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
expect(main).toContain(`url('out-assets/component-img-relative.png')`);

expect(host.scopedSync()
.exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true);
expect(host.scopedSync()
.exists(normalize('dist/out-assets/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/out-assets/component-img-relative.png'))).toBe(true);
}),
).toPromise().then(done, done.fail);
});

it(`supports blank resourcesOutputPath`, (done) => {
writeFiles();

// Check base paths are correctly generated.
const overrides = { aot: true, extractCss: true };
runTargetSpec(host, browserTargetSpec, overrides, undefined, undefined).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);
}),
).toPromise().then(done, done.fail);
});

});

0 comments on commit a38566f

Please sign in to comment.