getFilenameFromUrl.js 3.3 KB

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