AutoPublicPathRuntimeModule.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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. const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
  9. const { getUndoPath } = require("../util/identifier");
  10. /** @typedef {import("../Chunk")} Chunk */
  11. /** @typedef {import("../Compilation")} Compilation */
  12. class AutoPublicPathRuntimeModule extends RuntimeModule {
  13. constructor() {
  14. super("publicPath", RuntimeModule.STAGE_BASIC);
  15. }
  16. /**
  17. * @returns {string | null} runtime code
  18. */
  19. generate() {
  20. const compilation = /** @type {Compilation} */ (this.compilation);
  21. const { scriptType, importMetaName, path } = compilation.outputOptions;
  22. const chunkName = compilation.getPath(
  23. JavascriptModulesPlugin.getChunkFilenameTemplate(
  24. /** @type {Chunk} */
  25. (this.chunk),
  26. compilation.outputOptions
  27. ),
  28. {
  29. chunk: this.chunk,
  30. contentHashType: "javascript"
  31. }
  32. );
  33. const undoPath = getUndoPath(
  34. chunkName,
  35. /** @type {string} */ (path),
  36. false
  37. );
  38. return Template.asString([
  39. "var scriptUrl;",
  40. scriptType === "module"
  41. ? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
  42. : Template.asString([
  43. `if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
  44. `var document = ${RuntimeGlobals.global}.document;`,
  45. "if (!scriptUrl && document) {",
  46. Template.indent([
  47. // Technically we could use `document.currentScript instanceof window.HTMLScriptElement`,
  48. // but an attacker could try to inject `<script>HTMLScriptElement = HTMLImageElement</script>`
  49. // and use `<img name="currentScript" src="https://attacker.controlled.server/"></img>`
  50. "if (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')",
  51. Template.indent("scriptUrl = document.currentScript.src;"),
  52. "if (!scriptUrl) {",
  53. Template.indent([
  54. 'var scripts = document.getElementsByTagName("script");',
  55. "if(scripts.length) {",
  56. Template.indent([
  57. "var i = scripts.length - 1;",
  58. "while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;"
  59. ]),
  60. "}"
  61. ]),
  62. "}"
  63. ]),
  64. "}"
  65. ]),
  66. "// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
  67. '// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
  68. 'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
  69. 'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");',
  70. !undoPath
  71. ? `${RuntimeGlobals.publicPath} = scriptUrl;`
  72. : `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
  73. undoPath
  74. )};`
  75. ]);
  76. }
  77. }
  78. module.exports = AutoPublicPathRuntimeModule;