CssInjectStyleRuntimeModule.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. /** @import Compilation from "../Compilation" */
  8. const RuntimeGlobals = require("../RuntimeGlobals");
  9. const RuntimeModule = require("../RuntimeModule");
  10. const Template = require("../Template");
  11. const createHooksRegistry = require("../util/createHooksRegistry");
  12. /** @import Chunk from "../Chunk" */
  13. /** @import { ReadOnlyRuntimeRequirements } from "../Module" */
  14. /**
  15. * @typedef {object} CssInjectCompilationHooks
  16. * @property {SyncWaterfallHook<[string, Chunk]>} createStyle
  17. */
  18. class CssInjectStyleRuntimeModule extends RuntimeModule {
  19. /**
  20. * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
  21. */
  22. constructor(runtimeRequirements) {
  23. super("css inject style", RuntimeModule.STAGE_ATTACH);
  24. /** @type {ReadOnlyRuntimeRequirements} */
  25. this._runtimeRequirements = runtimeRequirements;
  26. }
  27. /**
  28. * Generates runtime code for this runtime module.
  29. * @returns {string | null} runtime code
  30. */
  31. generate() {
  32. const compilation = /** @type {Compilation} */ (this.compilation);
  33. const { runtimeTemplate, outputOptions } = compilation;
  34. const { uniqueName } = outputOptions;
  35. const { _runtimeRequirements } = this;
  36. /** @type {boolean} */
  37. const withHmr =
  38. _runtimeRequirements &&
  39. _runtimeRequirements.has(RuntimeGlobals.hmrDownloadUpdateHandlers);
  40. const { createStyle } =
  41. CssInjectStyleRuntimeModule.getCompilationHooks(compilation);
  42. // Only emit the nonce check when scriptNonce is part of the runtime
  43. // requirements. Otherwise referencing `__webpack_require__.nc` would
  44. // keep the require runtime alive for nothing.
  45. const withScriptNonce =
  46. _runtimeRequirements &&
  47. _runtimeRequirements.has(RuntimeGlobals.scriptNonce);
  48. const cst = runtimeTemplate.renderConst();
  49. const isNeutralPlatform = runtimeTemplate.isNeutralPlatform();
  50. // universal targets run in node too: collect styles for SSR retrieval
  51. // instead of touching a DOM that isn't there
  52. const injectGuard = isNeutralPlatform
  53. ? [
  54. "if (typeof document === 'undefined') {",
  55. Template.indent([
  56. // "module-" keeps numeric module ids from enumerating in ascending
  57. // order instead of the order the styles were applied in
  58. `${runtimeTemplate.cssServerStyleRegistry()}["module-" + identifier] = css;`,
  59. "return;"
  60. ]),
  61. "}"
  62. ]
  63. : [];
  64. const removeGuard = isNeutralPlatform
  65. ? [
  66. "if (typeof document === 'undefined') {",
  67. Template.indent([
  68. `${cst} styles = ${runtimeTemplate.cssServerStyleRegistry()};`,
  69. 'for (var i = 0; i < identifiers.length; i++) delete styles["module-" + identifiers[i]];',
  70. "return;"
  71. ]),
  72. "}"
  73. ]
  74. : [];
  75. const createStyleElementCode = Template.asString([
  76. `${cst} style = document.createElement('style');`,
  77. "",
  78. ...(withScriptNonce
  79. ? [
  80. `if (${RuntimeGlobals.scriptNonce}) {`,
  81. Template.indent(
  82. `style.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
  83. ),
  84. "}"
  85. ]
  86. : []),
  87. 'style.setAttribute("data-webpack", getDataWebpackId(key));'
  88. ]);
  89. return Template.asString([
  90. `${cst} dataWebpackPrefix = ${uniqueName ? JSON.stringify(`${uniqueName}:`) : '"webpack:"'};`,
  91. "",
  92. "function getDataWebpackId(identifier) {",
  93. Template.indent("return dataWebpackPrefix + identifier;"),
  94. "}",
  95. "",
  96. "function findStyleElement(identifier) {",
  97. Template.indent([
  98. `${cst} elements = document.getElementsByTagName('style');`,
  99. "for (var i = 0; i < elements.length; i++) {",
  100. Template.indent([
  101. `${cst} el = elements[i];`,
  102. "if (el.getAttribute('data-webpack') === getDataWebpackId(identifier)) {",
  103. Template.indent("return el;"),
  104. "}"
  105. ]),
  106. "}",
  107. "return null;"
  108. ]),
  109. "}",
  110. "",
  111. "function insertStyleElement(key) {",
  112. Template.indent([
  113. createStyle.call(
  114. createStyleElementCode,
  115. /** @type {Chunk} */ (this.chunk)
  116. ),
  117. "",
  118. "document.head.appendChild(style);",
  119. "",
  120. "return style;"
  121. ]),
  122. "}",
  123. "",
  124. `${RuntimeGlobals.cssInjectStyle} = ${runtimeTemplate.basicFunction(
  125. "identifier, css",
  126. [
  127. ...injectGuard,
  128. `${cst} element = findStyleElement(identifier) || insertStyleElement(identifier);`,
  129. "element.textContent = css;"
  130. ]
  131. )};`,
  132. "",
  133. withHmr
  134. ? Template.asString([
  135. "",
  136. "function removeStyleElement(styleElement) {",
  137. Template.indent([
  138. "if (styleElement.parentNode) {",
  139. Template.indent(
  140. "styleElement.parentNode.removeChild(styleElement);"
  141. ),
  142. "}"
  143. ]),
  144. "}",
  145. `${RuntimeGlobals.cssInjectStyle}.removeModules = ${runtimeTemplate.basicFunction(
  146. "removedModules",
  147. [
  148. "if (!removedModules) return;",
  149. `${cst} identifiers = Array.isArray(removedModules) ? removedModules : [removedModules];`,
  150. ...removeGuard,
  151. "for (var i = 0; i < identifiers.length; i++) {",
  152. Template.indent([
  153. `${cst} identifier = identifiers[i];`,
  154. `${cst} element = findStyleElement(identifier);`,
  155. "if (element) {",
  156. Template.indent("removeStyleElement(element);"),
  157. "}"
  158. ]),
  159. "}"
  160. ]
  161. )};`,
  162. `${RuntimeGlobals.hmrDownloadUpdateHandlers}.cssInjectStyle = ${runtimeTemplate.basicFunction(
  163. "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList, css",
  164. [
  165. "if (removedModules) {",
  166. Template.indent(
  167. `${RuntimeGlobals.cssInjectStyle}.removeModules(removedModules);`
  168. ),
  169. "}"
  170. ]
  171. )};`
  172. ])
  173. : "// no css inject style HMR download handler"
  174. ]);
  175. }
  176. }
  177. CssInjectStyleRuntimeModule.getCompilationHooks = createHooksRegistry(
  178. () =>
  179. /** @type {CssInjectCompilationHooks} */ ({
  180. createStyle: new SyncWaterfallHook(["source", "chunk"])
  181. })
  182. );
  183. module.exports = CssInjectStyleRuntimeModule;