client.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*eslint-env browser*/
  2. /*global __resourceQuery __webpack_public_path__*/
  3. var options = {
  4. path: '/__webpack_hmr',
  5. timeout: 20 * 1000,
  6. overlay: true,
  7. reload: false,
  8. log: true,
  9. warn: true,
  10. name: '',
  11. autoConnect: true,
  12. overlayStyles: {},
  13. overlayWarnings: false,
  14. ansiColors: {},
  15. };
  16. if (__resourceQuery) {
  17. var params = Array.from(new URLSearchParams(__resourceQuery.slice(1)));
  18. var overrides = params.reduce(function (memo, param) {
  19. memo[param[0]] = param[1];
  20. return memo;
  21. }, {});
  22. setOverrides(overrides);
  23. }
  24. if (typeof window === 'undefined') {
  25. // do nothing
  26. } else if (typeof window.EventSource === 'undefined') {
  27. console.warn(
  28. "webpack-hot-middleware's client requires EventSource to work. " +
  29. 'You should include a polyfill if you want to support this browser: ' +
  30. 'https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools'
  31. );
  32. } else {
  33. if (options.autoConnect) {
  34. connect();
  35. }
  36. }
  37. /* istanbul ignore next */
  38. function setOptionsAndConnect(overrides) {
  39. setOverrides(overrides);
  40. connect();
  41. }
  42. function setOverrides(overrides) {
  43. if (overrides.autoConnect)
  44. options.autoConnect = overrides.autoConnect == 'true';
  45. if (overrides.path) options.path = overrides.path;
  46. if (overrides.timeout) options.timeout = overrides.timeout;
  47. if (overrides.overlay) options.overlay = overrides.overlay !== 'false';
  48. if (overrides.reload) options.reload = overrides.reload !== 'false';
  49. if (overrides.noInfo && overrides.noInfo !== 'false') {
  50. options.log = false;
  51. }
  52. if (overrides.name) {
  53. options.name = overrides.name;
  54. }
  55. if (overrides.quiet && overrides.quiet !== 'false') {
  56. options.log = false;
  57. options.warn = false;
  58. }
  59. if (overrides.dynamicPublicPath) {
  60. options.path = __webpack_public_path__ + options.path;
  61. }
  62. if (overrides.ansiColors)
  63. options.ansiColors = JSON.parse(overrides.ansiColors);
  64. if (overrides.overlayStyles)
  65. options.overlayStyles = JSON.parse(overrides.overlayStyles);
  66. if (overrides.overlayWarnings) {
  67. options.overlayWarnings = overrides.overlayWarnings == 'true';
  68. }
  69. }
  70. function EventSourceWrapper() {
  71. var source;
  72. var lastActivity = new Date();
  73. var listeners = [];
  74. init();
  75. var timer = setInterval(function () {
  76. if (new Date() - lastActivity > options.timeout) {
  77. handleDisconnect();
  78. }
  79. }, options.timeout / 2);
  80. function init() {
  81. source = new window.EventSource(options.path);
  82. source.onopen = handleOnline;
  83. source.onerror = handleDisconnect;
  84. source.onmessage = handleMessage;
  85. }
  86. function handleOnline() {
  87. if (options.log) console.log('[HMR] connected');
  88. lastActivity = new Date();
  89. }
  90. function handleMessage(event) {
  91. lastActivity = new Date();
  92. for (var i = 0; i < listeners.length; i++) {
  93. listeners[i](event);
  94. }
  95. }
  96. function handleDisconnect() {
  97. clearInterval(timer);
  98. source.close();
  99. setTimeout(init, options.timeout);
  100. }
  101. return {
  102. addMessageListener: function (fn) {
  103. listeners.push(fn);
  104. },
  105. };
  106. }
  107. function getEventSourceWrapper() {
  108. if (!window.__whmEventSourceWrapper) {
  109. window.__whmEventSourceWrapper = {};
  110. }
  111. if (!window.__whmEventSourceWrapper[options.path]) {
  112. // cache the wrapper for other entries loaded on
  113. // the same page with the same options.path
  114. window.__whmEventSourceWrapper[options.path] = EventSourceWrapper();
  115. }
  116. return window.__whmEventSourceWrapper[options.path];
  117. }
  118. function connect() {
  119. getEventSourceWrapper().addMessageListener(handleMessage);
  120. function handleMessage(event) {
  121. if (event.data == '\uD83D\uDC93') {
  122. return;
  123. }
  124. try {
  125. processMessage(JSON.parse(event.data));
  126. } catch (ex) {
  127. if (options.warn) {
  128. console.warn('Invalid HMR message: ' + event.data + '\n' + ex);
  129. }
  130. }
  131. }
  132. }
  133. // the reporter needs to be a singleton on the page
  134. // in case the client is being used by multiple bundles
  135. // we only want to report once.
  136. // all the errors will go to all clients
  137. var singletonKey = '__webpack_hot_middleware_reporter__';
  138. var reporter;
  139. if (typeof window !== 'undefined') {
  140. if (!window[singletonKey]) {
  141. window[singletonKey] = createReporter();
  142. }
  143. reporter = window[singletonKey];
  144. }
  145. function createReporter() {
  146. var strip = require('strip-ansi');
  147. var overlay;
  148. if (typeof document !== 'undefined' && options.overlay) {
  149. overlay = require('./client-overlay')({
  150. ansiColors: options.ansiColors,
  151. overlayStyles: options.overlayStyles,
  152. });
  153. }
  154. var styles = {
  155. errors: 'color: #ff0000;',
  156. warnings: 'color: #999933;',
  157. };
  158. var previousProblems = null;
  159. function log(type, obj) {
  160. var newProblems = obj[type]
  161. .map(function (msg) {
  162. return strip(msg);
  163. })
  164. .join('\n');
  165. if (previousProblems == newProblems) {
  166. return;
  167. } else {
  168. previousProblems = newProblems;
  169. }
  170. var style = styles[type];
  171. var name = obj.name ? "'" + obj.name + "' " : '';
  172. var title = '[HMR] bundle ' + name + 'has ' + obj[type].length + ' ' + type;
  173. // NOTE: console.warn or console.error will print the stack trace
  174. // which isn't helpful here, so using console.log to escape it.
  175. if (console.group && console.groupEnd) {
  176. console.group('%c' + title, style);
  177. console.log('%c' + newProblems, style);
  178. console.groupEnd();
  179. } else {
  180. console.log(
  181. '%c' + title + '\n\t%c' + newProblems.replace(/\n/g, '\n\t'),
  182. style + 'font-weight: bold;',
  183. style + 'font-weight: normal;'
  184. );
  185. }
  186. }
  187. return {
  188. cleanProblemsCache: function () {
  189. previousProblems = null;
  190. },
  191. problems: function (type, obj) {
  192. if (options.warn) {
  193. log(type, obj);
  194. }
  195. if (overlay) {
  196. if (options.overlayWarnings || type === 'errors') {
  197. overlay.showProblems(type, obj[type]);
  198. return false;
  199. }
  200. overlay.clear();
  201. }
  202. return true;
  203. },
  204. success: function () {
  205. if (overlay) overlay.clear();
  206. },
  207. useCustomOverlay: function (customOverlay) {
  208. overlay = customOverlay;
  209. },
  210. };
  211. }
  212. var processUpdate = require('./process-update');
  213. var customHandler;
  214. var subscribeAllHandler;
  215. function processMessage(obj) {
  216. switch (obj.action) {
  217. case 'building':
  218. if (options.log) {
  219. console.log(
  220. '[HMR] bundle ' +
  221. (obj.name ? "'" + obj.name + "' " : '') +
  222. 'rebuilding'
  223. );
  224. }
  225. break;
  226. case 'built':
  227. if (options.log) {
  228. console.log(
  229. '[HMR] bundle ' +
  230. (obj.name ? "'" + obj.name + "' " : '') +
  231. 'rebuilt in ' +
  232. obj.time +
  233. 'ms'
  234. );
  235. }
  236. // fall through
  237. case 'sync':
  238. if (obj.name && options.name && obj.name !== options.name) {
  239. return;
  240. }
  241. var applyUpdate = true;
  242. if (obj.errors.length > 0) {
  243. if (reporter) reporter.problems('errors', obj);
  244. applyUpdate = false;
  245. } else if (obj.warnings.length > 0) {
  246. if (reporter) {
  247. var overlayShown = reporter.problems('warnings', obj);
  248. applyUpdate = overlayShown;
  249. }
  250. } else {
  251. if (reporter) {
  252. reporter.cleanProblemsCache();
  253. reporter.success();
  254. }
  255. }
  256. if (applyUpdate) {
  257. processUpdate(obj.hash, obj.modules, options);
  258. }
  259. break;
  260. default:
  261. if (customHandler) {
  262. customHandler(obj);
  263. }
  264. }
  265. if (subscribeAllHandler) {
  266. subscribeAllHandler(obj);
  267. }
  268. }
  269. if (module) {
  270. module.exports = {
  271. subscribeAll: function subscribeAll(handler) {
  272. subscribeAllHandler = handler;
  273. },
  274. subscribe: function subscribe(handler) {
  275. customHandler = handler;
  276. },
  277. useCustomOverlay: function useCustomOverlay(customOverlay) {
  278. if (reporter) reporter.useCustomOverlay(customOverlay);
  279. },
  280. setOptionsAndConnect: setOptionsAndConnect,
  281. };
  282. }