getFilenameFromUrl.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. "use strict";
  2. const path = require("path");
  3. const {
  4. parse
  5. } = require("url");
  6. const querystring = require("querystring");
  7. const getPaths = require("./getPaths");
  8. /** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
  9. /** @typedef {import("../index.js").ServerResponse} ServerResponse */
  10. const cacheStore = new WeakMap();
  11. /**
  12. * @param {Function} fn
  13. * @param {{ cache?: Map<any, any> }} [cache]
  14. * @returns {any}
  15. */
  16. const mem = (fn, {
  17. cache = new Map()
  18. } = {}) => {
  19. /**
  20. * @param {any} arguments_
  21. * @return {any}
  22. */
  23. const memoized = (...arguments_) => {
  24. const [key] = arguments_;
  25. const cacheItem = cache.get(key);
  26. if (cacheItem) {
  27. return cacheItem.data;
  28. }
  29. const result = fn.apply(void 0, arguments_);
  30. cache.set(key, {
  31. data: result
  32. });
  33. return result;
  34. };
  35. cacheStore.set(memoized, cache);
  36. return memoized;
  37. };
  38. const memoizedParse = mem(parse);
  39. /**
  40. * @template {IncomingMessage} Request
  41. * @template {ServerResponse} Response
  42. * @param {import("../index.js").Context<Request, Response>} context
  43. * @param {string} url
  44. * @returns {string | undefined}
  45. */
  46. function getFilenameFromUrl(context, url) {
  47. const {
  48. options
  49. } = context;
  50. const paths = getPaths(context);
  51. let foundFilename;
  52. let urlObject;
  53. try {
  54. // The `url` property of the `request` is contains only `pathname`, `search` and `hash`
  55. urlObject = memoizedParse(url, false, true);
  56. } catch (_ignoreError) {
  57. return;
  58. }
  59. for (const {
  60. publicPath,
  61. outputPath
  62. } of paths) {
  63. let filename;
  64. let publicPathObject;
  65. try {
  66. publicPathObject = memoizedParse(publicPath !== "auto" && publicPath ? publicPath : "/", false, true);
  67. } catch (_ignoreError) {
  68. // eslint-disable-next-line no-continue
  69. continue;
  70. }
  71. if (urlObject.pathname && urlObject.pathname.startsWith(publicPathObject.pathname)) {
  72. filename = outputPath; // Strip the `pathname` property from the `publicPath` option from the start of requested url
  73. // `/complex/foo.js` => `foo.js`
  74. const pathname = urlObject.pathname.slice(publicPathObject.pathname.length);
  75. if (pathname) {
  76. filename = path.join(outputPath, querystring.unescape(pathname));
  77. }
  78. let fsStats;
  79. try {
  80. fsStats =
  81. /** @type {import("fs").statSync} */
  82. context.outputFileSystem.statSync(filename);
  83. } catch (_ignoreError) {
  84. // eslint-disable-next-line no-continue
  85. continue;
  86. }
  87. if (fsStats.isFile()) {
  88. foundFilename = filename;
  89. break;
  90. } else if (fsStats.isDirectory() && (typeof options.index === "undefined" || options.index)) {
  91. const indexValue = typeof options.index === "undefined" || typeof options.index === "boolean" ? "index.html" : options.index;
  92. filename = path.join(filename, indexValue);
  93. try {
  94. fsStats =
  95. /** @type {import("fs").statSync} */
  96. context.outputFileSystem.statSync(filename);
  97. } catch (__ignoreError) {
  98. // eslint-disable-next-line no-continue
  99. continue;
  100. }
  101. if (fsStats.isFile()) {
  102. foundFilename = filename;
  103. break;
  104. }
  105. }
  106. }
  107. } // eslint-disable-next-line consistent-return
  108. return foundFilename;
  109. }
  110. module.exports = getFilenameFromUrl;