CssGenerator.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources");
  7. const { UsageState } = require("../ExportsInfo");
  8. const Generator = require("../Generator");
  9. const InitFragment = require("../InitFragment");
  10. const {
  11. JS_AND_CSS_EXPORT_TYPES,
  12. JS_AND_CSS_TYPES
  13. } = require("../ModuleSourceTypesConstants");
  14. const RuntimeGlobals = require("../RuntimeGlobals");
  15. const Template = require("../Template");
  16. /** @typedef {import("webpack-sources").Source} Source */
  17. /** @typedef {import("../../declarations/WebpackOptions").CssAutoGeneratorOptions} CssAutoGeneratorOptions */
  18. /** @typedef {import("../../declarations/WebpackOptions").CssGlobalGeneratorOptions} CssGlobalGeneratorOptions */
  19. /** @typedef {import("../../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */
  20. /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
  21. /** @typedef {import("../Dependency")} Dependency */
  22. /** @typedef {import("../DependencyTemplate").CssData} CssData */
  23. /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
  24. /** @typedef {import("../Generator").GenerateContext} GenerateContext */
  25. /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
  26. /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
  27. /** @typedef {import("../Module").SourceTypes} SourceTypes */
  28. /** @typedef {import("../NormalModule")} NormalModule */
  29. /** @typedef {import("../util/Hash")} Hash */
  30. class CssGenerator extends Generator {
  31. /**
  32. * @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options
  33. */
  34. constructor(options) {
  35. super();
  36. this.convention = options.exportsConvention;
  37. this.localIdentName = options.localIdentName;
  38. this.exportsOnly = options.exportsOnly;
  39. this.esModule = options.esModule;
  40. }
  41. /**
  42. * @param {NormalModule} module module for which the bailout reason should be determined
  43. * @param {ConcatenationBailoutReasonContext} context context
  44. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  45. */
  46. getConcatenationBailoutReason(module, context) {
  47. if (!this.esModule) {
  48. return "Module is not an ECMAScript module";
  49. }
  50. return undefined;
  51. }
  52. /**
  53. * @param {NormalModule} module module for which the code should be generated
  54. * @param {GenerateContext} generateContext context for generate
  55. * @returns {Source | null} generated code
  56. */
  57. generate(module, generateContext) {
  58. const source =
  59. generateContext.type === "javascript"
  60. ? new ReplaceSource(new RawSource(""))
  61. : new ReplaceSource(/** @type {Source} */ (module.originalSource()));
  62. /** @type {InitFragment<GenerateContext>[]} */
  63. const initFragments = [];
  64. /** @type {CssData} */
  65. const cssData = {
  66. esModule: this.esModule,
  67. exports: new Map()
  68. };
  69. /** @type {InitFragment<GenerateContext>[] | undefined} */
  70. let chunkInitFragments;
  71. /** @type {DependencyTemplateContext} */
  72. const templateContext = {
  73. runtimeTemplate: generateContext.runtimeTemplate,
  74. dependencyTemplates: generateContext.dependencyTemplates,
  75. moduleGraph: generateContext.moduleGraph,
  76. chunkGraph: generateContext.chunkGraph,
  77. module,
  78. runtime: generateContext.runtime,
  79. runtimeRequirements: generateContext.runtimeRequirements,
  80. concatenationScope: generateContext.concatenationScope,
  81. codeGenerationResults:
  82. /** @type {CodeGenerationResults} */
  83. (generateContext.codeGenerationResults),
  84. initFragments,
  85. cssData,
  86. get chunkInitFragments() {
  87. if (!chunkInitFragments) {
  88. const data =
  89. /** @type {NonNullable<GenerateContext["getData"]>} */
  90. (generateContext.getData)();
  91. chunkInitFragments = data.get("chunkInitFragments");
  92. if (!chunkInitFragments) {
  93. chunkInitFragments = [];
  94. data.set("chunkInitFragments", chunkInitFragments);
  95. }
  96. }
  97. return chunkInitFragments;
  98. }
  99. };
  100. /**
  101. * @param {Dependency} dependency dependency
  102. */
  103. const handleDependency = dependency => {
  104. const constructor =
  105. /** @type {new (...args: EXPECTED_ANY[]) => Dependency} */
  106. (dependency.constructor);
  107. const template = generateContext.dependencyTemplates.get(constructor);
  108. if (!template) {
  109. throw new Error(
  110. `No template for dependency: ${dependency.constructor.name}`
  111. );
  112. }
  113. template.apply(dependency, source, templateContext);
  114. };
  115. for (const dependency of module.dependencies) {
  116. handleDependency(dependency);
  117. }
  118. switch (generateContext.type) {
  119. case "javascript": {
  120. module.buildInfo.cssData = cssData;
  121. generateContext.runtimeRequirements.add(RuntimeGlobals.module);
  122. if (generateContext.concatenationScope) {
  123. const source = new ConcatSource();
  124. const usedIdentifiers = new Set();
  125. for (const [name, v] of cssData.exports) {
  126. const usedName = generateContext.moduleGraph
  127. .getExportInfo(module, name)
  128. .getUsedName(name, generateContext.runtime);
  129. if (!usedName) {
  130. continue;
  131. }
  132. let identifier = Template.toIdentifier(usedName);
  133. const { RESERVED_IDENTIFIER } = require("../util/propertyName");
  134. if (RESERVED_IDENTIFIER.has(identifier)) {
  135. identifier = `_${identifier}`;
  136. }
  137. const i = 0;
  138. while (usedIdentifiers.has(identifier)) {
  139. identifier = Template.toIdentifier(name + i);
  140. }
  141. usedIdentifiers.add(identifier);
  142. generateContext.concatenationScope.registerExport(name, identifier);
  143. source.add(
  144. `${
  145. generateContext.runtimeTemplate.supportsConst()
  146. ? "const"
  147. : "var"
  148. } ${identifier} = ${JSON.stringify(v)};\n`
  149. );
  150. }
  151. return source;
  152. }
  153. const needNsObj =
  154. this.esModule &&
  155. generateContext.moduleGraph
  156. .getExportsInfo(module)
  157. .otherExportsInfo.getUsed(generateContext.runtime) !==
  158. UsageState.Unused;
  159. if (needNsObj) {
  160. generateContext.runtimeRequirements.add(
  161. RuntimeGlobals.makeNamespaceObject
  162. );
  163. }
  164. const exports = [];
  165. for (const [name, v] of cssData.exports) {
  166. exports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`);
  167. }
  168. return new RawSource(
  169. `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
  170. module.moduleArgument
  171. }.exports = {\n${exports.join(",\n")}\n}${needNsObj ? ")" : ""};`
  172. );
  173. }
  174. case "css": {
  175. if (module.presentationalDependencies !== undefined) {
  176. for (const dependency of module.presentationalDependencies) {
  177. handleDependency(dependency);
  178. }
  179. }
  180. generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);
  181. return InitFragment.addToSource(source, initFragments, generateContext);
  182. }
  183. }
  184. }
  185. /**
  186. * @param {NormalModule} module fresh module
  187. * @returns {SourceTypes} available types (do not mutate)
  188. */
  189. getTypes(module) {
  190. // TODO, find a better way to prevent the original module from being removed after concatenation, maybe it is a bug
  191. return this.exportsOnly ? JS_AND_CSS_EXPORT_TYPES : JS_AND_CSS_TYPES;
  192. }
  193. /**
  194. * @param {NormalModule} module the module
  195. * @param {string=} type source type
  196. * @returns {number} estimate size of the module
  197. */
  198. getSize(module, type) {
  199. switch (type) {
  200. case "javascript": {
  201. if (!module.buildInfo.cssData) {
  202. return 42;
  203. }
  204. const exports = module.buildInfo.cssData.exports;
  205. const stringifiedExports = JSON.stringify(
  206. Array.from(exports).reduce((obj, [key, value]) => {
  207. obj[key] = value;
  208. return obj;
  209. }, {})
  210. );
  211. return stringifiedExports.length + 42;
  212. }
  213. case "css": {
  214. const originalSource = module.originalSource();
  215. if (!originalSource) {
  216. return 0;
  217. }
  218. return originalSource.size();
  219. }
  220. }
  221. }
  222. /**
  223. * @param {Hash} hash hash that will be modified
  224. * @param {UpdateHashContext} updateHashContext context for updating hash
  225. */
  226. updateHash(hash, { module }) {
  227. hash.update(this.esModule.toString());
  228. }
  229. }
  230. module.exports = CssGenerator;