UNPKG

6.17 kBJavaScriptView Raw
1var _ = require("lodash");
2var path = require("path");
3var async = require("async");
4var webpackDevMiddleware = require("webpack-dev-middleware");
5var webpack = require("webpack");
6var SingleEntryDependency = require("webpack/lib/dependencies/SingleEntryDependency");
7
8function Plugin(
9 /* config.webpack */webpackOptions,
10 /* config.webpackServer */webpackServerOptions,
11 /* config.webpackMiddleware */webpackMiddlewareOptions,
12 /* config.basePath */basePath,
13 /* config.files */files,
14 /* config.frameworks */frameworks,
15 customFileHandlers,
16 emitter) {
17 webpackOptions = _.clone(webpackOptions) || {};
18 webpackMiddlewareOptions = _.clone(webpackMiddlewareOptions || webpackServerOptions) || {};
19
20 var applyOptions = Array.isArray(webpackOptions) ? webpackOptions : [webpackOptions];
21 var includeIndex = applyOptions.length > 1;
22
23 applyOptions.forEach(function(webpackOptions, index) {
24 // The webpack tier owns the watch behavior so we want to force it in the config
25 webpackOptions.watch = true;
26
27 if(!webpackOptions.output) webpackOptions.output = {};
28
29 // When using an array, even of length 1, we want to include the index value for the build.
30 // This is due to the way that the dev server exposes commonPath for build output.
31 var indexPath = includeIndex ? index + "/" : "";
32
33 // Must have the common _karma_webpack_ prefix on path here to avoid
34 // https://github.com/webpack/webpack/issues/645
35 webpackOptions.output.path = "/_karma_webpack_/" + indexPath;
36 webpackOptions.output.publicPath = "/_karma_webpack_/" + indexPath + "/";
37 webpackOptions.output.filename = "[name]";
38 if(includeIndex)
39 webpackOptions.output.jsonpFunction = "webpackJsonp" + index;
40 webpackOptions.output.chunkFilename = "[id].chunk.js";
41 });
42
43 this.emitter = emitter;
44 this.wrapMocha = frameworks.indexOf('mocha') >= 0 && includeIndex;
45 this.optionsCount = applyOptions.length;
46 this.files = [];
47 this.basePath = basePath;
48 this.waiting = [];
49
50 var compiler = webpack(webpackOptions);
51 var applyPlugins = compiler.compilers || [compiler];
52 applyPlugins.forEach(function(compiler) {
53 compiler.plugin("this-compilation", function(compilation, params) {
54 compilation.dependencyFactories.set(SingleEntryDependency, params.normalModuleFactory);
55 });
56 compiler.plugin("make", this.make.bind(this));
57 }, this);
58
59 compiler.plugin("done", function(stats) {
60 var applyStats = Array.isArray(stats.stats) ? stats.stats : [stats];
61 var assets = [];
62 var noAssets = false;
63 applyStats.forEach(function(stats) {
64 stats = stats.toJson();
65
66 assets.push.apply(assets, stats.assets);
67 if(stats.assets.length === 0)
68 noAssets = true;
69 });
70
71 if(!this.waiting || this.waiting.length === 0) {
72 this.notifyKarmaAboutChanges();
73 }
74
75 if(this.waiting && !noAssets) {
76 var w = this.waiting;
77 this.waiting = null;
78 w.forEach(function(cb) {
79 cb();
80 });
81 }
82 }.bind(this));
83 compiler.plugin("invalid", function() {
84 if(!this.waiting) this.waiting = [];
85 }.bind(this));
86
87 webpackMiddlewareOptions.publicPath = "/_karma_webpack_/";
88 var middleware = this.middleware = new webpackDevMiddleware(compiler, webpackMiddlewareOptions);
89
90 customFileHandlers.push({
91 urlRegex: /^\/_karma_webpack_\/.*/,
92 handler: function(req, res) {
93 middleware(req, res, function() {
94 res.statusCode = 404;
95 res.end('Not found');
96 });
97 }
98 });
99
100 emitter.on("exit", function (done) {
101 middleware.close();
102 done();
103 });
104}
105
106Plugin.prototype.notifyKarmaAboutChanges = function() {
107 // Force a rebuild
108 this.emitter.refreshFiles();
109};
110
111Plugin.prototype.addFile = function(entry) {
112 if(this.files.indexOf(entry) >= 0) return;
113 this.files.push(entry);
114 return true;
115};
116
117Plugin.prototype.make = function(compilation, callback) {
118 async.forEach(this.files.slice(), function(file, callback) {
119 var entry = file;
120 if (this.wrapMocha) {
121 entry = require.resolve("./mocha-env-loader") + "!" + entry;
122 }
123
124 var dep = new SingleEntryDependency(entry);
125 compilation.addEntry("", dep, path.relative(this.basePath, file).replace(/\\/g, "/"), function() {
126 // If the module fails because of an File not found error, remove the test file
127 if(dep.module && dep.module.error && dep.module.error.error && dep.module.error.error.code === "ENOENT") {
128 this.files = this.files.filter(function(f) {
129 return file !== f;
130 });
131 this.middleware.invalidate();
132 }
133 callback();
134 }.bind(this));
135 }.bind(this), callback);
136};
137
138Plugin.prototype.readFile = function(file, callback) {
139 var middleware = this.middleware;
140 var optionsCount = this.optionsCount;
141
142 function doRead() {
143 if(optionsCount > 1) {
144 async.times(optionsCount, function(idx, callback) {
145 middleware.fileSystem.readFile("/_karma_webpack_/" + idx + "/" + file.replace(/\\/g, "/"), callback);
146 }, function(err, contents) {
147 if(err) return callback(err);
148 contents = contents.reduce(function(arr, x) {
149 if(!arr) return [x];
150 arr.push(new Buffer("\n"), x);
151 return arr;
152 }, null);
153 callback(null, Buffer.concat(contents));
154 });
155 } else {
156 middleware.fileSystem.readFile("/_karma_webpack_/" + file.replace(/\\/g, "/"), callback);
157 }
158 }
159 if(!this.waiting)
160 doRead();
161 else
162 // Retry to read once a build is finished
163 // do it on process.nextTick to catch changes while building
164 this.waiting.push(process.nextTick.bind(process, this.readFile.bind(this, file, callback)));
165};
166
167function createPreprocesor(/* config.basePath */basePath, webpackPlugin) {
168 return function(content, file, done) {
169
170 if (webpackPlugin.addFile(file.path)) {
171 // recompile as we have an asset that we have not seen before
172 webpackPlugin.middleware.invalidate();
173 }
174
175 // read blocks until bundle is done
176 webpackPlugin.readFile(path.relative(basePath, file.path), function(err, content) {
177 if (err) {
178 throw err;
179 }
180
181 done(err, content && content.toString());
182 });
183 };
184}
185
186module.exports = {
187 "webpackPlugin": ["type", Plugin],
188 "preprocessor:webpack": ["factory", createPreprocesor]
189};