StartupAssetHintRuntimeModule.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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 Compilation from "../Compilation" */
  9. /**
  10. * Fires `<link rel="prefetch">` / `<link rel="preload">` for every asset
  11. * referenced from this chunk via `new URL(..., import.meta.url)` carrying
  12. * `webpackPrefetch` / `webpackPreload`, at chunk startup. Runs before any
  13. * user module evaluates, so by the time user code reaches the
  14. * corresponding `new URL(...)` (or hands the URL to `<img>`, `fetch`,
  15. * `new Worker`, etc.), the browser already has the response in flight.
  16. *
  17. * The calls themselves are rendered by `ResourceHintPlugin`, while runtime
  18. * requirements are still open — see `collectStartupAssetHintLines`.
  19. */
  20. class StartupAssetHintRuntimeModule extends RuntimeModule {
  21. /**
  22. * @param {string[]} lines the `<link>` calls this chunk fires at startup
  23. */
  24. constructor(lines) {
  25. super("startup asset hints", RuntimeModule.STAGE_TRIGGER);
  26. this._lines = lines;
  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 lines = this._lines;
  35. if (lines.length === 0) return null;
  36. // `__webpack_nonce__` set inside the entry module is too late — the
  37. // entry hasn't run yet. Read the nonce off the `<script>` tag that
  38. // loaded the bundle, so prefetch / preload links match a CSP that
  39. // demands the same nonce as the script.
  40. //
  41. // - For classic script output, `document.currentScript` is the
  42. // loading `<script>` element while the runtime executes.
  43. // - For ESM output (`output.module: true`), `document.currentScript`
  44. // is `null`; locate the loading `<script type="module">` by
  45. // matching `script.src` against `import.meta.url`.
  46. const { module: isModule, importMetaName } = compilation.outputOptions;
  47. const nonceSetup = isModule
  48. ? [
  49. "if (typeof document !== 'undefined') {",
  50. Template.indent([
  51. `var url = ${importMetaName}.url;`,
  52. "var scripts = document.getElementsByTagName('script');",
  53. "for (var i = 0; i < scripts.length; i++) {",
  54. Template.indent([
  55. `if (scripts[i].src === url && scripts[i].nonce && !${RuntimeGlobals.scriptNonce}) {`,
  56. Template.indent([
  57. `${RuntimeGlobals.scriptNonce} = scripts[i].nonce;`,
  58. "break;"
  59. ]),
  60. "}"
  61. ]),
  62. "}"
  63. ]),
  64. "}"
  65. ]
  66. : [
  67. "if (typeof document !== 'undefined') {",
  68. Template.indent([
  69. "var currentScript = document.currentScript;",
  70. `if (currentScript && currentScript.nonce && !${RuntimeGlobals.scriptNonce}) {`,
  71. Template.indent(
  72. `${RuntimeGlobals.scriptNonce} = currentScript.nonce;`
  73. ),
  74. "}"
  75. ]),
  76. "}"
  77. ];
  78. return Template.asString([...nonceSetup, ...lines]);
  79. }
  80. }
  81. module.exports = StartupAssetHintRuntimeModule;