angular-cli/packages/@angular/cli/plugins/glob-copy-webpack-plugin.ts
Filipe Silva 9e91d8646b feat(@angular/cli): allow assets from outside of app root.
Fix #3555
Close #4691

BREAKING CHANGE: 'assets' as a string in angular-cli.json is no longer allowed, use an array instead.
2017-02-15 12:23:59 +00:00

106 lines
3.2 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import * as glob from 'glob';
import * as denodeify from 'denodeify';
const flattenDeep = require('lodash/flattenDeep');
const globPromise = <any>denodeify(glob);
const statPromise = <any>denodeify(fs.stat);
function isDirectory(path: string) {
try {
return fs.statSync(path).isDirectory();
} catch (_) {
return false;
}
}
interface Asset {
originPath: string;
destinationPath: string;
relativePath: string;
}
export interface Pattern {
glob: string;
input?: string;
output?: string;
}
export interface GlobCopyWebpackPluginOptions {
patterns: (string | Pattern)[];
globOptions: any;
}
// Adds an asset to the compilation assets;
function addAsset(compilation: any, asset: Asset) {
const realPath = path.resolve(asset.originPath, asset.relativePath);
// Make sure that asset keys use forward slashes, otherwise webpack dev server
const servedPath = path.join(asset.destinationPath, asset.relativePath).replace(/\\/g, '/');
// Don't re-add existing assets.
if (compilation.assets[servedPath]) {
return Promise.resolve();
}
// Read file and add it to assets;
return statPromise(realPath)
.then((stat: any) => compilation.assets[servedPath] = {
size: () => stat.size,
source: () => fs.readFileSync(realPath)
});
}
export class GlobCopyWebpackPlugin {
constructor(private options: GlobCopyWebpackPluginOptions) { }
apply(compiler: any): void {
let { patterns, globOptions } = this.options;
const defaultCwd = globOptions.cwd || compiler.options.context;
// Force nodir option, since we can't add dirs to assets.
globOptions.nodir = true;
// Process patterns.
patterns = patterns.map(pattern => {
// Convert all string patterns to Pattern type.
pattern = typeof pattern === 'string' ? { glob: pattern } : pattern;
// Add defaults
// Input is always resolved relative to the defaultCwd (appRoot)
pattern.input = path.resolve(defaultCwd, pattern.input || '');
pattern.output = pattern.output || '';
pattern.glob = pattern.glob || '';
// Convert dir patterns to globs.
if (isDirectory(path.resolve(pattern.input, pattern.glob))) {
pattern.glob = pattern.glob + '/**/*';
}
return pattern;
});
compiler.plugin('emit', (compilation: any, cb: any) => {
// Create an array of promises for each pattern glob
const globs = patterns.map((pattern: Pattern) => new Promise((resolve, reject) =>
// Individual patterns can override cwd
globPromise(pattern.glob, Object.assign({}, globOptions, { cwd: pattern.input }))
// Map the results onto an Asset
.then((globResults: string[]) => globResults.map(res => ({
originPath: pattern.input,
destinationPath: pattern.output,
relativePath: res
})))
.then((asset: Asset) => resolve(asset))
.catch(reject)
));
// Wait for all globs.
Promise.all(globs)
// Flatten results.
.then(assets => flattenDeep(assets))
// Add each asset to the compilation.
.then(assets =>
Promise.all(assets.map((asset: Asset) => addAsset(compilation, asset))))
.then(() => cb());
});
}
}