twreporter-react
Version:
React-Redux site for The Reporter Foundation in Taiwan
190 lines (163 loc) • 6.17 kB
JavaScript
var _ = require("lodash");
var path = require("path");
var async = require("async");
var webpackDevMiddleware = require("webpack-dev-middleware");
var webpack = require("webpack");
var SingleEntryDependency = require("webpack/lib/dependencies/SingleEntryDependency");
function Plugin(
/* config.webpack */webpackOptions,
/* config.webpackServer */webpackServerOptions,
/* config.webpackMiddleware */webpackMiddlewareOptions,
/* config.basePath */basePath,
/* config.files */files,
/* config.frameworks */frameworks,
customFileHandlers,
emitter) {
webpackOptions = _.clone(webpackOptions) || {};
webpackMiddlewareOptions = _.clone(webpackMiddlewareOptions || webpackServerOptions) || {};
var applyOptions = Array.isArray(webpackOptions) ? webpackOptions : [webpackOptions];
var includeIndex = applyOptions.length > 1;
applyOptions.forEach(function(webpackOptions, index) {
// The webpack tier owns the watch behavior so we want to force it in the config
webpackOptions.watch = true;
if(!webpackOptions.output) webpackOptions.output = {};
// When using an array, even of length 1, we want to include the index value for the build.
// This is due to the way that the dev server exposes commonPath for build output.
var indexPath = includeIndex ? index + "/" : "";
// Must have the common _karma_webpack_ prefix on path here to avoid
// https://github.com/webpack/webpack/issues/645
webpackOptions.output.path = "/_karma_webpack_/" + indexPath;
webpackOptions.output.publicPath = "/_karma_webpack_/" + indexPath + "/";
webpackOptions.output.filename = "[name]";
if(includeIndex)
webpackOptions.output.jsonpFunction = "webpackJsonp" + index;
webpackOptions.output.chunkFilename = "[id].chunk.js";
});
this.emitter = emitter;
this.wrapMocha = frameworks.indexOf('mocha') >= 0 && includeIndex;
this.optionsCount = applyOptions.length;
this.files = [];
this.basePath = basePath;
this.waiting = [];
var compiler = webpack(webpackOptions);
var applyPlugins = compiler.compilers || [compiler];
applyPlugins.forEach(function(compiler) {
compiler.plugin("this-compilation", function(compilation, params) {
compilation.dependencyFactories.set(SingleEntryDependency, params.normalModuleFactory);
});
compiler.plugin("make", this.make.bind(this));
}, this);
compiler.plugin("done", function(stats) {
var applyStats = Array.isArray(stats.stats) ? stats.stats : [stats];
var assets = [];
var noAssets = false;
applyStats.forEach(function(stats) {
stats = stats.toJson();
assets.push.apply(assets, stats.assets);
if(stats.assets.length === 0)
noAssets = true;
});
if(!this.waiting || this.waiting.length === 0) {
this.notifyKarmaAboutChanges();
}
if(this.waiting && !noAssets) {
var w = this.waiting;
this.waiting = null;
w.forEach(function(cb) {
cb();
});
}
}.bind(this));
compiler.plugin("invalid", function() {
if(!this.waiting) this.waiting = [];
}.bind(this));
webpackMiddlewareOptions.publicPath = "/_karma_webpack_/";
var middleware = this.middleware = new webpackDevMiddleware(compiler, webpackMiddlewareOptions);
customFileHandlers.push({
urlRegex: /^\/_karma_webpack_\/.*/,
handler: function(req, res) {
middleware(req, res, function() {
res.statusCode = 404;
res.end('Not found');
});
}
});
emitter.on("exit", function (done) {
middleware.close();
done();
});
}
Plugin.prototype.notifyKarmaAboutChanges = function() {
// Force a rebuild
this.emitter.refreshFiles();
};
Plugin.prototype.addFile = function(entry) {
if(this.files.indexOf(entry) >= 0) return;
this.files.push(entry);
return true;
};
Plugin.prototype.make = function(compilation, callback) {
async.forEach(this.files.slice(), function(file, callback) {
var entry = file;
if (this.wrapMocha) {
entry = require.resolve("./mocha-env-loader") + "!" + entry;
}
var dep = new SingleEntryDependency(entry);
compilation.addEntry("", dep, path.relative(this.basePath, file).replace(/\\/g, "/"), function() {
// If the module fails because of an File not found error, remove the test file
if(dep.module && dep.module.error && dep.module.error.error && dep.module.error.error.code === "ENOENT") {
this.files = this.files.filter(function(f) {
return file !== f;
});
this.middleware.invalidate();
}
callback();
}.bind(this));
}.bind(this), callback);
};
Plugin.prototype.readFile = function(file, callback) {
var middleware = this.middleware;
var optionsCount = this.optionsCount;
function doRead() {
if(optionsCount > 1) {
async.times(optionsCount, function(idx, callback) {
middleware.fileSystem.readFile("/_karma_webpack_/" + idx + "/" + file.replace(/\\/g, "/"), callback);
}, function(err, contents) {
if(err) return callback(err);
contents = contents.reduce(function(arr, x) {
if(!arr) return [x];
arr.push(new Buffer("\n"), x);
return arr;
}, null);
callback(null, Buffer.concat(contents));
});
} else {
middleware.fileSystem.readFile("/_karma_webpack_/" + file.replace(/\\/g, "/"), callback);
}
}
if(!this.waiting)
doRead();
else
// Retry to read once a build is finished
// do it on process.nextTick to catch changes while building
this.waiting.push(process.nextTick.bind(process, this.readFile.bind(this, file, callback)));
};
function createPreprocesor(/* config.basePath */basePath, webpackPlugin) {
return function(content, file, done) {
if (webpackPlugin.addFile(file.path)) {
// recompile as we have an asset that we have not seen before
webpackPlugin.middleware.invalidate();
}
// read blocks until bundle is done
webpackPlugin.readFile(path.relative(basePath, file.path), function(err, content) {
if (err) {
throw err;
}
done(err, content && content.toString());
});
};
}
module.exports = {
"webpackPlugin": ["type", Plugin],
"preprocessor:webpack": ["factory", createPreprocesor]
};