CssGenerator.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const {
  7. ConcatSource,
  8. OriginalSource,
  9. RawSource,
  10. ReplaceSource,
  11. SourceMapSource
  12. } = require("webpack-sources");
  13. const { UsageState } = require("../ExportsInfo");
  14. const Generator = require("../Generator");
  15. const InitFragment = require("../InitFragment");
  16. const ModuleFilenameHelpers = require("../ModuleFilenameHelpers");
  17. const {
  18. CSS_TEXT_TYPE,
  19. CSS_TEXT_TYPES,
  20. CSS_TYPE,
  21. CSS_TYPES,
  22. JAVASCRIPT_AND_CSS_TEXT_TYPES,
  23. JAVASCRIPT_AND_CSS_TYPES,
  24. JAVASCRIPT_TYPE,
  25. JAVASCRIPT_TYPES,
  26. NO_TYPES
  27. } = require("../ModuleSourceTypeConstants");
  28. const RuntimeGlobals = require("../RuntimeGlobals");
  29. const Template = require("../Template");
  30. const { getPresentKinds } = require("../TemplatedPathPlugin");
  31. const CssImportDependency = require("../dependencies/CssImportDependency");
  32. const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
  33. const { encodeMappings } = require("../util/createMappings");
  34. const {
  35. contextifySourceUrl,
  36. makePathsAbsolute
  37. } = require("../util/identifier");
  38. const PropertyName = require("../util/property");
  39. const {
  40. PUBLIC_PATH_FULL_HASH,
  41. walkFullHashPlaceholders
  42. } = require("../util/publicPathPlaceholder");
  43. const CssModulesPlugin = require("./CssModulesPlugin");
  44. // Avoids `type.split("/")[0]` allocation on the `getTypes` hot path.
  45. const CSS_TYPE_PREFIX = `${CSS_TYPE}/`;
  46. const URL_SCHEME_REGEXP = /^(?:data|https?):/;
  47. /** @import { Source, RawSourceMap } from "webpack-sources" */
  48. /**
  49. * @import {
  50. * CssModuleGeneratorOptions
  51. * } from "../../declarations/WebpackOptions"
  52. */
  53. /** @import { DependencyConstructor } from "../Compilation" */
  54. /** @import CodeGenerationResults from "../CodeGenerationResults" */
  55. /** @import Dependency from "../Dependency" */
  56. /**
  57. * @import {
  58. * CssData,
  59. * DependencyTemplateContext,
  60. * CssDependencyTemplateContext
  61. * } from "../DependencyTemplate"
  62. */
  63. /** @import { GenerateContext, UpdateHashContext } from "../Generator" */
  64. /**
  65. * @import {
  66. * ConcatenationBailoutReasonContext,
  67. * SourceType,
  68. * SourceTypes
  69. * } from "../Module"
  70. */
  71. /** @import ModuleGraph from "../ModuleGraph" */
  72. /** @import NormalModule from "../NormalModule" */
  73. /** @import Hash from "../util/Hash" */
  74. /** @import { ModuleFactoryCacheEntry } from "./CssModulesPlugin" */
  75. /**
  76. * @import CssModule, {
  77. * CssModuleBuildInfo,
  78. * CssModuleBuildMeta
  79. * } from "./CssModule"
  80. */
  81. /** @typedef {{ line: number, column: number }} SourcePosition */
  82. /** @typedef {Map<string, SourcePosition>} ExportLocsMap */
  83. /**
  84. * Build a v3 source map mapping CSS-class export lines back to their selector positions.
  85. * @param {string} generatedJs the generated JS string
  86. * @param {ExportLocsMap} exportLocs map of export names to CSS source location
  87. * @param {string} cssContent original CSS source content
  88. * @param {string} sourceName source identifier to use in the map
  89. * @returns {RawSourceMap} a v3 RawSourceMap
  90. */
  91. const buildExportsSourceMap = (
  92. generatedJs,
  93. exportLocs,
  94. cssContent,
  95. sourceName
  96. ) => {
  97. const lines = generatedJs.split("\n");
  98. // O(L) pass: match `\t"<name>": <value>` lines against export names.
  99. const keyToName = new Map();
  100. for (const [exportName] of exportLocs) {
  101. keyToName.set(JSON.stringify(exportName), exportName);
  102. }
  103. const lineByExport = new Map();
  104. for (let i = 0; i < lines.length && lineByExport.size < keyToName.size; i++) {
  105. const line = lines[i];
  106. if (line.charCodeAt(0) !== 9 || line.charCodeAt(1) !== 34) continue;
  107. let j = 2;
  108. while (j < line.length) {
  109. const c = line.charCodeAt(j);
  110. if (c === 92) {
  111. j += 2;
  112. continue;
  113. }
  114. if (c === 34) break;
  115. j++;
  116. }
  117. if (line.charCodeAt(j + 1) !== 58) continue;
  118. const name = keyToName.get(line.slice(1, j + 1));
  119. if (name !== undefined && !lineByExport.has(name)) {
  120. lineByExport.set(name, i);
  121. }
  122. }
  123. /** @type {(import("../util/createMappings").LineMappings)[]} */
  124. const perLine = lines.map(() => null);
  125. for (const [exportName, genLine] of lineByExport) {
  126. const pos = /** @type {SourcePosition} */ (exportLocs.get(exportName));
  127. // V3 source maps are 0-based; webpack `loc` lines are 1-based.
  128. perLine[genLine] = {
  129. generatedColumn: 0,
  130. sourceIndex: 0,
  131. originalLine: pos.line - 1,
  132. originalColumn: pos.column
  133. };
  134. }
  135. return {
  136. version: 3,
  137. file: "",
  138. sources: [sourceName],
  139. sourcesContent: [cssContent],
  140. names: [],
  141. mappings: encodeMappings(perLine)
  142. };
  143. };
  144. class CssGenerator extends Generator {
  145. /**
  146. * Creates an instance of CssGenerator.
  147. * @param {CssModuleGeneratorOptions} options options
  148. * @param {ModuleGraph} moduleGraph the module graph
  149. */
  150. constructor(options, moduleGraph) {
  151. super();
  152. /** @type {CssModuleGeneratorOptions} */
  153. this.options = options;
  154. /** @type {boolean | undefined} */
  155. this._exportsOnly = options.exportsOnly;
  156. /** @type {boolean | undefined} */
  157. this._esModule = options.esModule;
  158. /** @type {ModuleGraph} */
  159. this._moduleGraph = moduleGraph;
  160. /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
  161. this._moduleFactoryCache = new WeakMap();
  162. const localIdentName = options.localIdentName;
  163. // Detect hash placeholders via the canonical template parser so this stays
  164. // in sync with `interpolate`'s grammar.
  165. const isFn = typeof localIdentName === "function";
  166. const kinds =
  167. typeof localIdentName === "string"
  168. ? getPresentKinds(localIdentName)
  169. : undefined;
  170. /** @type {boolean} */
  171. this._localIdentNeedsHash =
  172. isFn ||
  173. (kinds !== undefined && (kinds.has("fullhash") || kinds.has("hash")));
  174. /** @type {boolean} */
  175. this._localIdentNeedsContentHash =
  176. isFn || (kinds !== undefined && kinds.has("contenthash"));
  177. }
  178. /**
  179. * Returns the reason this module cannot be concatenated, when one exists.
  180. * @param {NormalModule} module module for which the bailout reason should be determined
  181. * @param {ConcatenationBailoutReasonContext} context context
  182. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  183. */
  184. getConcatenationBailoutReason(module, context) {
  185. if (!this._esModule) {
  186. return "Module is not an ECMAScript module";
  187. }
  188. return undefined;
  189. }
  190. /**
  191. * Processes the provided module.
  192. * @param {Dependency} dependency the dependency to generate
  193. * @param {ReplaceSource} source the current replace source which can be modified
  194. * @param {DependencyTemplateContext & { cssData: CssData, type: string }} templateContext the template context (shared across all dependencies of the module)
  195. * @returns {void}
  196. */
  197. sourceDependency(dependency, source, templateContext) {
  198. const constructor =
  199. /** @type {DependencyConstructor} */
  200. (dependency.constructor);
  201. const template = templateContext.dependencyTemplates.get(constructor);
  202. if (!template) {
  203. throw new Error(
  204. `No template for dependency: ${dependency.constructor.name}`
  205. );
  206. }
  207. template.apply(dependency, source, templateContext);
  208. }
  209. /**
  210. * Processes the provided module.
  211. * @param {NormalModule} module the module to generate
  212. * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
  213. * @param {ReplaceSource} source the current replace source which can be modified
  214. * @param {GenerateContext & { cssData: CssData }} generateContext the generateContext
  215. * @returns {void}
  216. */
  217. sourceModule(module, initFragments, source, generateContext) {
  218. // Only `dependency` varies across `template.apply` calls, so one context
  219. // (and one lazy `chunkInitFragments` getter) serves the whole module.
  220. /** @type {InitFragment<GenerateContext>[] | undefined} */
  221. let chunkInitFragments;
  222. /** @type {CssDependencyTemplateContext} */
  223. const templateContext = {
  224. runtimeTemplate: generateContext.runtimeTemplate,
  225. dependencyTemplates: generateContext.dependencyTemplates,
  226. moduleGraph: generateContext.moduleGraph,
  227. chunkGraph: generateContext.chunkGraph,
  228. module,
  229. runtime: generateContext.runtime,
  230. runtimeRequirements: generateContext.runtimeRequirements,
  231. concatenationScope: generateContext.concatenationScope,
  232. codeGenerationResults:
  233. /** @type {CodeGenerationResults} */
  234. (generateContext.codeGenerationResults),
  235. initFragments,
  236. cssData: generateContext.cssData,
  237. type: generateContext.type,
  238. get chunkInitFragments() {
  239. if (!chunkInitFragments) {
  240. const data =
  241. /** @type {NonNullable<GenerateContext["getData"]>} */
  242. (generateContext.getData)();
  243. chunkInitFragments = data.get("chunkInitFragments");
  244. if (!chunkInitFragments) {
  245. chunkInitFragments = [];
  246. data.set("chunkInitFragments", chunkInitFragments);
  247. }
  248. }
  249. return chunkInitFragments;
  250. }
  251. };
  252. for (const dependency of module.dependencies) {
  253. this.sourceDependency(dependency, source, templateContext);
  254. }
  255. if (module.presentationalDependencies !== undefined) {
  256. for (const dependency of module.presentationalDependencies) {
  257. this.sourceDependency(dependency, source, templateContext);
  258. }
  259. }
  260. }
  261. /**
  262. * Generates generated code for this runtime module.
  263. * @param {NormalModule} module module for which the code should be generated
  264. * @param {GenerateContext} generateContext context for generate
  265. * @returns {Source | null} generated code
  266. */
  267. generate(module, generateContext) {
  268. const exportType = /** @type {CssModule} */ (module).exportType || "link";
  269. const source =
  270. generateContext.type === JAVASCRIPT_TYPE && exportType === "link"
  271. ? new ReplaceSource(new RawSource(""))
  272. : new ReplaceSource(/** @type {Source} */ (module.originalSource()));
  273. /** @type {InitFragment<GenerateContext>[]} */
  274. const initFragments = [];
  275. /** @type {CssData} */
  276. const cssData = {
  277. esModule: /** @type {boolean} */ (this._esModule),
  278. exports: new Map(),
  279. exportLocs: new Map()
  280. };
  281. this.sourceModule(module, initFragments, source, {
  282. ...generateContext,
  283. cssData
  284. });
  285. switch (generateContext.type) {
  286. case JAVASCRIPT_TYPE: {
  287. const compilation = generateContext.runtimeTemplate.compilation;
  288. const devtool = compilation.options.devtool;
  289. const isCssModule = /** @type {CssModuleBuildMeta} */ (module.buildMeta)
  290. .isCssModule;
  291. const generateContentCode = () => {
  292. switch (exportType) {
  293. case "style": {
  294. const rendered = this._renderCss(module, generateContext);
  295. if (!rendered) return "";
  296. const cssSource = this._renderEmbeddedSource(
  297. rendered,
  298. module,
  299. generateContext
  300. );
  301. generateContext.runtimeRequirements.add(
  302. RuntimeGlobals.cssInjectStyle
  303. );
  304. const moduleId = generateContext.chunkGraph.getModuleId(module);
  305. if (generateContext.concatenationScope) {
  306. return new ConcatSource(
  307. `__webpack_css_styles__.push([${JSON.stringify(moduleId)}, `,
  308. this._cssToJsLiteral(cssSource, devtool, generateContext),
  309. "]);"
  310. );
  311. }
  312. return new ConcatSource(
  313. `${RuntimeGlobals.cssInjectStyle}(${JSON.stringify(
  314. moduleId
  315. )}, `,
  316. this._cssToJsLiteral(cssSource, devtool, generateContext),
  317. ");"
  318. );
  319. }
  320. default:
  321. return "";
  322. }
  323. };
  324. const generateImportCode = () => {
  325. switch (exportType) {
  326. case "style": {
  327. return this._generateImportExpressions(module, generateContext)
  328. .map((expr) => `${expr};`)
  329. .join("\n");
  330. }
  331. default:
  332. return "";
  333. }
  334. };
  335. const generateExportCode = () => {
  336. /**
  337. * Generates js default export.
  338. * @returns {Source | null} the default export
  339. */
  340. const generateJSDefaultExport = () => {
  341. switch (exportType) {
  342. case "text": {
  343. return this._generateCssText(module, generateContext, true);
  344. }
  345. case "css-style-sheet": {
  346. const rt = generateContext.runtimeTemplate;
  347. const fnPrefix = rt.supportsArrowFunction()
  348. ? "() => {\n"
  349. : "function() {\n";
  350. const constOrVar = rt.renderConst();
  351. const fallback =
  352. "{ cssText: css, replaceSync: function(value) { this.cssText = value; } }";
  353. if (rt.compilation.compiler.platform.node === true) {
  354. return new ConcatSource(
  355. `(${fnPrefix}${constOrVar} css = `,
  356. this._generateCssText(module, generateContext, true),
  357. `;\nreturn ${fallback};\n})()`
  358. );
  359. }
  360. if (rt.isUniversalTarget()) {
  361. return new ConcatSource(
  362. `(${fnPrefix}${constOrVar} css = `,
  363. this._generateCssText(module, generateContext, true),
  364. `;\nif (typeof CSSStyleSheet === 'undefined') return ${fallback};\n${constOrVar} sheet = new CSSStyleSheet();\nsheet.replaceSync(css);\nreturn sheet;\n})()`
  365. );
  366. }
  367. return new ConcatSource(
  368. `(${fnPrefix}${constOrVar} sheet = new CSSStyleSheet();\nsheet.replaceSync(`,
  369. this._generateCssText(module, generateContext, true),
  370. ");\nreturn sheet;\n})()"
  371. );
  372. }
  373. default:
  374. return null;
  375. }
  376. };
  377. /** @type {Source | null} */
  378. const defaultExport = generateJSDefaultExport();
  379. /** @type {CssModuleBuildInfo} */
  380. (module.buildInfo).cssData = cssData;
  381. // Required for HMR
  382. if (module.hot) {
  383. generateContext.runtimeRequirements.add(RuntimeGlobals.module);
  384. }
  385. if (!defaultExport && cssData.exports.size === 0 && !isCssModule) {
  386. return new RawSource("");
  387. }
  388. const exportsInfo =
  389. generateContext.moduleGraph.getExportsInfo(module);
  390. /**
  391. * @param {string} name export name
  392. * @returns {string | false} used export name when the export is referenced
  393. */
  394. const getUsedExportName = (name) =>
  395. /** @type {string | false} */ (
  396. exportsInfo
  397. .getExportInfo(name)
  398. .getUsedName(name, generateContext.runtime)
  399. );
  400. if (generateContext.concatenationScope) {
  401. const source = new ConcatSource();
  402. /** @type {Set<string>} */
  403. const usedIdentifiers = new Set();
  404. const { RESERVED_IDENTIFIER } = PropertyName;
  405. if (defaultExport) {
  406. const usedName = getUsedExportName("default");
  407. if (usedName) {
  408. let identifier = Template.toIdentifier(usedName);
  409. if (RESERVED_IDENTIFIER.has(identifier)) {
  410. identifier = `_${identifier}`;
  411. }
  412. usedIdentifiers.add(identifier);
  413. generateContext.concatenationScope.registerExport(
  414. "default",
  415. identifier
  416. );
  417. source.add(
  418. `${generateContext.runtimeTemplate.renderConst()} ${identifier} = `
  419. );
  420. source.add(defaultExport);
  421. source.add(";\n");
  422. }
  423. }
  424. for (const [name, v] of cssData.exports) {
  425. const usedName = getUsedExportName(name);
  426. let identifier = Template.toIdentifier(usedName || name);
  427. if (RESERVED_IDENTIFIER.has(identifier)) {
  428. identifier = `_${identifier}`;
  429. }
  430. let i = 0;
  431. while (usedIdentifiers.has(identifier)) {
  432. identifier = Template.toIdentifier(name + i);
  433. i += 1;
  434. }
  435. usedIdentifiers.add(identifier);
  436. generateContext.concatenationScope.registerExport(
  437. name,
  438. identifier
  439. );
  440. source.add(
  441. `${generateContext.runtimeTemplate.renderConst()} ${identifier} = ${JSON.stringify(
  442. v
  443. )};\n`
  444. );
  445. }
  446. return source;
  447. }
  448. const needNsObj =
  449. this._esModule &&
  450. exportsInfo.otherExportsInfo.getUsed(generateContext.runtime) !==
  451. UsageState.Unused;
  452. if (needNsObj) {
  453. generateContext.runtimeRequirements.add(
  454. RuntimeGlobals.makeNamespaceObject
  455. );
  456. }
  457. // Should be after `concatenationScope` to allow module inlining
  458. generateContext.runtimeRequirements.add(RuntimeGlobals.module);
  459. const defaultUsedName = defaultExport
  460. ? getUsedExportName("default")
  461. : false;
  462. /** @type {string[]} */
  463. const exportEntries = [];
  464. for (const [name, v] of cssData.exports) {
  465. const exportKey = getUsedExportName(name) || name;
  466. exportEntries.push(
  467. `\t${JSON.stringify(exportKey)}: ${JSON.stringify(v)}`
  468. );
  469. }
  470. if (
  471. !isCssModule &&
  472. !needNsObj &&
  473. exportEntries.length === 0 &&
  474. defaultUsedName
  475. ) {
  476. return new ConcatSource(
  477. `${module.moduleArgument}.exports = `,
  478. /** @type {Source} */ (defaultExport)
  479. );
  480. }
  481. const result = new ConcatSource();
  482. result.add(
  483. `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
  484. module.moduleArgument
  485. }.exports = {\n`
  486. );
  487. if (defaultUsedName) {
  488. result.add('\t"default": ');
  489. result.add(/** @type {Source} */ (defaultExport));
  490. if (exportEntries.length > 0) {
  491. result.add(",\n");
  492. }
  493. }
  494. if (exportEntries.length > 0) {
  495. result.add(exportEntries.join(",\n"));
  496. }
  497. result.add(`\n}${needNsObj ? ")" : ""};`);
  498. return result;
  499. };
  500. const codeParts = this._exportsOnly
  501. ? [generateExportCode()]
  502. : [generateImportCode(), generateContentCode(), generateExportCode()];
  503. const source = new ConcatSource();
  504. for (const part of codeParts) {
  505. if (part) {
  506. source.add(part);
  507. source.add("\n");
  508. }
  509. }
  510. if (
  511. exportType === "link" &&
  512. !isCssModule &&
  513. cssData.exports.size === 0
  514. ) {
  515. return source;
  516. }
  517. const generatedJs = /** @type {string} */ (source.source());
  518. // Context-relative identifier, like `NormalModule.createSource` — so
  519. // `SourceMapDevToolPlugin` can match the module and apply the
  520. // devtool filename template instead of leaking this raw name
  521. const sourceName = contextifySourceUrl(
  522. /** @type {string} */ (compilation.options.context),
  523. module.identifier(),
  524. compilation.compiler.root
  525. );
  526. if (
  527. /** @type {ExportLocsMap} */
  528. (cssData.exportLocs).size > 0
  529. ) {
  530. const cssOriginal = module.originalSource();
  531. if (cssOriginal) {
  532. const sourceMap = buildExportsSourceMap(
  533. generatedJs,
  534. /** @type {ExportLocsMap} */
  535. (cssData.exportLocs),
  536. /** @type {string} */ (cssOriginal.source()),
  537. sourceName
  538. );
  539. return new SourceMapSource(generatedJs, sourceName, sourceMap);
  540. }
  541. }
  542. return new OriginalSource(generatedJs, sourceName);
  543. }
  544. case CSS_TYPE: {
  545. if (!(this._exportsOnly || exportType !== "link")) {
  546. generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);
  547. }
  548. return InitFragment.addToSource(source, initFragments, generateContext);
  549. }
  550. case CSS_TEXT_TYPE:
  551. return this._generateCssText(module, generateContext, false);
  552. default:
  553. return null;
  554. }
  555. }
  556. /**
  557. * Generates fallback output for the provided error condition.
  558. * @param {Error} error the error
  559. * @param {NormalModule} module module for which the code should be generated
  560. * @param {GenerateContext} generateContext context for generate
  561. * @returns {Source | null} generated code
  562. */
  563. generateError(error, module, generateContext) {
  564. switch (generateContext.type) {
  565. case JAVASCRIPT_TYPE: {
  566. return new RawSource(Generator.throwBuildErrorCode(error));
  567. }
  568. case CSS_TYPE: {
  569. return new RawSource(`/**\n ${error.message} \n**/`);
  570. }
  571. default:
  572. return null;
  573. }
  574. }
  575. /**
  576. * Returns the source types available for this module.
  577. * @param {NormalModule} module fresh module
  578. * @returns {SourceTypes} available types (do not mutate)
  579. */
  580. getTypes(module) {
  581. const exportType = /** @type {CssModule} */ (module).exportType || "link";
  582. if (exportType === "style") {
  583. return JAVASCRIPT_TYPES;
  584. }
  585. let hasJs = false;
  586. let hasCssText = false;
  587. // Uncached per call (#20800) — the common link case stops scanning once
  588. // js is seen; `hasCssText` only matters for exports-only / non-link.
  589. const needsCssText = this._exportsOnly || exportType !== "link";
  590. const connections = this._moduleGraph.getIncomingConnections(module);
  591. for (const connection of connections) {
  592. if (hasJs && (hasCssText || !needsCssText)) break;
  593. if (
  594. exportType === "link" &&
  595. connection.dependency instanceof CssImportDependency
  596. ) {
  597. continue;
  598. }
  599. if (connection.dependency instanceof HarmonyImportSideEffectDependency) {
  600. continue;
  601. }
  602. // HTML inline styles consume CSS_TEXT_TYPE directly.
  603. if (
  604. connection.dependency &&
  605. (connection.dependency.category === "html-style" ||
  606. connection.dependency.category === "html-style-attribute")
  607. ) {
  608. hasCssText = true;
  609. continue;
  610. }
  611. if (!connection.originModule) {
  612. continue;
  613. }
  614. if (!connection.originModule.type.startsWith(CSS_TYPE_PREFIX)) {
  615. hasJs = true;
  616. } else {
  617. const originModule = /** @type {CssModule} */ connection.originModule;
  618. const originExportType =
  619. /** @type {CssModule} */ (originModule).exportType || "link";
  620. if (originExportType !== "link") {
  621. hasJs = true;
  622. }
  623. }
  624. }
  625. if (this._exportsOnly || exportType !== "link") {
  626. if (hasJs && hasCssText) return JAVASCRIPT_AND_CSS_TEXT_TYPES;
  627. if (hasJs) return JAVASCRIPT_TYPES;
  628. if (hasCssText) return CSS_TEXT_TYPES;
  629. return NO_TYPES;
  630. }
  631. if (hasJs) {
  632. return JAVASCRIPT_AND_CSS_TYPES;
  633. }
  634. return CSS_TYPES;
  635. }
  636. /**
  637. * @returns {boolean} whether getTypes() depends on the module's incoming connections
  638. */
  639. getTypesDependOnIncomingConnections() {
  640. return true;
  641. }
  642. /**
  643. * Returns the estimated size for the requested source type.
  644. * @param {NormalModule} module the module
  645. * @param {SourceType=} type source type
  646. * @returns {number} estimate size of the module
  647. */
  648. getSize(module, type) {
  649. switch (type) {
  650. case JAVASCRIPT_TYPE: {
  651. const cssData = /** @type {CssModuleBuildInfo} */ (module.buildInfo)
  652. .cssData;
  653. if (!cssData) {
  654. return 42;
  655. }
  656. if (cssData.exports.size === 0) {
  657. if (
  658. /** @type {CssModuleBuildMeta} */ (module.buildMeta).isCssModule
  659. ) {
  660. return 42;
  661. }
  662. return 0;
  663. }
  664. // `JSON.stringify({...exports}).length` computed entry-wise, without
  665. // materializing the object or the full string.
  666. let length = 2 + (cssData.exports.size - 1);
  667. for (const [key, value] of cssData.exports) {
  668. length +=
  669. JSON.stringify(key).length + 1 + JSON.stringify(value).length;
  670. }
  671. return length + 42;
  672. }
  673. case CSS_TEXT_TYPE:
  674. case CSS_TYPE: {
  675. const originalSource = module.originalSource();
  676. if (!originalSource) {
  677. return 0;
  678. }
  679. return originalSource.size();
  680. }
  681. default:
  682. return 0;
  683. }
  684. }
  685. /**
  686. * Updates the hash with the data contributed by this instance.
  687. * @param {Hash} hash hash that will be modified
  688. * @param {UpdateHashContext} updateHashContext context for updating hash
  689. */
  690. updateHash(hash, { module, runtimeTemplate }) {
  691. hash.update(/** @type {boolean} */ (this._esModule).toString());
  692. hash.update(/** @type {boolean} */ (this._exportsOnly).toString());
  693. // Only a module embedded in JS gets an inlined map, which varies on devtool
  694. // and its filename options without changing the source. `String`: templates
  695. // may be functions.
  696. const exportType = /** @type {CssModule} */ (module).exportType || "link";
  697. if (runtimeTemplate && exportType !== "link") {
  698. // What a tap varies on has to reach the key, or a changed option replays.
  699. runtimeTemplate.compilation.hooks.embeddedSourceHash.call(module, hash);
  700. const { compilation } = runtimeTemplate;
  701. const devtool = compilation.options.devtool;
  702. const outputOptions = compilation.outputOptions;
  703. hash.update(
  704. `|${typeof devtool === "string" ? devtool : ""}|${
  705. outputOptions.devtoolNamespace || ""
  706. }|${String(outputOptions.devtoolModuleFilenameTemplate || "")}|${String(
  707. outputOptions.devtoolFallbackModuleFilenameTemplate || ""
  708. )}`
  709. );
  710. }
  711. }
  712. /**
  713. * Resolve the effective `@charset` by walking text imports.
  714. * @param {NormalModule} module the module
  715. * @param {ModuleGraph} moduleGraph module graph
  716. * @param {WeakSet<NormalModule>=} visited cycle guard
  717. * @returns {string | undefined} the effective charset
  718. */
  719. _getEffectiveCharset(module, moduleGraph, visited = new WeakSet()) {
  720. if (!module || visited.has(module)) return undefined;
  721. const exportType = /** @type {CssModule} */ (module).exportType;
  722. if (exportType !== "text" && exportType !== "css-style-sheet") {
  723. return undefined;
  724. }
  725. visited.add(module);
  726. const own =
  727. module.buildInfo &&
  728. /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset;
  729. if (own !== undefined) return own;
  730. if (exportType !== "text") return undefined;
  731. for (const dep of module.dependencies) {
  732. if (dep instanceof CssImportDependency) {
  733. const depModule = /** @type {NormalModule} */ (
  734. moduleGraph.getModule(dep)
  735. );
  736. const inherited = this._getEffectiveCharset(
  737. depModule,
  738. moduleGraph,
  739. visited
  740. );
  741. if (inherited !== undefined) return inherited;
  742. }
  743. }
  744. return undefined;
  745. }
  746. /**
  747. * Generate JS expressions for `@import` side effects (`style` exportType only).
  748. * @param {NormalModule} module the module to generate CSS text for
  749. * @param {GenerateContext} generateContext the generate context
  750. * @returns {string[]} JS expressions, one per `@import` dependency
  751. */
  752. _generateImportExpressions(module, generateContext) {
  753. const { moduleGraph, concatenationScope } = generateContext;
  754. const parts = [];
  755. for (const dep of module.dependencies) {
  756. if (!(dep instanceof CssImportDependency)) continue;
  757. const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
  758. if (concatenationScope && concatenationScope.isModuleInScope(depModule)) {
  759. continue;
  760. }
  761. parts.push(
  762. generateContext.runtimeTemplate.moduleExports({
  763. module: depModule,
  764. chunkGraph: generateContext.chunkGraph,
  765. request: depModule.userRequest,
  766. weak: false,
  767. runtimeRequirements: generateContext.runtimeRequirements
  768. })
  769. );
  770. }
  771. return parts;
  772. }
  773. /**
  774. * Recursively merge `@import`'d CSS into a single Source (text/css-style-sheet only).
  775. * @param {NormalModule} module the module to render
  776. * @param {GenerateContext} generateContext the generate context
  777. * @param {Set<NormalModule>} ancestors modules on the current path (cycle guard)
  778. * @returns {Source | null} merged CSS source, or null when the module has no content
  779. */
  780. _renderMergedCss(module, generateContext, ancestors) {
  781. if (ancestors.has(module)) return null;
  782. ancestors.add(module);
  783. try {
  784. const { moduleGraph } = generateContext;
  785. /** @type {Source[]} */
  786. const parts = [];
  787. for (const dep of module.dependencies) {
  788. if (!(dep instanceof CssImportDependency)) continue;
  789. const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
  790. if (!depModule) continue;
  791. const depExportType = depModule.exportType;
  792. if (depExportType !== "text" && depExportType !== "css-style-sheet") {
  793. continue;
  794. }
  795. const depMerged = this._renderMergedCss(
  796. depModule,
  797. generateContext,
  798. ancestors
  799. );
  800. if (depMerged) parts.push(depMerged);
  801. }
  802. const own = this._renderCss(module, generateContext);
  803. if (own) parts.push(own);
  804. if (parts.length === 0) return null;
  805. if (parts.length === 1) return parts[0];
  806. return new ConcatSource(...parts);
  807. } finally {
  808. ancestors.delete(module);
  809. }
  810. }
  811. /**
  812. * Generate CSS source for the current module
  813. * @param {NormalModule} module the module to generate CSS source for
  814. * @param {GenerateContext} generateContext the generate context
  815. * @returns {Source | null} the CSS source
  816. */
  817. _renderCss(module, generateContext) {
  818. const moduleSourceContent = /** @type {Source} */ (
  819. this.generate(module, {
  820. ...generateContext,
  821. type: CSS_TYPE
  822. })
  823. );
  824. if (!moduleSourceContent) {
  825. return null;
  826. }
  827. const compilation = generateContext.runtimeTemplate.compilation;
  828. const undoPath = "";
  829. const hooks = CssModulesPlugin.getCompilationHooks(compilation);
  830. return CssModulesPlugin.renderModule(
  831. /** @type {CssModule} */ (module),
  832. {
  833. undoPath,
  834. moduleSourceContent,
  835. moduleFactoryCache: this._moduleFactoryCache,
  836. runtimeTemplate: generateContext.runtimeTemplate
  837. },
  838. hooks
  839. );
  840. }
  841. /**
  842. * Applies the devtool filename template to the sources of a map that is inlined
  843. * into the module's own output: `SourceMapDevToolPlugin` only rewrites maps of
  844. * emitted assets, so this one would keep raw module identifiers.
  845. * @param {RawSourceMap} map the map to rename the sources of
  846. * @param {GenerateContext} generateContext the generate context
  847. * @returns {RawSourceMap} the map with devtool source names
  848. */
  849. _nameMapSources(map, generateContext) {
  850. const { chunkGraph, runtimeTemplate } = generateContext;
  851. const { compilation } = runtimeTemplate;
  852. const outputOptions = compilation.outputOptions;
  853. const absolutify = makePathsAbsolute.bindContextCache(
  854. /** @type {string} */ (compilation.options.context),
  855. compilation.compiler.root
  856. );
  857. const options = {
  858. moduleFilenameTemplate:
  859. outputOptions.devtoolModuleFilenameTemplate ||
  860. ModuleFilenameHelpers.DEFAULT_MODULE_FILENAME_TEMPLATE,
  861. namespace: outputOptions.devtoolNamespace || ""
  862. };
  863. const contextInfo = {
  864. requestShortener: compilation.requestShortener,
  865. chunkGraph,
  866. hashFunction: outputOptions.hashFunction
  867. };
  868. /** @type {Set<string>} */
  869. const usedNames = new Set();
  870. const sources = map.sources.map((source) => {
  871. if (!source.startsWith("webpack://")) return source;
  872. const absoluteSource = absolutify(source.slice(10));
  873. // Sources a loader reported for files that are not modules (e.g. a less
  874. // partial) are templated as a path, like `SourceMapDevToolPlugin` does
  875. const module = compilation.findModule(absoluteSource) || absoluteSource;
  876. if (typeof module === "string" && URL_SCHEME_REGEXP.test(module)) {
  877. return module;
  878. }
  879. let name = ModuleFilenameHelpers.createFilename(
  880. module,
  881. options,
  882. contextInfo
  883. );
  884. // Same disambiguation as `SourceMapDevToolPlugin`: modules sharing a
  885. // resource path (e.g. one file imported under two layers) collide
  886. if (usedNames.has(name)) {
  887. name = ModuleFilenameHelpers.createFilename(
  888. module,
  889. {
  890. moduleFilenameTemplate:
  891. outputOptions.devtoolFallbackModuleFilenameTemplate ||
  892. ModuleFilenameHelpers.DEFAULT_FALLBACK_MODULE_FILENAME_TEMPLATE,
  893. namespace: options.namespace
  894. },
  895. contextInfo
  896. );
  897. while (usedNames.has(name)) name += "*";
  898. }
  899. usedNames.add(name);
  900. return name;
  901. });
  902. return { ...map, sources };
  903. }
  904. /**
  905. * Offer CSS bound for a JS string literal to `renderEmbeddedSource`. No `.css`
  906. * asset carries this text, so an asset-level minimizer never sees it.
  907. * @param {Source} cssSource the CSS source
  908. * @param {NormalModule} module the module
  909. * @param {GenerateContext} generateContext the generate context
  910. * @returns {Source} the source a tap returned, or the original
  911. */
  912. _renderEmbeddedSource(cssSource, module, generateContext) {
  913. const { compilation } = generateContext.runtimeTemplate;
  914. return compilation._resolveEmbeddedSource(cssSource, {
  915. type: CSS_TYPE,
  916. hostType: JAVASCRIPT_TYPE,
  917. module
  918. });
  919. }
  920. /**
  921. * Convert a CSS Source into a JS string literal, splicing `__webpack_require__.h()` for `[fullhash]` placeholders.
  922. * @param {Source} cssSource the CSS source
  923. * @param {import("../../declarations/WebpackOptions").DevTool | undefined} devtool the devtool option
  924. * @param {GenerateContext} generateContext the generate context
  925. * @returns {Source} a Source representing a JS string literal
  926. */
  927. _cssToJsLiteral(cssSource, devtool, generateContext) {
  928. const { source, map } = cssSource.sourceAndMap();
  929. let content = /** @type {string} */ (source);
  930. // A `renderEmbeddedSource` tap may hand back a mapped source whatever
  931. // `devtool` says, so asking for no map at all is checked here too — this
  932. // data URI would ship the sources inside the bundle. `hidden` is the same
  933. // answer for the same reason.
  934. const wanted = typeof devtool === "string" && !devtool.includes("hidden");
  935. if (map && wanted) {
  936. const namedMap = this._nameMapSources(map, generateContext);
  937. const inlineMap =
  938. typeof devtool === "string" && devtool.includes("nosources")
  939. ? { ...namedMap, sourcesContent: undefined }
  940. : namedMap;
  941. const base64Map = Buffer.from(JSON.stringify(inlineMap), "utf8").toString(
  942. "base64"
  943. );
  944. const trailingNewline = content.endsWith("\n") ? "" : "\n";
  945. content += `${trailingNewline}/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}*/`;
  946. }
  947. if (!content.includes(PUBLIC_PATH_FULL_HASH)) {
  948. return new RawSource(JSON.stringify(content));
  949. }
  950. const result = new ConcatSource();
  951. let last = 0;
  952. walkFullHashPlaceholders(content, (start, end, length) => {
  953. result.add(JSON.stringify(content.slice(last, start)));
  954. result.add(
  955. length === 0
  956. ? ` + ${RuntimeGlobals.getFullHash}() + `
  957. : ` + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + `
  958. );
  959. last = end;
  960. });
  961. result.add(JSON.stringify(content.slice(last)));
  962. generateContext.runtimeRequirements.add(RuntimeGlobals.getFullHash);
  963. return result;
  964. }
  965. /**
  966. * Generate the merged CSS text for a module, optionally wrapped as a
  967. * JS string literal for embedding in JavaScript output.
  968. * @param {NormalModule} module the module
  969. * @param {GenerateContext} generateContext the generate context
  970. * @param {boolean} asJsLiteral wrap the result as a JS string literal
  971. * @returns {Source} the CSS text source
  972. */
  973. _generateCssText(module, generateContext, asJsLiteral) {
  974. const cssSource = this._renderMergedCss(module, generateContext, new Set());
  975. const effectiveCharset = this._getEffectiveCharset(
  976. module,
  977. generateContext.moduleGraph
  978. );
  979. let result;
  980. if (effectiveCharset !== undefined) {
  981. const prefix = `@charset "${effectiveCharset}";\n`;
  982. result = cssSource
  983. ? new ConcatSource(prefix, cssSource)
  984. : new RawSource(prefix);
  985. } else {
  986. result = cssSource;
  987. }
  988. // Only text bound for a JS literal. `CSS_TEXT_TYPE` is an HTML inline
  989. // `<style>` / `style=""`, and whether that is CSS at all depends on the
  990. // element's `type` — a call the HTML layer makes and `htmlMinify` honors,
  991. // which is not visible here. After the `@charset` prefix, so a tap sees a
  992. // whole stylesheet.
  993. if (result && asJsLiteral) {
  994. result = this._renderEmbeddedSource(result, module, generateContext);
  995. }
  996. if (asJsLiteral) {
  997. const devtool =
  998. generateContext.runtimeTemplate.compilation.options.devtool;
  999. return result
  1000. ? this._cssToJsLiteral(result, devtool, generateContext)
  1001. : new RawSource('""');
  1002. }
  1003. return result || new RawSource("");
  1004. }
  1005. }
  1006. module.exports = CssGenerator;