144 lines
4.2 KiB
JavaScript
144 lines
4.2 KiB
JavaScript
'use strict'; // eslint-disable-line
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const glob = require('glob');
|
|
const utils = require('loader-utils');
|
|
const includes = require('lodash/includes');
|
|
|
|
const interpolateName = require('./util/interpolateName');
|
|
const promisify = require('./util/promisify');
|
|
|
|
const fixPath = v => v.replace(/\\/g, '/');
|
|
const errorMsg = msg => `\x1b[31m${msg}\x1b[0m`;
|
|
|
|
const GLOB_CWD_AUTO = null;
|
|
|
|
const globAsync = promisify(glob);
|
|
const statAsync = promisify(fs.stat);
|
|
const readFileAsync = promisify(fs.readFile);
|
|
|
|
class PatternUndefinedError extends Error {
|
|
constructor() {
|
|
super(errorMsg('[copy-globs] You must provide glob pattern.'));
|
|
}
|
|
}
|
|
|
|
class ArgsArrayError extends TypeError {
|
|
constructor() {
|
|
super(errorMsg(
|
|
'[copy-globs] pattern cannot be an array.\n' +
|
|
'For multiple folders, use something like:\n\n' +
|
|
' +(images|fonts)/**/*\n\n' +
|
|
'See also: https://github.com/isaacs/node-glob#glob-primer\n'
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throws an error if pattern is an array or undefined
|
|
*
|
|
* @param pattern
|
|
*/
|
|
const testPattern = (pattern) => {
|
|
if (pattern === undefined) {
|
|
throw new PatternUndefinedError();
|
|
}
|
|
if (Array.isArray(pattern)) {
|
|
throw new ArgsArrayError();
|
|
}
|
|
};
|
|
|
|
const normalizeArguments = (input) => {
|
|
testPattern(input);
|
|
const options = {};
|
|
if (typeof input === 'string') {
|
|
options.pattern = input;
|
|
} else {
|
|
testPattern(input.pattern);
|
|
return input;
|
|
}
|
|
return options;
|
|
};
|
|
|
|
module.exports = class {
|
|
constructor(o) {
|
|
const options = normalizeArguments(o);
|
|
this.pattern = options.pattern;
|
|
this.disable = options.disable;
|
|
this.output = options.output || '[path][name].[ext]';
|
|
this.globOptions = Object.assign(options.globOptions || {}, { cwd: GLOB_CWD_AUTO });
|
|
this.globOptions.nodir = true;
|
|
this.manifest = options.manifest || {};
|
|
this.files = [];
|
|
}
|
|
apply(compiler) {
|
|
if (this.disable) {
|
|
return;
|
|
}
|
|
this.compiler = compiler;
|
|
this.resolveWorkingDirectory();
|
|
compiler.plugin('emit', this.emitHandler.bind(this));
|
|
compiler.plugin('after-emit', this.afterEmitHandler.bind(this));
|
|
}
|
|
emitHandler(compilation, callback) {
|
|
this.compilation = compilation;
|
|
globAsync(this.pattern, this.globOptions)
|
|
.then(
|
|
paths => Promise.all(paths.map(this.processAsset.bind(this))),
|
|
err => compilation.errors.push(err)
|
|
)
|
|
.then(() => {
|
|
Object.keys(this.files).forEach((absoluteFrom) => {
|
|
const file = this.files[absoluteFrom];
|
|
this.manifest[file.relativeFrom] = file.webpackTo;
|
|
this.compilation.assets[file.webpackTo] = {
|
|
size: () => file.stat.size,
|
|
source: () => file.content,
|
|
};
|
|
});
|
|
})
|
|
.then(callback);
|
|
}
|
|
afterEmitHandler(compilation, callback) {
|
|
Object.keys(this.files)
|
|
.filter(absoluteFrom => !includes(compilation.fileDependencies, absoluteFrom))
|
|
.forEach(absoluteFrom => compilation.fileDependencies.push(absoluteFrom));
|
|
callback();
|
|
}
|
|
resolveWorkingDirectory() {
|
|
if (this.globOptions.cwd === GLOB_CWD_AUTO) {
|
|
this.globOptions.cwd = this.compiler.options.context;
|
|
}
|
|
this.context = this.globOptions.cwd || this.compiler.options.context;
|
|
}
|
|
processAsset(relativeFrom) {
|
|
if (this.compilation.assets[relativeFrom]) {
|
|
return Promise.resolve();
|
|
}
|
|
const absoluteFrom = path.resolve(this.context, relativeFrom);
|
|
return statAsync(absoluteFrom)
|
|
.then(stat => this.buildFileObject(relativeFrom, absoluteFrom, stat))
|
|
.then(this.addAsset.bind(this));
|
|
}
|
|
buildFileObject(relativeFrom, absoluteFrom, stat) {
|
|
return readFileAsync(absoluteFrom)
|
|
.then((content) => {
|
|
const hash = utils.getHashDigest(content);
|
|
const webpackTo = fixPath(interpolateName(this.output, relativeFrom, content));
|
|
return { relativeFrom, absoluteFrom, stat, content, hash, webpackTo };
|
|
});
|
|
}
|
|
addAsset(file) {
|
|
const asset = this.getAsset(file.absoluteFrom);
|
|
if (asset && asset.hash === file.hash) {
|
|
return null;
|
|
}
|
|
this.files[file.absoluteFrom] = file;
|
|
return file;
|
|
}
|
|
getAsset(absoluteFrom) {
|
|
return this.files[absoluteFrom];
|
|
}
|
|
};
|