LoadScriptRuntimeModule.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { SyncWaterfallHook } = require("tapable");
  6. /** @import Compilation from "../Compilation" */
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const Template = require("../Template");
  9. const createHooksRegistry = require("../util/createHooksRegistry");
  10. const HelperRuntimeModule = require("./HelperRuntimeModule");
  11. /** @import Chunk from "../Chunk" */
  12. /**
  13. * @typedef {object} LoadScriptCompilationHooks
  14. * @property {SyncWaterfallHook<[string, Chunk]>} createScript
  15. */
  16. class LoadScriptRuntimeModule extends HelperRuntimeModule {
  17. /**
  18. * @param {boolean=} withCreateScriptUrl use create script url for trusted types
  19. * @param {boolean=} withFetchPriority use `fetchPriority` attribute
  20. */
  21. constructor(withCreateScriptUrl, withFetchPriority) {
  22. super("load script");
  23. /** @type {boolean | undefined} */
  24. this._withCreateScriptUrl = withCreateScriptUrl;
  25. /** @type {boolean | undefined} */
  26. this._withFetchPriority = withFetchPriority;
  27. }
  28. /**
  29. * Generates runtime code for this runtime module.
  30. * @returns {string | null} runtime code
  31. */
  32. generate() {
  33. const compilation = /** @type {Compilation} */ (this.compilation);
  34. const { runtimeTemplate, outputOptions } = compilation;
  35. const {
  36. scriptType,
  37. chunkLoadTimeout: loadTimeout,
  38. crossOriginLoading,
  39. uniqueName,
  40. charset
  41. } = outputOptions;
  42. const fn = RuntimeGlobals.loadScript;
  43. const { createScript } =
  44. LoadScriptRuntimeModule.getCompilationHooks(compilation);
  45. const code = Template.asString([
  46. "script = document.createElement('script');",
  47. scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "",
  48. charset ? "script.charset = 'utf-8';" : "",
  49. `if (${RuntimeGlobals.scriptNonce}) {`,
  50. Template.indent(
  51. `script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  52. ),
  53. "}",
  54. uniqueName
  55. ? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
  56. : "",
  57. this._withFetchPriority
  58. ? Template.asString([
  59. "if(fetchPriority) {",
  60. Template.indent(
  61. 'script.setAttribute("fetchpriority", fetchPriority);'
  62. ),
  63. "}"
  64. ])
  65. : "",
  66. `script.src = ${
  67. this._withCreateScriptUrl
  68. ? `${RuntimeGlobals.createScriptUrl}(url)`
  69. : "url"
  70. };`,
  71. crossOriginLoading
  72. ? crossOriginLoading === "use-credentials"
  73. ? 'script.crossOrigin = "use-credentials";'
  74. : Template.asString([
  75. "if (script.src.indexOf(window.location.origin + '/') !== 0) {",
  76. Template.indent(
  77. `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
  78. ),
  79. "}"
  80. ])
  81. : ""
  82. ]);
  83. const cst = runtimeTemplate.renderConst();
  84. const lt = runtimeTemplate.renderLet();
  85. return Template.asString([
  86. `${cst} inProgress = {};`,
  87. uniqueName
  88. ? `${cst} dataWebpackPrefix = ${JSON.stringify(`${uniqueName}:`)};`
  89. : "// data-webpack is not used as build has no uniqueName",
  90. "// loadScript function to load a script via script tag",
  91. `${fn} = ${runtimeTemplate.basicFunction(
  92. `url, done, key, chunkId${
  93. this._withFetchPriority ? ", fetchPriority" : ""
  94. }`,
  95. [
  96. "if(inProgress[url]) { inProgress[url].push(done); return; }",
  97. `${lt} script, needAttach;`,
  98. "if(key !== undefined) {",
  99. Template.indent([
  100. `${cst} scripts = document.getElementsByTagName("script");`,
  101. "for(var i = 0; i < scripts.length; i++) {",
  102. Template.indent([
  103. `${cst} s = scripts[i];`,
  104. `if(s.getAttribute("src") == url${
  105. uniqueName
  106. ? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key'
  107. : ""
  108. }) { script = s; break; }`
  109. ]),
  110. "}"
  111. ]),
  112. "}",
  113. "if(!script) {",
  114. Template.indent([
  115. "needAttach = true;",
  116. createScript.call(code, /** @type {Chunk} */ (this.chunk))
  117. ]),
  118. "}",
  119. "inProgress[url] = [done];",
  120. `${cst} onScriptComplete = ${runtimeTemplate.basicFunction(
  121. "prev, event",
  122. Template.asString([
  123. "// avoid mem leaks in IE.",
  124. "script.onerror = script.onload = null;",
  125. "clearTimeout(timeout);",
  126. `${cst} doneFns = inProgress[url];`,
  127. "delete inProgress[url];",
  128. `${runtimeTemplate.optionalChaining(
  129. "script.parentNode",
  130. "removeChild(script)"
  131. )};`,
  132. `${runtimeTemplate.optionalChaining(
  133. "doneFns",
  134. `forEach(${runtimeTemplate.returningFunction("fn(event)", "fn")})`
  135. )};`,
  136. "if(prev) return prev(event);"
  137. ])
  138. )}`,
  139. `${cst} timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`,
  140. "script.onerror = onScriptComplete.bind(null, script.onerror);",
  141. "script.onload = onScriptComplete.bind(null, script.onload);",
  142. "needAttach && document.head.appendChild(script);"
  143. ]
  144. )};`
  145. ]);
  146. }
  147. }
  148. LoadScriptRuntimeModule.getCompilationHooks = createHooksRegistry(
  149. () =>
  150. /** @type {LoadScriptCompilationHooks} */ ({
  151. createScript: new SyncWaterfallHook(["source", "chunk"])
  152. })
  153. );
  154. module.exports = LoadScriptRuntimeModule;