* Update dependencies * Remove monkey-hot-loader * Use webpack-merge instead of util/mergeWithConcat() * Fix: copyglobs plugin `after-emit` is bound twice * NoErrorsPlugin() is deprecated in favor of NoEmitOnErrorsPlugin() * webpack-dev-middleware should use same publicPath as compiler * webpack-hot-middleware/client should be prepended to entries * Browser should refresh when HMR fails * Bootstrap package.json has correct main property Sometime between alpha 2 and 3, package.json was pointing to nonexistent file, so we referenced file manually in our repo. Underlying issue was fixed by alpha 4, so we no longer have to specify full path to file.
148 lines
4.3 KiB
JavaScript
148 lines
4.3 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 = [];
|
|
this.started = false;
|
|
}
|
|
apply(compiler) {
|
|
if (this.disable) {
|
|
return;
|
|
}
|
|
this.compiler = compiler;
|
|
this.resolveWorkingDirectory();
|
|
if (!this.started) {
|
|
compiler.plugin('emit', this.emitHandler.bind(this));
|
|
compiler.plugin('after-emit', this.afterEmitHandler.bind(this));
|
|
this.started = true;
|
|
}
|
|
}
|
|
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];
|
|
}
|
|
};
|