ResourceHintRuntimeModule.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { SyncWaterfallHook } = require("tapable");
  6. const Compilation = require("../Compilation");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const RuntimeModule = require("../RuntimeModule");
  9. const Template = require("../Template");
  10. const { guessAsAttribute } = require("./parseResourceHintOptions");
  11. /**
  12. * @typedef {object} ResourceHintRuntimeModulePluginHooks
  13. * @property {SyncWaterfallHook<[string]>} linkPrefetch
  14. * @property {SyncWaterfallHook<[string]>} linkPreload
  15. */
  16. /** @type {WeakMap<Compilation, ResourceHintRuntimeModulePluginHooks>} */
  17. const compilationHooksMap = new WeakMap();
  18. class ResourceHintRuntimeModule extends RuntimeModule {
  19. /**
  20. * @param {Compilation} compilation the compilation
  21. * @returns {ResourceHintRuntimeModulePluginHooks} hooks
  22. */
  23. static getCompilationHooks(compilation) {
  24. if (!(compilation instanceof Compilation)) {
  25. throw new TypeError(
  26. "The 'compilation' argument must be an instance of Compilation"
  27. );
  28. }
  29. let hooks = compilationHooksMap.get(compilation);
  30. if (hooks === undefined) {
  31. hooks = {
  32. linkPrefetch: new SyncWaterfallHook(["source"]),
  33. linkPreload: new SyncWaterfallHook(["source"])
  34. };
  35. compilationHooksMap.set(compilation, hooks);
  36. }
  37. return hooks;
  38. }
  39. /**
  40. * @param {"prefetch" | "preload"} rel link rel value
  41. */
  42. constructor(rel) {
  43. super(`asset ${rel}`, RuntimeModule.STAGE_ATTACH);
  44. this._rel = rel;
  45. }
  46. /**
  47. * Generates runtime code for this runtime module.
  48. * @returns {string | null} runtime code
  49. */
  50. generate() {
  51. const compilation = /** @type {Compilation} */ (this.compilation);
  52. const { runtimeTemplate, outputOptions } = compilation;
  53. const { crossOriginLoading } = outputOptions;
  54. const isNeutralPlatform = runtimeTemplate.isNeutralPlatform();
  55. const rel = this._rel;
  56. const fnName =
  57. rel === "prefetch"
  58. ? RuntimeGlobals.prefetchAsset
  59. : RuntimeGlobals.preloadAsset;
  60. const hook =
  61. ResourceHintRuntimeModule.getCompilationHooks(compilation)[
  62. rel === "prefetch" ? "linkPrefetch" : "linkPreload"
  63. ];
  64. const body = [
  65. "if (installed[href]) return href;",
  66. "installed[href] = 1;",
  67. hook.call(
  68. Template.asString([
  69. "var link = document.createElement('link');",
  70. `link.rel = ${JSON.stringify(rel)};`,
  71. "link.href = href;",
  72. "if (as) link.as = as;",
  73. "if (type) link.type = type;",
  74. "if (media) link.media = media;",
  75. "if (fetchPriority) {",
  76. Template.indent([
  77. 'link.setAttribute("fetchpriority", fetchPriority);'
  78. ]),
  79. "}",
  80. `if (${RuntimeGlobals.scriptNonce}) {`,
  81. Template.indent(
  82. `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  83. ),
  84. "}",
  85. crossOriginLoading
  86. ? crossOriginLoading === "use-credentials"
  87. ? 'link.crossOrigin = "use-credentials";'
  88. : Template.asString([
  89. "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
  90. Template.indent(
  91. `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
  92. ),
  93. "}"
  94. ])
  95. : ""
  96. ])
  97. ),
  98. "document.head.appendChild(link);",
  99. "return href;"
  100. ];
  101. const fnBody = isNeutralPlatform
  102. ? ["if (typeof document === 'undefined') return href;", ...body]
  103. : body;
  104. // `Object.create(null)` keeps the dedupe map free of inherited
  105. // properties (`toString`, `__proto__`, …) so any URL string is a
  106. // safe key.
  107. return Template.asString([
  108. "var installed = Object.create(null);",
  109. `${fnName} = ${runtimeTemplate.basicFunction(
  110. "href, as, type, media, fetchPriority",
  111. fnBody
  112. )};`
  113. ]);
  114. }
  115. }
  116. ResourceHintRuntimeModule.guessAsAttribute = guessAsAttribute;
  117. module.exports = ResourceHintRuntimeModule;