Compiler.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const Tapable = require("tapable");
  8. const util = require("util");
  9. const Compilation = require("./Compilation");
  10. const Stats = require("./Stats");
  11. const NormalModuleFactory = require("./NormalModuleFactory");
  12. const ContextModuleFactory = require("./ContextModuleFactory");
  13. const makePathsRelative = require("./util/identifier").makePathsRelative;
  14. class Watching {
  15. constructor(compiler, watchOptions, handler) {
  16. this.startTime = null;
  17. this.invalid = false;
  18. this.handler = handler;
  19. this.callbacks = [];
  20. this.closed = false;
  21. if(typeof watchOptions === "number") {
  22. this.watchOptions = {
  23. aggregateTimeout: watchOptions
  24. };
  25. } else if(watchOptions && typeof watchOptions === "object") {
  26. this.watchOptions = Object.assign({}, watchOptions);
  27. } else {
  28. this.watchOptions = {};
  29. }
  30. this.watchOptions.aggregateTimeout = this.watchOptions.aggregateTimeout || 200;
  31. this.compiler = compiler;
  32. this.running = true;
  33. this.compiler.readRecords(err => {
  34. if(err) return this._done(err);
  35. this._go();
  36. });
  37. }
  38. _go() {
  39. this.startTime = Date.now();
  40. this.running = true;
  41. this.invalid = false;
  42. this.compiler.applyPluginsAsync("watch-run", this, err => {
  43. if(err) return this._done(err);
  44. const onCompiled = (err, compilation) => {
  45. if(err) return this._done(err);
  46. if(this.invalid) return this._done();
  47. if(this.compiler.applyPluginsBailResult("should-emit", compilation) === false) {
  48. return this._done(null, compilation);
  49. }
  50. this.compiler.emitAssets(compilation, err => {
  51. if(err) return this._done(err);
  52. if(this.invalid) return this._done();
  53. this.compiler.emitRecords(err => {
  54. if(err) return this._done(err);
  55. if(compilation.applyPluginsBailResult("need-additional-pass")) {
  56. compilation.needAdditionalPass = true;
  57. const stats = new Stats(compilation);
  58. stats.startTime = this.startTime;
  59. stats.endTime = Date.now();
  60. this.compiler.applyPlugins("done", stats);
  61. this.compiler.applyPluginsAsync("additional-pass", err => {
  62. if(err) return this._done(err);
  63. this.compiler.compile(onCompiled);
  64. });
  65. return;
  66. }
  67. return this._done(null, compilation);
  68. });
  69. });
  70. };
  71. this.compiler.compile(onCompiled);
  72. });
  73. }
  74. _getStats(compilation) {
  75. const stats = new Stats(compilation);
  76. stats.startTime = this.startTime;
  77. stats.endTime = Date.now();
  78. return stats;
  79. }
  80. _done(err, compilation) {
  81. this.running = false;
  82. if(this.invalid) return this._go();
  83. const stats = compilation ? this._getStats(compilation) : null;
  84. if(err) {
  85. this.compiler.applyPlugins("failed", err);
  86. this.handler(err, stats);
  87. return;
  88. }
  89. this.compiler.applyPlugins("done", stats);
  90. this.handler(null, stats);
  91. if(!this.closed) {
  92. this.watch(compilation.fileDependencies, compilation.contextDependencies, compilation.missingDependencies);
  93. }
  94. this.callbacks.forEach(cb => cb());
  95. this.callbacks.length = 0;
  96. }
  97. watch(files, dirs, missing) {
  98. this.pausedWatcher = null;
  99. this.watcher = this.compiler.watchFileSystem.watch(files, dirs, missing, this.startTime, this.watchOptions, (err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) => {
  100. this.pausedWatcher = this.watcher;
  101. this.watcher = null;
  102. if(err) return this.handler(err);
  103. this.compiler.fileTimestamps = fileTimestamps;
  104. this.compiler.contextTimestamps = contextTimestamps;
  105. this.invalidate();
  106. }, (fileName, changeTime) => {
  107. this.compiler.applyPlugins("invalid", fileName, changeTime);
  108. });
  109. }
  110. invalidate(callback) {
  111. if(callback) {
  112. this.callbacks.push(callback);
  113. }
  114. if(this.watcher) {
  115. this.pausedWatcher = this.watcher;
  116. this.watcher.pause();
  117. this.watcher = null;
  118. }
  119. if(this.running) {
  120. this.invalid = true;
  121. return false;
  122. } else {
  123. this._go();
  124. }
  125. }
  126. close(callback) {
  127. if(callback === undefined) callback = function() {};
  128. this.closed = true;
  129. if(this.watcher) {
  130. this.watcher.close();
  131. this.watcher = null;
  132. }
  133. if(this.pausedWatcher) {
  134. this.pausedWatcher.close();
  135. this.pausedWatcher = null;
  136. }
  137. if(this.running) {
  138. this.invalid = true;
  139. this._done = () => {
  140. this.compiler.applyPlugins("watch-close");
  141. callback();
  142. };
  143. } else {
  144. this.compiler.applyPlugins("watch-close");
  145. callback();
  146. }
  147. }
  148. }
  149. class Compiler extends Tapable {
  150. constructor() {
  151. super();
  152. this.outputPath = "";
  153. this.outputFileSystem = null;
  154. this.inputFileSystem = null;
  155. this.recordsInputPath = null;
  156. this.recordsOutputPath = null;
  157. this.records = {};
  158. this.fileTimestamps = {};
  159. this.contextTimestamps = {};
  160. this.resolvers = {
  161. normal: null,
  162. loader: null,
  163. context: null
  164. };
  165. this.parser = {
  166. plugin: util.deprecate(
  167. (hook, fn) => {
  168. this.plugin("compilation", (compilation, data) => {
  169. data.normalModuleFactory.plugin("parser", parser => {
  170. parser.plugin(hook, fn);
  171. });
  172. });
  173. },
  174. "webpack: Using compiler.parser is deprecated.\n" +
  175. "Use compiler.plugin(\"compilation\", function(compilation, data) {\n data.normalModuleFactory.plugin(\"parser\", function(parser, options) { parser.plugin(/* ... */); });\n}); instead. "
  176. ),
  177. apply: util.deprecate(
  178. () => {
  179. const args = arguments;
  180. this.plugin("compilation", (compilation, data) => {
  181. data.normalModuleFactory.plugin("parser", parser => {
  182. parser.apply.apply(parser, args);
  183. });
  184. });
  185. },
  186. "webpack: Using compiler.parser is deprecated.\n" +
  187. "Use compiler.plugin(\"compilation\", function(compilation, data) {\n data.normalModuleFactory.plugin(\"parser\", function(parser, options) { parser.apply(/* ... */); });\n}); instead. "
  188. )
  189. };
  190. this.options = {};
  191. }
  192. watch(watchOptions, handler) {
  193. this.fileTimestamps = {};
  194. this.contextTimestamps = {};
  195. const watching = new Watching(this, watchOptions, handler);
  196. return watching;
  197. }
  198. run(callback) {
  199. const startTime = Date.now();
  200. const onCompiled = (err, compilation) => {
  201. if(err) return callback(err);
  202. if(this.applyPluginsBailResult("should-emit", compilation) === false) {
  203. const stats = new Stats(compilation);
  204. stats.startTime = startTime;
  205. stats.endTime = Date.now();
  206. this.applyPlugins("done", stats);
  207. return callback(null, stats);
  208. }
  209. this.emitAssets(compilation, err => {
  210. if(err) return callback(err);
  211. if(compilation.applyPluginsBailResult("need-additional-pass")) {
  212. compilation.needAdditionalPass = true;
  213. const stats = new Stats(compilation);
  214. stats.startTime = startTime;
  215. stats.endTime = Date.now();
  216. this.applyPlugins("done", stats);
  217. this.applyPluginsAsync("additional-pass", err => {
  218. if(err) return callback(err);
  219. this.compile(onCompiled);
  220. });
  221. return;
  222. }
  223. this.emitRecords(err => {
  224. if(err) return callback(err);
  225. const stats = new Stats(compilation);
  226. stats.startTime = startTime;
  227. stats.endTime = Date.now();
  228. this.applyPlugins("done", stats);
  229. return callback(null, stats);
  230. });
  231. });
  232. };
  233. this.applyPluginsAsync("before-run", this, err => {
  234. if(err) return callback(err);
  235. this.applyPluginsAsync("run", this, err => {
  236. if(err) return callback(err);
  237. this.readRecords(err => {
  238. if(err) return callback(err);
  239. this.compile(onCompiled);
  240. });
  241. });
  242. });
  243. }
  244. runAsChild(callback) {
  245. this.compile((err, compilation) => {
  246. if(err) return callback(err);
  247. this.parentCompilation.children.push(compilation);
  248. Object.keys(compilation.assets).forEach(name => {
  249. this.parentCompilation.assets[name] = compilation.assets[name];
  250. });
  251. const entries = Object.keys(compilation.entrypoints).map(name => {
  252. return compilation.entrypoints[name].chunks;
  253. }).reduce((array, chunks) => {
  254. return array.concat(chunks);
  255. }, []);
  256. return callback(null, entries, compilation);
  257. });
  258. }
  259. purgeInputFileSystem() {
  260. if(this.inputFileSystem && this.inputFileSystem.purge)
  261. this.inputFileSystem.purge();
  262. }
  263. emitAssets(compilation, callback) {
  264. let outputPath;
  265. const emitFiles = (err) => {
  266. if(err) return callback(err);
  267. require("async").forEach(Object.keys(compilation.assets), (file, callback) => {
  268. let targetFile = file;
  269. const queryStringIdx = targetFile.indexOf("?");
  270. if(queryStringIdx >= 0) {
  271. targetFile = targetFile.substr(0, queryStringIdx);
  272. }
  273. const writeOut = (err) => {
  274. if(err) return callback(err);
  275. const targetPath = this.outputFileSystem.join(outputPath, targetFile);
  276. const source = compilation.assets[file];
  277. if(source.existsAt === targetPath) {
  278. source.emitted = false;
  279. return callback();
  280. }
  281. let content = source.source();
  282. if(!Buffer.isBuffer(content)) {
  283. content = new Buffer(content, "utf8"); // eslint-disable-line
  284. }
  285. source.existsAt = targetPath;
  286. source.emitted = true;
  287. this.outputFileSystem.writeFile(targetPath, content, callback);
  288. };
  289. if(targetFile.match(/\/|\\/)) {
  290. const dir = path.dirname(targetFile);
  291. this.outputFileSystem.mkdirp(this.outputFileSystem.join(outputPath, dir), writeOut);
  292. } else writeOut();
  293. }, err => {
  294. if(err) return callback(err);
  295. afterEmit.call(this);
  296. });
  297. };
  298. this.applyPluginsAsync("emit", compilation, err => {
  299. if(err) return callback(err);
  300. outputPath = compilation.getPath(this.outputPath);
  301. this.outputFileSystem.mkdirp(outputPath, emitFiles);
  302. });
  303. function afterEmit() {
  304. this.applyPluginsAsyncSeries1("after-emit", compilation, err => {
  305. if(err) return callback(err);
  306. return callback();
  307. });
  308. }
  309. }
  310. emitRecords(callback) {
  311. if(!this.recordsOutputPath) return callback();
  312. const idx1 = this.recordsOutputPath.lastIndexOf("/");
  313. const idx2 = this.recordsOutputPath.lastIndexOf("\\");
  314. let recordsOutputPathDirectory = null;
  315. if(idx1 > idx2) recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx1);
  316. if(idx1 < idx2) recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx2);
  317. if(!recordsOutputPathDirectory) return writeFile.call(this);
  318. this.outputFileSystem.mkdirp(recordsOutputPathDirectory, err => {
  319. if(err) return callback(err);
  320. writeFile.call(this);
  321. });
  322. function writeFile() {
  323. this.outputFileSystem.writeFile(this.recordsOutputPath, JSON.stringify(this.records, undefined, 2), callback);
  324. }
  325. }
  326. readRecords(callback) {
  327. if(!this.recordsInputPath) {
  328. this.records = {};
  329. return callback();
  330. }
  331. this.inputFileSystem.stat(this.recordsInputPath, err => {
  332. // It doesn't exist
  333. // We can ignore this.
  334. if(err) return callback();
  335. this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
  336. if(err) return callback(err);
  337. try {
  338. this.records = JSON.parse(content.toString("utf-8"));
  339. } catch(e) {
  340. e.message = "Cannot parse records: " + e.message;
  341. return callback(e);
  342. }
  343. return callback();
  344. });
  345. });
  346. }
  347. createChildCompiler(compilation, compilerName, compilerIndex, outputOptions, plugins) {
  348. const childCompiler = new Compiler();
  349. if(Array.isArray(plugins)) {
  350. plugins.forEach(plugin => childCompiler.apply(plugin));
  351. }
  352. for(const name in this._plugins) {
  353. if(["make", "compile", "emit", "after-emit", "invalid", "done", "this-compilation"].indexOf(name) < 0)
  354. childCompiler._plugins[name] = this._plugins[name].slice();
  355. }
  356. childCompiler.name = compilerName;
  357. childCompiler.outputPath = this.outputPath;
  358. childCompiler.inputFileSystem = this.inputFileSystem;
  359. childCompiler.outputFileSystem = null;
  360. childCompiler.resolvers = this.resolvers;
  361. childCompiler.fileTimestamps = this.fileTimestamps;
  362. childCompiler.contextTimestamps = this.contextTimestamps;
  363. const relativeCompilerName = makePathsRelative(this.context, compilerName);
  364. if(!this.records[relativeCompilerName]) this.records[relativeCompilerName] = [];
  365. if(this.records[relativeCompilerName][compilerIndex])
  366. childCompiler.records = this.records[relativeCompilerName][compilerIndex];
  367. else
  368. this.records[relativeCompilerName].push(childCompiler.records = {});
  369. childCompiler.options = Object.create(this.options);
  370. childCompiler.options.output = Object.create(childCompiler.options.output);
  371. for(const name in outputOptions) {
  372. childCompiler.options.output[name] = outputOptions[name];
  373. }
  374. childCompiler.parentCompilation = compilation;
  375. compilation.applyPlugins("child-compiler", childCompiler, compilerName, compilerIndex);
  376. return childCompiler;
  377. }
  378. isChild() {
  379. return !!this.parentCompilation;
  380. }
  381. createCompilation() {
  382. return new Compilation(this);
  383. }
  384. newCompilation(params) {
  385. const compilation = this.createCompilation();
  386. compilation.fileTimestamps = this.fileTimestamps;
  387. compilation.contextTimestamps = this.contextTimestamps;
  388. compilation.name = this.name;
  389. compilation.records = this.records;
  390. compilation.compilationDependencies = params.compilationDependencies;
  391. this.applyPlugins("this-compilation", compilation, params);
  392. this.applyPlugins("compilation", compilation, params);
  393. return compilation;
  394. }
  395. createNormalModuleFactory() {
  396. const normalModuleFactory = new NormalModuleFactory(this.options.context, this.resolvers, this.options.module || {});
  397. this.applyPlugins("normal-module-factory", normalModuleFactory);
  398. return normalModuleFactory;
  399. }
  400. createContextModuleFactory() {
  401. const contextModuleFactory = new ContextModuleFactory(this.resolvers, this.inputFileSystem);
  402. this.applyPlugins("context-module-factory", contextModuleFactory);
  403. return contextModuleFactory;
  404. }
  405. newCompilationParams() {
  406. const params = {
  407. normalModuleFactory: this.createNormalModuleFactory(),
  408. contextModuleFactory: this.createContextModuleFactory(),
  409. compilationDependencies: []
  410. };
  411. return params;
  412. }
  413. compile(callback) {
  414. const params = this.newCompilationParams();
  415. this.applyPluginsAsync("before-compile", params, err => {
  416. if(err) return callback(err);
  417. this.applyPlugins("compile", params);
  418. const compilation = this.newCompilation(params);
  419. this.applyPluginsParallel("make", compilation, err => {
  420. if(err) return callback(err);
  421. compilation.finish();
  422. compilation.seal(err => {
  423. if(err) return callback(err);
  424. this.applyPluginsAsync("after-compile", compilation, err => {
  425. if(err) return callback(err);
  426. return callback(null, compilation);
  427. });
  428. });
  429. });
  430. });
  431. }
  432. }
  433. Compiler.Watching = Watching;
  434. module.exports = Compiler;