CssInjectStyleRuntimeModule.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Natsu @xiaoxiaojx
  4. */
  5. "use strict";
  6. const { SyncWaterfallHook } = require("tapable");
  7. const Compilation = require("../Compilation");
  8. const RuntimeGlobals = require("../RuntimeGlobals");
  9. const RuntimeModule = require("../RuntimeModule");
  10. const Template = require("../Template");
  11. /** @typedef {import("../Chunk")} Chunk */
  12. /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
  13. /**
  14. * @typedef {object} CssInjectCompilationHooks
  15. * @property {SyncWaterfallHook<[string, Chunk]>} createStyle
  16. */
  17. /** @type {WeakMap<Compilation, CssInjectCompilationHooks>} */
  18. const compilationHooksMap = new WeakMap();
  19. class CssInjectStyleRuntimeModule extends RuntimeModule {
  20. /**
  21. * @param {Compilation} compilation the compilation
  22. * @returns {CssInjectCompilationHooks} hooks
  23. */
  24. static getCompilationHooks(compilation) {
  25. if (!(compilation instanceof Compilation)) {
  26. throw new TypeError(
  27. "The 'compilation' argument must be an instance of Compilation"
  28. );
  29. }
  30. let hooks = compilationHooksMap.get(compilation);
  31. if (hooks === undefined) {
  32. hooks = {
  33. createStyle: new SyncWaterfallHook(["source", "chunk"])
  34. };
  35. compilationHooksMap.set(compilation, hooks);
  36. }
  37. return hooks;
  38. }
  39. /**
  40. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  41. */
  42. constructor(runtimeRequirements) {
  43. super("css inject style", RuntimeModule.STAGE_ATTACH);
  44. /** @type {ReadOnlyRuntimeRequirements} */
  45. this._runtimeRequirements = runtimeRequirements;
  46. }
  47. /**
  48. * Generates runtime code for this runtime module.
  49. * @returns {string | null} runtime code
  50. */
  51. generate() {
  52. const compilation = /** @type {Compilation} */ (this.compilation);
  53. const { runtimeTemplate, outputOptions } = compilation;
  54. const { uniqueName } = outputOptions;
  55. const { _runtimeRequirements } = this;
  56. /** @type {boolean} */
  57. const withHmr =
  58. _runtimeRequirements &&
  59. _runtimeRequirements.has(RuntimeGlobals.hmrDownloadUpdateHandlers);
  60. const { createStyle } =
  61. CssInjectStyleRuntimeModule.getCompilationHooks(compilation);
  62. const createStyleElementCode = Template.asString([
  63. "var style = document.createElement('style');",
  64. "",
  65. `if (${RuntimeGlobals.scriptNonce}) {`,
  66. Template.indent(
  67. `style.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  68. ),
  69. "}",
  70. 'style.setAttribute("data-webpack", getDataWebpackId(key));'
  71. ]);
  72. return Template.asString([
  73. `var dataWebpackPrefix = ${uniqueName ? JSON.stringify(`${uniqueName}:`) : '"webpack:"'};`,
  74. "",
  75. "function getDataWebpackId(identifier) {",
  76. Template.indent("return dataWebpackPrefix + identifier;"),
  77. "}",
  78. "",
  79. "function applyStyle(styleElement, css) {",
  80. Template.indent("styleElement.textContent = css;"),
  81. "}",
  82. "",
  83. "function removeStyleElement(styleElement) {",
  84. Template.indent([
  85. "if (styleElement.parentNode) {",
  86. Template.indent("styleElement.parentNode.removeChild(styleElement);"),
  87. "}"
  88. ]),
  89. "}",
  90. "",
  91. "function findStyleElement(identifier) {",
  92. Template.indent([
  93. "var elements = document.getElementsByTagName('style');",
  94. "for (var i = 0; i < elements.length; i++) {",
  95. Template.indent([
  96. "var el = elements[i];",
  97. "if (el.getAttribute('data-webpack') === getDataWebpackId(identifier)) {",
  98. Template.indent("return el;"),
  99. "}"
  100. ]),
  101. "}",
  102. "return null;"
  103. ]),
  104. "}",
  105. "",
  106. "function insertStyleElement(key) {",
  107. Template.indent([
  108. createStyle.call(
  109. createStyleElementCode,
  110. /** @type {Chunk} */ (this.chunk)
  111. ),
  112. "",
  113. "document.head.appendChild(style);",
  114. "",
  115. "return style;"
  116. ]),
  117. "}",
  118. "",
  119. `${RuntimeGlobals.cssInjectStyle} = ${runtimeTemplate.basicFunction(
  120. "identifier, css",
  121. [
  122. "var element = findStyleElement(identifier);",
  123. "if (element) {",
  124. Template.indent("applyStyle(element, css);"),
  125. "} else {",
  126. Template.indent([
  127. "var element = insertStyleElement(identifier);",
  128. "applyStyle(element, css);"
  129. ]),
  130. "}"
  131. ]
  132. )};`,
  133. "",
  134. `${RuntimeGlobals.cssInjectStyle}.removeModules = ${runtimeTemplate.basicFunction(
  135. "removedModules",
  136. [
  137. "if (!removedModules) return;",
  138. "var identifiers = Array.isArray(removedModules) ? removedModules : [removedModules];",
  139. "for (var i = 0; i < identifiers.length; i++) {",
  140. Template.indent([
  141. "var identifier = identifiers[i];",
  142. "var element = findStyleElement(identifier);",
  143. "if (element) {",
  144. Template.indent("removeStyleElement(element);"),
  145. "}"
  146. ]),
  147. "}"
  148. ]
  149. )};`,
  150. withHmr
  151. ? Template.asString([
  152. `${RuntimeGlobals.hmrDownloadUpdateHandlers}.cssInjectStyle = ${runtimeTemplate.basicFunction(
  153. "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList, css",
  154. [
  155. "if (removedModules) {",
  156. Template.indent(
  157. `${RuntimeGlobals.cssInjectStyle}.removeModules(removedModules);`
  158. ),
  159. "}"
  160. ]
  161. )};`
  162. ])
  163. : "// no css inject style HMR download handler"
  164. ]);
  165. }
  166. }
  167. module.exports = CssInjectStyleRuntimeModule;