CssModulesPlugin.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const {
  7. ConcatSource,
  8. PrefixSource,
  9. ReplaceSource,
  10. CachedSource
  11. } = require("webpack-sources");
  12. const CssModule = require("../CssModule");
  13. const HotUpdateChunk = require("../HotUpdateChunk");
  14. const {
  15. CSS_MODULE_TYPE,
  16. CSS_MODULE_TYPE_GLOBAL,
  17. CSS_MODULE_TYPE_MODULE,
  18. CSS_MODULE_TYPE_AUTO
  19. } = require("../ModuleTypeConstants");
  20. const RuntimeGlobals = require("../RuntimeGlobals");
  21. const SelfModuleFactory = require("../SelfModuleFactory");
  22. const WebpackError = require("../WebpackError");
  23. const CssExportDependency = require("../dependencies/CssExportDependency");
  24. const CssImportDependency = require("../dependencies/CssImportDependency");
  25. const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
  26. const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
  27. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  28. const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
  29. const { compareModulesByIdentifier } = require("../util/comparators");
  30. const createSchemaValidation = require("../util/create-schema-validation");
  31. const createHash = require("../util/createHash");
  32. const { getUndoPath } = require("../util/identifier");
  33. const memoize = require("../util/memoize");
  34. const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
  35. const CssExportsGenerator = require("./CssExportsGenerator");
  36. const CssGenerator = require("./CssGenerator");
  37. const CssParser = require("./CssParser");
  38. /** @typedef {import("webpack-sources").Source} Source */
  39. /** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */
  40. /** @typedef {import("../Chunk")} Chunk */
  41. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  42. /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
  43. /** @typedef {import("../Compilation")} Compilation */
  44. /** @typedef {import("../Compiler")} Compiler */
  45. /** @typedef {import("../CssModule").Inheritance} Inheritance */
  46. /** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
  47. /** @typedef {import("../Module")} Module */
  48. /** @typedef {import("../util/memoize")} Memoize */
  49. const getCssLoadingRuntimeModule = memoize(() =>
  50. require("./CssLoadingRuntimeModule")
  51. );
  52. /**
  53. * @param {string} name name
  54. * @returns {{oneOf: [{$ref: string}], definitions: *}} schema
  55. */
  56. const getSchema = name => {
  57. const { definitions } = require("../../schemas/WebpackOptions.json");
  58. return {
  59. definitions,
  60. oneOf: [{ $ref: `#/definitions/${name}` }]
  61. };
  62. };
  63. const generatorValidationOptions = {
  64. name: "Css Modules Plugin",
  65. baseDataPath: "generator"
  66. };
  67. const validateGeneratorOptions = {
  68. css: createSchemaValidation(
  69. require("../../schemas/plugins/css/CssGeneratorOptions.check.js"),
  70. () => getSchema("CssGeneratorOptions"),
  71. generatorValidationOptions
  72. ),
  73. "css/auto": createSchemaValidation(
  74. require("../../schemas/plugins/css/CssAutoGeneratorOptions.check.js"),
  75. () => getSchema("CssAutoGeneratorOptions"),
  76. generatorValidationOptions
  77. ),
  78. "css/module": createSchemaValidation(
  79. require("../../schemas/plugins/css/CssModuleGeneratorOptions.check.js"),
  80. () => getSchema("CssModuleGeneratorOptions"),
  81. generatorValidationOptions
  82. ),
  83. "css/global": createSchemaValidation(
  84. require("../../schemas/plugins/css/CssGlobalGeneratorOptions.check.js"),
  85. () => getSchema("CssGlobalGeneratorOptions"),
  86. generatorValidationOptions
  87. )
  88. };
  89. const parserValidationOptions = {
  90. name: "Css Modules Plugin",
  91. baseDataPath: "parser"
  92. };
  93. const validateParserOptions = {
  94. css: createSchemaValidation(
  95. require("../../schemas/plugins/css/CssParserOptions.check.js"),
  96. () => getSchema("CssParserOptions"),
  97. parserValidationOptions
  98. ),
  99. "css/auto": createSchemaValidation(
  100. require("../../schemas/plugins/css/CssAutoParserOptions.check.js"),
  101. () => getSchema("CssAutoParserOptions"),
  102. parserValidationOptions
  103. ),
  104. "css/module": createSchemaValidation(
  105. require("../../schemas/plugins/css/CssModuleParserOptions.check.js"),
  106. () => getSchema("CssModuleParserOptions"),
  107. parserValidationOptions
  108. ),
  109. "css/global": createSchemaValidation(
  110. require("../../schemas/plugins/css/CssGlobalParserOptions.check.js"),
  111. () => getSchema("CssGlobalParserOptions"),
  112. parserValidationOptions
  113. )
  114. };
  115. /**
  116. * @param {string} str string
  117. * @param {boolean=} omitOptionalUnderscore if true, optional underscore is not added
  118. * @returns {string} escaped string
  119. */
  120. const escapeCss = (str, omitOptionalUnderscore) => {
  121. const escaped = `${str}`.replace(
  122. // cspell:word uffff
  123. /[^a-zA-Z0-9_\u0081-\uffff-]/g,
  124. s => `\\${s}`
  125. );
  126. return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)
  127. ? `_${escaped}`
  128. : escaped;
  129. };
  130. /**
  131. * @param {string} str string
  132. * @returns {string} encoded string
  133. */
  134. const LZWEncode = str => {
  135. /** @type {Map<string, string>} */
  136. const map = new Map();
  137. let encoded = "";
  138. let phrase = str[0];
  139. let code = 256;
  140. let maxCode = "\uffff".charCodeAt(0);
  141. for (let i = 1; i < str.length; i++) {
  142. const c = str[i];
  143. if (map.has(phrase + c)) {
  144. phrase += c;
  145. } else {
  146. encoded += phrase.length > 1 ? map.get(phrase) : phrase;
  147. map.set(phrase + c, String.fromCharCode(code));
  148. phrase = c;
  149. if (++code > maxCode) {
  150. code = 256;
  151. map.clear();
  152. }
  153. }
  154. }
  155. encoded += phrase.length > 1 ? map.get(phrase) : phrase;
  156. return encoded;
  157. };
  158. const plugin = "CssModulesPlugin";
  159. class CssModulesPlugin {
  160. constructor() {
  161. /** @type {WeakMap<Source, { undoPath: string, inheritance: Inheritance, source: CachedSource }>} */
  162. this._moduleCache = new WeakMap();
  163. }
  164. /**
  165. * Apply the plugin
  166. * @param {Compiler} compiler the compiler instance
  167. * @returns {void}
  168. */
  169. apply(compiler) {
  170. compiler.hooks.compilation.tap(
  171. plugin,
  172. (compilation, { normalModuleFactory }) => {
  173. const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
  174. compilation.dependencyFactories.set(
  175. CssUrlDependency,
  176. normalModuleFactory
  177. );
  178. compilation.dependencyTemplates.set(
  179. CssUrlDependency,
  180. new CssUrlDependency.Template()
  181. );
  182. compilation.dependencyTemplates.set(
  183. CssLocalIdentifierDependency,
  184. new CssLocalIdentifierDependency.Template()
  185. );
  186. compilation.dependencyFactories.set(
  187. CssSelfLocalIdentifierDependency,
  188. selfFactory
  189. );
  190. compilation.dependencyTemplates.set(
  191. CssSelfLocalIdentifierDependency,
  192. new CssSelfLocalIdentifierDependency.Template()
  193. );
  194. compilation.dependencyTemplates.set(
  195. CssExportDependency,
  196. new CssExportDependency.Template()
  197. );
  198. compilation.dependencyFactories.set(
  199. CssImportDependency,
  200. normalModuleFactory
  201. );
  202. compilation.dependencyTemplates.set(
  203. CssImportDependency,
  204. new CssImportDependency.Template()
  205. );
  206. compilation.dependencyTemplates.set(
  207. StaticExportsDependency,
  208. new StaticExportsDependency.Template()
  209. );
  210. for (const type of [
  211. CSS_MODULE_TYPE,
  212. CSS_MODULE_TYPE_GLOBAL,
  213. CSS_MODULE_TYPE_MODULE,
  214. CSS_MODULE_TYPE_AUTO
  215. ]) {
  216. normalModuleFactory.hooks.createParser
  217. .for(type)
  218. .tap(plugin, parserOptions => {
  219. validateParserOptions[type](parserOptions);
  220. const { namedExports } = parserOptions;
  221. switch (type) {
  222. case CSS_MODULE_TYPE_GLOBAL:
  223. case CSS_MODULE_TYPE_AUTO:
  224. return new CssParser({
  225. namedExports
  226. });
  227. case CSS_MODULE_TYPE:
  228. return new CssParser({
  229. allowModeSwitch: false,
  230. namedExports
  231. });
  232. case CSS_MODULE_TYPE_MODULE:
  233. return new CssParser({
  234. defaultMode: "local",
  235. namedExports
  236. });
  237. }
  238. });
  239. normalModuleFactory.hooks.createGenerator
  240. .for(type)
  241. .tap(plugin, generatorOptions => {
  242. validateGeneratorOptions[type](generatorOptions);
  243. return generatorOptions.exportsOnly
  244. ? new CssExportsGenerator(
  245. generatorOptions.exportsConvention,
  246. generatorOptions.localIdentName,
  247. generatorOptions.esModule
  248. )
  249. : new CssGenerator(
  250. generatorOptions.exportsConvention,
  251. generatorOptions.localIdentName,
  252. generatorOptions.esModule
  253. );
  254. });
  255. normalModuleFactory.hooks.createModuleClass
  256. .for(type)
  257. .tap(plugin, (createData, resolveData) => {
  258. if (resolveData.dependencies.length > 0) {
  259. // When CSS is imported from CSS there is only one dependency
  260. const dependency = resolveData.dependencies[0];
  261. if (dependency instanceof CssImportDependency) {
  262. const parent =
  263. /** @type {CssModule} */
  264. (compilation.moduleGraph.getParentModule(dependency));
  265. if (parent instanceof CssModule) {
  266. /** @type {import("../CssModule").Inheritance | undefined} */
  267. let inheritance;
  268. if (
  269. (parent.cssLayer !== null &&
  270. parent.cssLayer !== undefined) ||
  271. parent.supports ||
  272. parent.media
  273. ) {
  274. if (!inheritance) {
  275. inheritance = [];
  276. }
  277. inheritance.push([
  278. parent.cssLayer,
  279. parent.supports,
  280. parent.media
  281. ]);
  282. }
  283. if (parent.inheritance) {
  284. if (!inheritance) {
  285. inheritance = [];
  286. }
  287. inheritance.push(...parent.inheritance);
  288. }
  289. return new CssModule({
  290. ...createData,
  291. cssLayer: dependency.layer,
  292. supports: dependency.supports,
  293. media: dependency.media,
  294. inheritance
  295. });
  296. }
  297. return new CssModule({
  298. ...createData,
  299. cssLayer: dependency.layer,
  300. supports: dependency.supports,
  301. media: dependency.media
  302. });
  303. }
  304. }
  305. return new CssModule(createData);
  306. });
  307. }
  308. const orderedCssModulesPerChunk = new WeakMap();
  309. compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {
  310. const { chunkGraph } = compilation;
  311. for (const chunk of compilation.chunks) {
  312. if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
  313. orderedCssModulesPerChunk.set(
  314. chunk,
  315. this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
  316. );
  317. }
  318. }
  319. });
  320. compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {
  321. const {
  322. chunkGraph,
  323. outputOptions: {
  324. hashSalt,
  325. hashDigest,
  326. hashDigestLength,
  327. hashFunction
  328. }
  329. } = compilation;
  330. const modules = orderedCssModulesPerChunk.get(chunk);
  331. if (modules === undefined) return;
  332. const hash = createHash(hashFunction);
  333. if (hashSalt) hash.update(hashSalt);
  334. for (const module of modules) {
  335. hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
  336. }
  337. const digest = /** @type {string} */ (hash.digest(hashDigest));
  338. chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
  339. });
  340. compilation.hooks.renderManifest.tap(plugin, (result, options) => {
  341. const { chunkGraph } = compilation;
  342. const { hash, chunk, codeGenerationResults } = options;
  343. if (chunk instanceof HotUpdateChunk) return result;
  344. /** @type {CssModule[] | undefined} */
  345. const modules = orderedCssModulesPerChunk.get(chunk);
  346. if (modules !== undefined) {
  347. const { path: filename, info } = compilation.getPathWithInfo(
  348. CssModulesPlugin.getChunkFilenameTemplate(
  349. chunk,
  350. compilation.outputOptions
  351. ),
  352. {
  353. hash,
  354. runtime: chunk.runtime,
  355. chunk,
  356. contentHashType: "css"
  357. }
  358. );
  359. const undoPath = getUndoPath(
  360. filename,
  361. compilation.outputOptions.path,
  362. false
  363. );
  364. result.push({
  365. render: () =>
  366. this.renderChunk({
  367. chunk,
  368. chunkGraph,
  369. codeGenerationResults,
  370. uniqueName: compilation.outputOptions.uniqueName,
  371. cssHeadDataCompression:
  372. compilation.outputOptions.cssHeadDataCompression,
  373. undoPath,
  374. modules
  375. }),
  376. filename,
  377. info,
  378. identifier: `css${chunk.id}`,
  379. hash: chunk.contentHash.css
  380. });
  381. }
  382. return result;
  383. });
  384. const globalChunkLoading = compilation.outputOptions.chunkLoading;
  385. /**
  386. * @param {Chunk} chunk the chunk
  387. * @returns {boolean} true, when enabled
  388. */
  389. const isEnabledForChunk = chunk => {
  390. const options = chunk.getEntryOptions();
  391. const chunkLoading =
  392. options && options.chunkLoading !== undefined
  393. ? options.chunkLoading
  394. : globalChunkLoading;
  395. return chunkLoading === "jsonp" || chunkLoading === "import";
  396. };
  397. const onceForChunkSet = new WeakSet();
  398. /**
  399. * @param {Chunk} chunk chunk to check
  400. * @param {Set<string>} set runtime requirements
  401. */
  402. const handler = (chunk, set) => {
  403. if (onceForChunkSet.has(chunk)) return;
  404. onceForChunkSet.add(chunk);
  405. if (!isEnabledForChunk(chunk)) return;
  406. set.add(RuntimeGlobals.publicPath);
  407. set.add(RuntimeGlobals.getChunkCssFilename);
  408. set.add(RuntimeGlobals.hasOwnProperty);
  409. set.add(RuntimeGlobals.moduleFactoriesAddOnly);
  410. set.add(RuntimeGlobals.makeNamespaceObject);
  411. const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
  412. compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
  413. };
  414. compilation.hooks.runtimeRequirementInTree
  415. .for(RuntimeGlobals.hasCssModules)
  416. .tap(plugin, handler);
  417. compilation.hooks.runtimeRequirementInTree
  418. .for(RuntimeGlobals.ensureChunkHandlers)
  419. .tap(plugin, handler);
  420. compilation.hooks.runtimeRequirementInTree
  421. .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
  422. .tap(plugin, handler);
  423. }
  424. );
  425. }
  426. /**
  427. * @param {Chunk} chunk chunk
  428. * @param {Iterable<Module>} modules unordered modules
  429. * @param {Compilation} compilation compilation
  430. * @returns {Module[]} ordered modules
  431. */
  432. getModulesInOrder(chunk, modules, compilation) {
  433. if (!modules) return [];
  434. /** @type {Module[]} */
  435. const modulesList = [...modules];
  436. // Get ordered list of modules per chunk group
  437. // Lists are in reverse order to allow to use Array.pop()
  438. const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {
  439. const sortedModules = modulesList
  440. .map(module => {
  441. return {
  442. module,
  443. index: chunkGroup.getModulePostOrderIndex(module)
  444. };
  445. })
  446. .filter(item => item.index !== undefined)
  447. .sort(
  448. (a, b) =>
  449. /** @type {number} */ (b.index) - /** @type {number} */ (a.index)
  450. )
  451. .map(item => item.module);
  452. return { list: sortedModules, set: new Set(sortedModules) };
  453. });
  454. if (modulesByChunkGroup.length === 1)
  455. return modulesByChunkGroup[0].list.reverse();
  456. const compareModuleLists = ({ list: a }, { list: b }) => {
  457. if (a.length === 0) {
  458. return b.length === 0 ? 0 : 1;
  459. } else {
  460. if (b.length === 0) return -1;
  461. return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]);
  462. }
  463. };
  464. modulesByChunkGroup.sort(compareModuleLists);
  465. /** @type {Module[]} */
  466. const finalModules = [];
  467. for (;;) {
  468. const failedModules = new Set();
  469. const list = modulesByChunkGroup[0].list;
  470. if (list.length === 0) {
  471. // done, everything empty
  472. break;
  473. }
  474. /** @type {Module} */
  475. let selectedModule = list[list.length - 1];
  476. let hasFailed = undefined;
  477. outer: for (;;) {
  478. for (const { list, set } of modulesByChunkGroup) {
  479. if (list.length === 0) continue;
  480. const lastModule = list[list.length - 1];
  481. if (lastModule === selectedModule) continue;
  482. if (!set.has(selectedModule)) continue;
  483. failedModules.add(selectedModule);
  484. if (failedModules.has(lastModule)) {
  485. // There is a conflict, try other alternatives
  486. hasFailed = lastModule;
  487. continue;
  488. }
  489. selectedModule = lastModule;
  490. hasFailed = false;
  491. continue outer; // restart
  492. }
  493. break;
  494. }
  495. if (hasFailed) {
  496. // There is a not resolve-able conflict with the selectedModule
  497. // TODO print better warning
  498. compilation.warnings.push(
  499. new WebpackError(
  500. `chunk ${chunk.name || chunk.id}\nConflicting order between ${
  501. /** @type {Module} */
  502. (hasFailed).readableIdentifier(compilation.requestShortener)
  503. } and ${selectedModule.readableIdentifier(
  504. compilation.requestShortener
  505. )}`
  506. )
  507. );
  508. selectedModule = /** @type {Module} */ (hasFailed);
  509. }
  510. // Insert the selected module into the final modules list
  511. finalModules.push(selectedModule);
  512. // Remove the selected module from all lists
  513. for (const { list, set } of modulesByChunkGroup) {
  514. const lastModule = list[list.length - 1];
  515. if (lastModule === selectedModule) list.pop();
  516. else if (hasFailed && set.has(selectedModule)) {
  517. const idx = list.indexOf(selectedModule);
  518. if (idx >= 0) list.splice(idx, 1);
  519. }
  520. }
  521. modulesByChunkGroup.sort(compareModuleLists);
  522. }
  523. return finalModules;
  524. }
  525. /**
  526. * @param {Chunk} chunk chunk
  527. * @param {ChunkGraph} chunkGraph chunk graph
  528. * @param {Compilation} compilation compilation
  529. * @returns {Module[]} ordered css modules
  530. */
  531. getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
  532. return [
  533. ...this.getModulesInOrder(
  534. chunk,
  535. /** @type {Iterable<Module>} */
  536. (
  537. chunkGraph.getOrderedChunkModulesIterableBySourceType(
  538. chunk,
  539. "css-import",
  540. compareModulesByIdentifier
  541. )
  542. ),
  543. compilation
  544. ),
  545. ...this.getModulesInOrder(
  546. chunk,
  547. /** @type {Iterable<Module>} */
  548. (
  549. chunkGraph.getOrderedChunkModulesIterableBySourceType(
  550. chunk,
  551. "css",
  552. compareModulesByIdentifier
  553. )
  554. ),
  555. compilation
  556. )
  557. ];
  558. }
  559. /**
  560. * @param {object} options options
  561. * @param {string[]} options.metaData meta data
  562. * @param {string} options.undoPath undo path for public path auto
  563. * @param {Chunk} options.chunk chunk
  564. * @param {ChunkGraph} options.chunkGraph chunk graph
  565. * @param {CodeGenerationResults} options.codeGenerationResults code generation results
  566. * @param {CssModule} options.module css module
  567. * @returns {Source} css module source
  568. */
  569. renderModule({
  570. metaData,
  571. undoPath,
  572. chunk,
  573. chunkGraph,
  574. codeGenerationResults,
  575. module
  576. }) {
  577. const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
  578. const moduleSourceContent =
  579. /** @type {Source} */
  580. (
  581. codeGenResult.sources.get("css") ||
  582. codeGenResult.sources.get("css-import")
  583. );
  584. const cacheEntry = this._moduleCache.get(moduleSourceContent);
  585. /** @type {Inheritance} */
  586. let inheritance = [[module.cssLayer, module.supports, module.media]];
  587. if (module.inheritance) {
  588. inheritance.push(...module.inheritance);
  589. }
  590. let source;
  591. if (
  592. cacheEntry &&
  593. cacheEntry.undoPath === undoPath &&
  594. cacheEntry.inheritance.every(([layer, supports, media], i) => {
  595. const item = inheritance[i];
  596. if (Array.isArray(item)) {
  597. return layer === item[0] && supports === item[1] && media === item[2];
  598. }
  599. return false;
  600. })
  601. ) {
  602. source = cacheEntry.source;
  603. } else {
  604. const moduleSourceCode = /** @type {string} */ (
  605. moduleSourceContent.source()
  606. );
  607. const publicPathAutoRegex = new RegExp(
  608. CssUrlDependency.PUBLIC_PATH_AUTO,
  609. "g"
  610. );
  611. /** @type {Source} */
  612. let moduleSource = new ReplaceSource(moduleSourceContent);
  613. let match;
  614. while ((match = publicPathAutoRegex.exec(moduleSourceCode))) {
  615. /** @type {ReplaceSource} */ (moduleSource).replace(
  616. match.index,
  617. (match.index += match[0].length - 1),
  618. undoPath
  619. );
  620. }
  621. for (let i = 0; i < inheritance.length; i++) {
  622. const layer = inheritance[i][0];
  623. const supports = inheritance[i][1];
  624. const media = inheritance[i][2];
  625. if (media) {
  626. moduleSource = new ConcatSource(
  627. `@media ${media} {\n`,
  628. new PrefixSource("\t", moduleSource),
  629. "}\n"
  630. );
  631. }
  632. if (supports) {
  633. moduleSource = new ConcatSource(
  634. `@supports (${supports}) {\n`,
  635. new PrefixSource("\t", moduleSource),
  636. "}\n"
  637. );
  638. }
  639. // Layer can be anonymous
  640. if (layer !== undefined && layer !== null) {
  641. moduleSource = new ConcatSource(
  642. `@layer${layer ? ` ${layer}` : ""} {\n`,
  643. new PrefixSource("\t", moduleSource),
  644. "}\n"
  645. );
  646. }
  647. }
  648. if (moduleSource) {
  649. moduleSource = new ConcatSource(moduleSource, "\n");
  650. }
  651. source = new CachedSource(moduleSource);
  652. this._moduleCache.set(moduleSourceContent, {
  653. inheritance,
  654. undoPath,
  655. source
  656. });
  657. }
  658. /** @type {CssExportsData | undefined} */
  659. const cssExportsData =
  660. codeGenResult.data && codeGenResult.data.get("css-exports");
  661. const exports = cssExportsData && cssExportsData.exports;
  662. const esModule = cssExportsData && cssExportsData.esModule;
  663. let moduleId = chunkGraph.getModuleId(module) + "";
  664. // When `optimization.moduleIds` is `named` the module id is a path, so we need to normalize it between platforms
  665. if (typeof moduleId === "string") {
  666. moduleId = moduleId.replace(/\\/g, "/");
  667. }
  668. metaData.push(
  669. `${
  670. exports
  671. ? Array.from(
  672. exports,
  673. ([n, v]) => `${escapeCss(n)}:${escapeCss(v)}/`
  674. ).join("")
  675. : ""
  676. }${esModule ? "&" : ""}${escapeCss(moduleId)}`
  677. );
  678. return source;
  679. }
  680. /**
  681. * @param {object} options options
  682. * @param {string | undefined} options.uniqueName unique name
  683. * @param {boolean | undefined} options.cssHeadDataCompression compress css head data
  684. * @param {string} options.undoPath undo path for public path auto
  685. * @param {Chunk} options.chunk chunk
  686. * @param {ChunkGraph} options.chunkGraph chunk graph
  687. * @param {CodeGenerationResults} options.codeGenerationResults code generation results
  688. * @param {CssModule[]} options.modules ordered css modules
  689. * @returns {Source} generated source
  690. */
  691. renderChunk({
  692. uniqueName,
  693. cssHeadDataCompression,
  694. undoPath,
  695. chunk,
  696. chunkGraph,
  697. codeGenerationResults,
  698. modules
  699. }) {
  700. const source = new ConcatSource();
  701. /** @type {string[]} */
  702. const metaData = [];
  703. for (const module of modules) {
  704. try {
  705. const moduleSource = this.renderModule({
  706. metaData,
  707. undoPath,
  708. chunk,
  709. chunkGraph,
  710. codeGenerationResults,
  711. module
  712. });
  713. source.add(moduleSource);
  714. } catch (e) {
  715. /** @type {Error} */
  716. (e).message += `\nduring rendering of css ${module.identifier()}`;
  717. throw e;
  718. }
  719. }
  720. const metaDataStr = metaData.join(",");
  721. source.add(
  722. `head{--webpack-${escapeCss(
  723. (uniqueName ? uniqueName + "-" : "") + chunk.id,
  724. true
  725. )}:${cssHeadDataCompression ? LZWEncode(metaDataStr) : metaDataStr};}`
  726. );
  727. return source;
  728. }
  729. /**
  730. * @param {Chunk} chunk chunk
  731. * @param {OutputOptions} outputOptions output options
  732. * @returns {Chunk["cssFilenameTemplate"] | OutputOptions["cssFilename"] | OutputOptions["cssChunkFilename"]} used filename template
  733. */
  734. static getChunkFilenameTemplate(chunk, outputOptions) {
  735. if (chunk.cssFilenameTemplate) {
  736. return chunk.cssFilenameTemplate;
  737. } else if (chunk.canBeInitial()) {
  738. return outputOptions.cssFilename;
  739. } else {
  740. return outputOptions.cssChunkFilename;
  741. }
  742. }
  743. /**
  744. * @param {Chunk} chunk chunk
  745. * @param {ChunkGraph} chunkGraph chunk graph
  746. * @returns {boolean} true, when the chunk has css
  747. */
  748. static chunkHasCss(chunk, chunkGraph) {
  749. return (
  750. !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css") ||
  751. !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import")
  752. );
  753. }
  754. }
  755. module.exports = CssModulesPlugin;