Shared.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. var mime = require("mime");
  2. var parseRange = require("range-parser");
  3. var pathIsAbsolute = require("path-is-absolute");
  4. var MemoryFileSystem = require("memory-fs");
  5. var timestamp = require("time-stamp");
  6. var HASH_REGEXP = /[0-9a-f]{10,}/;
  7. module.exports = function Shared(context) {
  8. var share = {
  9. setOptions: function(options) {
  10. if(!options) options = {};
  11. if(typeof options.reportTime === "undefined") options.reportTime = false;
  12. if(typeof options.watchOptions === "undefined") options.watchOptions = {};
  13. if(typeof options.reporter !== "function") options.reporter = share.defaultReporter;
  14. if(typeof options.log !== "function") options.log = console.log.bind(console);
  15. if(typeof options.warn !== "function") options.warn = console.warn.bind(console);
  16. if(typeof options.error !== "function") options.error = console.error.bind(console);
  17. if(typeof options.watchDelay !== "undefined") {
  18. // TODO remove this in next major version
  19. options.warn("options.watchDelay is deprecated: Use 'options.watchOptions.aggregateTimeout' instead");
  20. options.watchOptions.aggregateTimeout = options.watchDelay;
  21. }
  22. if(typeof options.watchOptions.aggregateTimeout === "undefined") options.watchOptions.aggregateTimeout = 200;
  23. if(typeof options.stats === "undefined") options.stats = {};
  24. if(!options.stats.context) options.stats.context = process.cwd();
  25. if(options.lazy) {
  26. if(typeof options.filename === "string") {
  27. var str = options.filename
  28. .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")
  29. .replace(/\\\[[a-z]+\\\]/ig, ".+");
  30. options.filename = new RegExp("^[\/]{0,1}" + str + "$");
  31. }
  32. }
  33. // defining custom MIME type
  34. if(options.mimeTypes) mime.define(options.mimeTypes);
  35. context.options = options;
  36. },
  37. defaultReporter: function(reporterOptions) {
  38. var time = "";
  39. var state = reporterOptions.state;
  40. var stats = reporterOptions.stats;
  41. var options = reporterOptions.options;
  42. if(!!options.reportTime) {
  43. time = "[" + timestamp("HH:mm:ss") + "] ";
  44. }
  45. if(state) {
  46. var displayStats = (!options.quiet && options.stats !== false);
  47. if(displayStats && !(stats.hasErrors() || stats.hasWarnings()) &&
  48. options.noInfo)
  49. displayStats = false;
  50. if(displayStats) {
  51. if(stats.hasErrors()) {
  52. options.error(stats.toString(options.stats));
  53. } else if(stats.hasWarnings()) {
  54. options.warn(stats.toString(options.stats));
  55. } else {
  56. options.log(stats.toString(options.stats));
  57. }
  58. }
  59. if(!options.noInfo && !options.quiet) {
  60. var msg = "Compiled successfully.";
  61. if(stats.hasErrors()) {
  62. msg = "Failed to compile.";
  63. } else if(stats.hasWarnings()) {
  64. msg = "Compiled with warnings.";
  65. }
  66. options.log(time + "webpack: " + msg);
  67. }
  68. } else {
  69. options.log(time + "webpack: Compiling...");
  70. }
  71. },
  72. handleRangeHeaders: function handleRangeHeaders(content, req, res) {
  73. //assumes express API. For other servers, need to add logic to access alternative header APIs
  74. res.setHeader("Accept-Ranges", "bytes");
  75. if(req.headers.range) {
  76. var ranges = parseRange(content.length, req.headers.range);
  77. // unsatisfiable
  78. if(-1 == ranges) {
  79. res.setHeader("Content-Range", "bytes */" + content.length);
  80. res.statusCode = 416;
  81. }
  82. // valid (syntactically invalid/multiple ranges are treated as a regular response)
  83. if(-2 != ranges && ranges.length === 1) {
  84. // Content-Range
  85. res.statusCode = 206;
  86. var length = content.length;
  87. res.setHeader(
  88. "Content-Range",
  89. "bytes " + ranges[0].start + "-" + ranges[0].end + "/" + length
  90. );
  91. content = content.slice(ranges[0].start, ranges[0].end + 1);
  92. }
  93. }
  94. return content;
  95. },
  96. setFs: function(compiler) {
  97. if(typeof compiler.outputPath === "string" && !pathIsAbsolute.posix(compiler.outputPath) && !pathIsAbsolute.win32(compiler.outputPath)) {
  98. throw new Error("`output.path` needs to be an absolute path or `/`.");
  99. }
  100. // store our files in memory
  101. var fs;
  102. var isMemoryFs = !compiler.compilers && compiler.outputFileSystem instanceof MemoryFileSystem;
  103. if(isMemoryFs) {
  104. fs = compiler.outputFileSystem;
  105. } else {
  106. fs = compiler.outputFileSystem = new MemoryFileSystem();
  107. }
  108. context.fs = fs;
  109. },
  110. compilerDone: function(stats) {
  111. // We are now on valid state
  112. context.state = true;
  113. context.webpackStats = stats;
  114. // Do the stuff in nextTick, because bundle may be invalidated
  115. // if a change happened while compiling
  116. process.nextTick(function() {
  117. // check if still in valid state
  118. if(!context.state) return;
  119. // print webpack output
  120. context.options.reporter({
  121. state: true,
  122. stats: stats,
  123. options: context.options
  124. });
  125. // execute callback that are delayed
  126. var cbs = context.callbacks;
  127. context.callbacks = [];
  128. cbs.forEach(function continueBecauseBundleAvailable(cb) {
  129. cb(stats);
  130. });
  131. });
  132. // In lazy mode, we may issue another rebuild
  133. if(context.forceRebuild) {
  134. context.forceRebuild = false;
  135. share.rebuild();
  136. }
  137. },
  138. compilerInvalid: function() {
  139. if(context.state && (!context.options.noInfo && !context.options.quiet))
  140. context.options.reporter({
  141. state: false,
  142. options: context.options
  143. });
  144. // We are now in invalid state
  145. context.state = false;
  146. //resolve async
  147. if(arguments.length === 2 && typeof arguments[1] === "function") {
  148. var callback = arguments[1];
  149. callback();
  150. }
  151. },
  152. ready: function ready(fn, req) {
  153. var options = context.options;
  154. if(context.state) return fn(context.webpackStats);
  155. if(!options.noInfo && !options.quiet)
  156. options.log("webpack: wait until bundle finished: " + (req.url || fn.name));
  157. context.callbacks.push(fn);
  158. },
  159. startWatch: function() {
  160. var options = context.options;
  161. var compiler = context.compiler;
  162. // start watching
  163. if(!options.lazy) {
  164. var watching = compiler.watch(options.watchOptions, share.handleCompilerCallback);
  165. context.watching = watching;
  166. } else {
  167. context.state = true;
  168. }
  169. },
  170. rebuild: function rebuild() {
  171. if(context.state) {
  172. context.state = false;
  173. context.compiler.run(share.handleCompilerCallback);
  174. } else {
  175. context.forceRebuild = true;
  176. }
  177. },
  178. handleCompilerCallback: function(err) {
  179. if(err) {
  180. context.options.error(err.stack || err);
  181. if(err.details) context.options.error(err.details);
  182. }
  183. },
  184. handleRequest: function(filename, processRequest, req) {
  185. // in lazy mode, rebuild on bundle request
  186. if(context.options.lazy && (!context.options.filename || context.options.filename.test(filename)))
  187. share.rebuild();
  188. if(HASH_REGEXP.test(filename)) {
  189. try {
  190. if(context.fs.statSync(filename).isFile()) {
  191. processRequest();
  192. return;
  193. }
  194. } catch(e) {
  195. }
  196. }
  197. share.ready(processRequest, req);
  198. },
  199. waitUntilValid: function(callback) {
  200. callback = callback || function() {};
  201. share.ready(callback, {});
  202. },
  203. invalidate: function(callback) {
  204. callback = callback || function() {};
  205. if(context.watching) {
  206. share.ready(callback, {});
  207. context.watching.invalidate();
  208. } else {
  209. callback();
  210. }
  211. },
  212. close: function(callback) {
  213. callback = callback || function() {};
  214. if(context.watching) context.watching.close(callback);
  215. else callback();
  216. }
  217. };
  218. share.setOptions(context.options);
  219. share.setFs(context.compiler);
  220. context.compiler.plugin("done", share.compilerDone);
  221. context.compiler.plugin("invalid", share.compilerInvalid);
  222. context.compiler.plugin("watch-run", share.compilerInvalid);
  223. context.compiler.plugin("run", share.compilerInvalid);
  224. share.startWatch();
  225. return share;
  226. };