AutoPublicPathRuntimeModule.js 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const RuntimeGlobals = require("../RuntimeGlobals");
  6. const RuntimeModule = require("../RuntimeModule");
  7. const Template = require("../Template");
  8. /** @import Chunk from "../Chunk" */
  9. /** @import ChunkGraph from "../ChunkGraph" */
  10. /** @import Compilation from "../Compilation" */
  11. class AutoPublicPathRuntimeModule extends RuntimeModule {
  12. constructor() {
  13. super("publicPath", RuntimeModule.STAGE_BASIC);
  14. }
  15. /**
  16. * Generates runtime code for this runtime module.
  17. * @returns {string | null} runtime code
  18. */
  19. generate() {
  20. const compilation = /** @type {Compilation} */ (this.compilation);
  21. // The virtual build-time chunk has no script url and sits at the output
  22. // root, so urls resolve against the `importModule` baseUri.
  23. if (/** @type {ChunkGraph} */ (this.chunkGraph).buildTimeExecution) {
  24. return `${RuntimeGlobals.publicPath} = "";`;
  25. }
  26. const { scriptType, importMetaName, environment } =
  27. compilation.outputOptions;
  28. const chunk = /** @type {Chunk} */ (this.chunk);
  29. const undoPath = compilation.runtimeTemplate.chunkRootOutputDir(
  30. chunk,
  31. false
  32. );
  33. const global = environment.globalThis
  34. ? "globalThis"
  35. : RuntimeGlobals.global;
  36. const entryOptions = chunk.getEntryOptions();
  37. // A worklet chunk is always loaded as a module via `addModule`, so it can
  38. // read `import.meta.url` directly instead of the worker-scope detection.
  39. const fromImportMeta =
  40. scriptType === "module" || Boolean(entryOptions && entryOptions.worklet);
  41. const runtimeTemplate = compilation.runtimeTemplate;
  42. const cst = runtimeTemplate.renderConst();
  43. const lt = runtimeTemplate.renderLet();
  44. return Template.asString([
  45. `${lt} scriptUrl;`,
  46. fromImportMeta
  47. ? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
  48. : Template.asString([
  49. `if (${global}.importScripts) scriptUrl = ${global}.location + "";`,
  50. `${cst} document = ${global}.document;`,
  51. "if (!scriptUrl && document) {",
  52. Template.indent([
  53. // Technically we could use `document.currentScript instanceof window.HTMLScriptElement`,
  54. // but an attacker could try to inject `<script>HTMLScriptElement = HTMLImageElement</script>`
  55. // and use `<img name="currentScript" src="https://attacker.controlled.server/"></img>`
  56. `if (${runtimeTemplate.optionalChaining(
  57. "document.currentScript",
  58. "tagName.toUpperCase() === 'SCRIPT'"
  59. )})`,
  60. Template.indent("scriptUrl = document.currentScript.src;"),
  61. "if (!scriptUrl) {",
  62. Template.indent([
  63. `${cst} scripts = document.getElementsByTagName("script");`,
  64. "if(scripts.length) {",
  65. Template.indent([
  66. `${lt} i = scripts.length - 1;`,
  67. "while (i > -1 && (!scriptUrl || !/^https?:/.test(scriptUrl))) scriptUrl = scripts[i--].src;"
  68. ]),
  69. "}"
  70. ]),
  71. "}"
  72. ]),
  73. "}"
  74. ]),
  75. "// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
  76. '// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
  77. 'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
  78. // One pass for the `blob:` prefix and the query/fragment: both stop at the
  79. // first `?` or `#`, so stripping either first leaves the same url.
  80. 'scriptUrl = scriptUrl.replace(/^blob:|[?#].*$/g, "").replace(/\\/[^/]+$/, "/");',
  81. !undoPath
  82. ? `${RuntimeGlobals.publicPath} = scriptUrl;`
  83. : `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
  84. undoPath
  85. )};`
  86. ]);
  87. }
  88. }
  89. module.exports = AutoPublicPathRuntimeModule;