You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

188 lines
5.0 KiB

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