index.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. 'use strict';
  2. /* global __resourceQuery WorkerGlobalScope self */
  3. /* eslint prefer-destructuring: off */
  4. var querystring = require('querystring');
  5. var url = require('url');
  6. var stripAnsi = require('strip-ansi');
  7. var log = require('loglevel').getLogger('webpack-dev-server');
  8. var socket = require('./socket');
  9. var overlay = require('./overlay');
  10. function getCurrentScriptSource() {
  11. // `document.currentScript` is the most accurate way to find the current script,
  12. // but is not supported in all browsers.
  13. if (document.currentScript) {
  14. return document.currentScript.getAttribute('src');
  15. } // Fall back to getting all scripts in the document.
  16. var scriptElements = document.scripts || [];
  17. var currentScript = scriptElements[scriptElements.length - 1];
  18. if (currentScript) {
  19. return currentScript.getAttribute('src');
  20. } // Fail as there was no script to use.
  21. throw new Error('[WDS] Failed to get current script source.');
  22. }
  23. var urlParts;
  24. var hotReload = true;
  25. if (typeof window !== 'undefined') {
  26. var qs = window.location.search.toLowerCase();
  27. hotReload = qs.indexOf('hotreload=false') === -1;
  28. }
  29. if (typeof __resourceQuery === 'string' && __resourceQuery) {
  30. // If this bundle is inlined, use the resource query to get the correct url.
  31. urlParts = url.parse(__resourceQuery.substr(1));
  32. } else {
  33. // Else, get the url from the <script> this file was called with.
  34. var scriptHost = getCurrentScriptSource(); // eslint-disable-next-line no-useless-escape
  35. scriptHost = scriptHost.replace(/\/[^\/]+$/, '');
  36. urlParts = url.parse(scriptHost || '/', false, true);
  37. }
  38. if (!urlParts.port || urlParts.port === '0') {
  39. urlParts.port = self.location.port;
  40. }
  41. var _hot = false;
  42. var initial = true;
  43. var currentHash = '';
  44. var useWarningOverlay = false;
  45. var useErrorOverlay = false;
  46. var useProgress = false;
  47. var INFO = 'info';
  48. var WARNING = 'warning';
  49. var ERROR = 'error';
  50. var NONE = 'none'; // Set the default log level
  51. log.setDefaultLevel(INFO); // Send messages to the outside, so plugins can consume it.
  52. function sendMsg(type, data) {
  53. if (typeof self !== 'undefined' && (typeof WorkerGlobalScope === 'undefined' || !(self instanceof WorkerGlobalScope))) {
  54. self.postMessage({
  55. type: "webpack".concat(type),
  56. data: data
  57. }, '*');
  58. }
  59. }
  60. var onSocketMsg = {
  61. hot: function hot() {
  62. _hot = true;
  63. log.info('[WDS] Hot Module Replacement enabled.');
  64. },
  65. invalid: function invalid() {
  66. log.info('[WDS] App updated. Recompiling...'); // fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
  67. if (useWarningOverlay || useErrorOverlay) {
  68. overlay.clear();
  69. }
  70. sendMsg('Invalid');
  71. },
  72. hash: function hash(_hash) {
  73. currentHash = _hash;
  74. },
  75. 'still-ok': function stillOk() {
  76. log.info('[WDS] Nothing changed.');
  77. if (useWarningOverlay || useErrorOverlay) {
  78. overlay.clear();
  79. }
  80. sendMsg('StillOk');
  81. },
  82. 'log-level': function logLevel(level) {
  83. var hotCtx = require.context('webpack/hot', false, /^\.\/log$/);
  84. if (hotCtx.keys().indexOf('./log') !== -1) {
  85. hotCtx('./log').setLogLevel(level);
  86. }
  87. switch (level) {
  88. case INFO:
  89. case ERROR:
  90. log.setLevel(level);
  91. break;
  92. case WARNING:
  93. // loglevel's warning name is different from webpack's
  94. log.setLevel('warn');
  95. break;
  96. case NONE:
  97. log.disableAll();
  98. break;
  99. default:
  100. log.error("[WDS] Unknown clientLogLevel '".concat(level, "'"));
  101. }
  102. },
  103. overlay: function overlay(value) {
  104. if (typeof document !== 'undefined') {
  105. if (typeof value === 'boolean') {
  106. useWarningOverlay = false;
  107. useErrorOverlay = value;
  108. } else if (value) {
  109. useWarningOverlay = value.warnings;
  110. useErrorOverlay = value.errors;
  111. }
  112. }
  113. },
  114. progress: function progress(_progress) {
  115. if (typeof document !== 'undefined') {
  116. useProgress = _progress;
  117. }
  118. },
  119. 'progress-update': function progressUpdate(data) {
  120. if (useProgress) {
  121. log.info("[WDS] ".concat(data.percent, "% - ").concat(data.msg, "."));
  122. }
  123. sendMsg('Progress', data);
  124. },
  125. ok: function ok() {
  126. sendMsg('Ok');
  127. if (useWarningOverlay || useErrorOverlay) {
  128. overlay.clear();
  129. }
  130. if (initial) {
  131. return initial = false;
  132. } // eslint-disable-line no-return-assign
  133. reloadApp();
  134. },
  135. 'content-changed': function contentChanged() {
  136. log.info('[WDS] Content base changed. Reloading...');
  137. self.location.reload();
  138. },
  139. warnings: function warnings(_warnings) {
  140. log.warn('[WDS] Warnings while compiling.');
  141. var strippedWarnings = _warnings.map(function (warning) {
  142. return stripAnsi(warning);
  143. });
  144. sendMsg('Warnings', strippedWarnings);
  145. for (var i = 0; i < strippedWarnings.length; i++) {
  146. log.warn(strippedWarnings[i]);
  147. }
  148. if (useWarningOverlay) {
  149. overlay.showMessage(_warnings);
  150. }
  151. if (initial) {
  152. return initial = false;
  153. } // eslint-disable-line no-return-assign
  154. reloadApp();
  155. },
  156. errors: function errors(_errors) {
  157. log.error('[WDS] Errors while compiling. Reload prevented.');
  158. var strippedErrors = _errors.map(function (error) {
  159. return stripAnsi(error);
  160. });
  161. sendMsg('Errors', strippedErrors);
  162. for (var i = 0; i < strippedErrors.length; i++) {
  163. log.error(strippedErrors[i]);
  164. }
  165. if (useErrorOverlay) {
  166. overlay.showMessage(_errors);
  167. }
  168. initial = false;
  169. },
  170. error: function error(_error) {
  171. log.error(_error);
  172. },
  173. close: function close() {
  174. log.error('[WDS] Disconnected!');
  175. sendMsg('Close');
  176. }
  177. };
  178. var hostname = urlParts.hostname;
  179. var protocol = urlParts.protocol;
  180. var port = urlParts.port; // check ipv4 and ipv6 `all hostname`
  181. if (hostname === '0.0.0.0' || hostname === '::') {
  182. // why do we need this check?
  183. // hostname n/a for file protocol (example, when using electron, ionic)
  184. // see: https://github.com/webpack/webpack-dev-server/pull/384
  185. // eslint-disable-next-line no-bitwise
  186. if (self.location.hostname && !!~self.location.protocol.indexOf('http')) {
  187. hostname = self.location.hostname;
  188. port = self.location.port;
  189. }
  190. } // `hostname` can be empty when the script path is relative. In that case, specifying
  191. // a protocol would result in an invalid URL.
  192. // When https is used in the app, secure websockets are always necessary
  193. // because the browser doesn't accept non-secure websockets.
  194. if (hostname && (self.location.protocol === 'https:' || urlParts.hostname === '0.0.0.0')) {
  195. protocol = self.location.protocol;
  196. }
  197. var socketUrl = url.format({
  198. protocol: protocol,
  199. auth: urlParts.auth,
  200. hostname: hostname,
  201. port: port,
  202. // If sockPath is provided it'll be passed in via the __resourceQuery as a
  203. // query param so it has to be parsed out of the querystring in order for the
  204. // client to open the socket to the correct location.
  205. pathname: urlParts.path == null || urlParts.path === '/' ? '/sockjs-node' : querystring.parse(urlParts.path).sockPath || urlParts.path
  206. });
  207. socket(socketUrl, onSocketMsg);
  208. var isUnloading = false;
  209. self.addEventListener('beforeunload', function () {
  210. isUnloading = true;
  211. });
  212. function reloadApp() {
  213. if (isUnloading || !hotReload) {
  214. return;
  215. }
  216. if (_hot) {
  217. log.info('[WDS] App hot update...'); // eslint-disable-next-line global-require
  218. var hotEmitter = require('webpack/hot/emitter');
  219. hotEmitter.emit('webpackHotUpdate', currentHash);
  220. if (typeof self !== 'undefined' && self.window) {
  221. // broadcast update to window
  222. self.postMessage("webpackHotUpdate".concat(currentHash), '*');
  223. }
  224. } else {
  225. var rootWindow = self; // use parent window for reload (in case we're in an iframe with no valid src)
  226. var intervalId = self.setInterval(function () {
  227. if (rootWindow.location.protocol !== 'about:') {
  228. // reload immediately if protocol is valid
  229. applyReload(rootWindow, intervalId);
  230. } else {
  231. rootWindow = rootWindow.parent;
  232. if (rootWindow.parent === rootWindow) {
  233. // if parent equals current window we've reached the root which would continue forever, so trigger a reload anyways
  234. applyReload(rootWindow, intervalId);
  235. }
  236. }
  237. });
  238. }
  239. function applyReload(rootWindow, intervalId) {
  240. clearInterval(intervalId);
  241. log.info('[WDS] App updated. Reloading...');
  242. rootWindow.location.reload();
  243. }
  244. }