util.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. 'use strict';
  2. const { parse } = require('url');
  3. const querystring = require('querystring');
  4. const pathabs = require('path-is-absolute');
  5. const parseRange = require('range-parser');
  6. const urlJoin = require('url-join');
  7. const HASH_REGEXP = /[0-9a-f]{10,}/;
  8. // support for multi-compiler configuration
  9. // see: https://github.com/webpack/webpack-dev-server/issues/641
  10. function getPaths(publicPath, compiler, url) {
  11. const compilers = compiler && compiler.compilers;
  12. if (Array.isArray(compilers)) {
  13. let compilerPublicPath;
  14. // the path portion of compilerPublicPath
  15. let compilerPublicPathBase;
  16. for (let i = 0; i < compilers.length; i++) {
  17. compilerPublicPath = compilers[i].options
  18. && compilers[i].options.output
  19. && compilers[i].options.output.publicPath;
  20. if (compilerPublicPath) {
  21. if (compilerPublicPath.indexOf('/') === 0) {
  22. compilerPublicPathBase = compilerPublicPath;
  23. } else {
  24. // handle the case where compilerPublicPath is a URL with hostname
  25. compilerPublicPathBase = parse(compilerPublicPath).pathname;
  26. }
  27. // check the url vs the path part of the compilerPublicPath
  28. if (url.indexOf(compilerPublicPathBase) === 0) {
  29. return {
  30. publicPath: compilerPublicPath,
  31. outputPath: compilers[i].outputPath
  32. };
  33. }
  34. }
  35. }
  36. }
  37. return {
  38. publicPath,
  39. outputPath: compiler.outputPath
  40. };
  41. }
  42. function ready(context, fn, req) {
  43. if (context.state) {
  44. return fn(context.webpackStats);
  45. }
  46. context.log.info(`wait until bundle finished: ${req.url || fn.name}`);
  47. context.callbacks.push(fn);
  48. }
  49. module.exports = {
  50. getFilenameFromUrl(pubPath, compiler, url) {
  51. const { outputPath, publicPath } = getPaths(pubPath, compiler, url);
  52. // localPrefix is the folder our bundle should be in
  53. const localPrefix = parse(publicPath || '/', false, true);
  54. const urlObject = parse(url);
  55. let filename;
  56. // publicPath has the hostname that is not the same as request url's, should fail
  57. if (localPrefix.hostname !== null && urlObject.hostname !== null &&
  58. localPrefix.hostname !== urlObject.hostname) {
  59. return false;
  60. }
  61. // publicPath is not in url, so it should fail
  62. if (publicPath && localPrefix.hostname === urlObject.hostname &&
  63. url.indexOf(publicPath) !== 0) {
  64. return false;
  65. }
  66. // strip localPrefix from the start of url
  67. if (urlObject.pathname.indexOf(localPrefix.pathname) === 0) {
  68. filename = urlObject.pathname.substr(localPrefix.pathname.length);
  69. }
  70. if (!urlObject.hostname && localPrefix.hostname &&
  71. url.indexOf(localPrefix.path) !== 0) {
  72. return false;
  73. }
  74. let uri = outputPath;
  75. /* istanbul ignore if */
  76. if (process.platform === 'win32') {
  77. // Path Handling for Microsoft Windows
  78. if (filename) {
  79. uri = urlJoin((outputPath || ''), querystring.unescape(filename));
  80. if (!pathabs.win32(uri)) {
  81. uri = `/${uri}`;
  82. }
  83. }
  84. return uri;
  85. }
  86. // Path Handling for all other operating systems
  87. if (filename) {
  88. uri = urlJoin((outputPath || ''), filename);
  89. if (!pathabs.posix(uri)) {
  90. uri = `/${uri}`;
  91. }
  92. }
  93. // if no matches, use outputPath as filename
  94. return querystring.unescape(uri);
  95. },
  96. handleRangeHeaders(content, req, res) {
  97. // assumes express API. For other servers, need to add logic to access
  98. // alternative header APIs
  99. res.setHeader('Accept-Ranges', 'bytes');
  100. if (req.headers.range) {
  101. const ranges = parseRange(content.length, req.headers.range);
  102. // unsatisfiable
  103. if (ranges === -1) {
  104. res.setHeader('Content-Range', `bytes */${content.length}`);
  105. res.statusCode = 416;
  106. }
  107. // valid (syntactically invalid/multiple ranges are treated as a
  108. // regular response)
  109. if (ranges !== -2 && ranges.length === 1) {
  110. const { length } = content;
  111. // Content-Range
  112. res.statusCode = 206;
  113. res.setHeader(
  114. 'Content-Range',
  115. `bytes ${ranges[0].start}-${ranges[0].end}/${length}`
  116. );
  117. content = content.slice(ranges[0].start, ranges[0].end + 1);
  118. }
  119. }
  120. return content;
  121. },
  122. handleRequest(context, filename, processRequest, req) {
  123. // in lazy mode, rebuild on bundle request
  124. if (context.options.lazy && (!context.options.filename || context.options.filename.test(filename))) {
  125. context.rebuild();
  126. }
  127. if (HASH_REGEXP.test(filename)) {
  128. try {
  129. if (context.fs.statSync(filename).isFile()) {
  130. processRequest();
  131. return;
  132. }
  133. } catch (e) {
  134. // eslint-disable-line
  135. }
  136. }
  137. ready(context, processRequest, req);
  138. },
  139. noop: () => {},
  140. ready
  141. };