CssModulesPlugin.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { SyncHook, SyncWaterfallHook } = require("tapable");
  7. const {
  8. CachedSource,
  9. ConcatSource,
  10. PrefixSource,
  11. RawSource,
  12. ReplaceSource
  13. } = require("webpack-sources");
  14. const Compilation = require("../Compilation");
  15. const CssModule = require("../CssModule");
  16. const { tryRunOrWebpackError } = require("../HookWebpackError");
  17. const HotUpdateChunk = require("../HotUpdateChunk");
  18. const { CSS_IMPORT_TYPE, CSS_TYPE } = require("../ModuleSourceTypeConstants");
  19. const {
  20. CSS_MODULE_TYPE,
  21. CSS_MODULE_TYPE_AUTO,
  22. CSS_MODULE_TYPE_GLOBAL,
  23. CSS_MODULE_TYPE_MODULE
  24. } = require("../ModuleTypeConstants");
  25. const NormalModule = require("../NormalModule");
  26. const RuntimeGlobals = require("../RuntimeGlobals");
  27. const Template = require("../Template");
  28. const WebpackError = require("../WebpackError");
  29. const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
  30. const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
  31. const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
  32. const CssImportDependency = require("../dependencies/CssImportDependency");
  33. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  34. const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
  35. const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
  36. const { compareModulesByFullName } = require("../util/comparators");
  37. const createHash = require("../util/createHash");
  38. const { getUndoPath } = require("../util/identifier");
  39. const memoize = require("../util/memoize");
  40. const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
  41. const removeBOM = require("../util/removeBOM");
  42. const CssGenerator = require("./CssGenerator");
  43. const CssParser = require("./CssParser");
  44. const publicPathAutoRegex = new RegExp(CssUrlDependency.PUBLIC_PATH_AUTO, "g");
  45. /** @typedef {import("webpack-sources").Source} Source */
  46. /** @typedef {import("../config/defaults").OutputNormalizedWithDefaults} OutputOptions */
  47. /** @typedef {import("../Chunk")} Chunk */
  48. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  49. /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
  50. /** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
  51. /** @typedef {import("../Compiler")} Compiler */
  52. /** @typedef {import("../CssModule").Inheritance} Inheritance */
  53. /** @typedef {import("../CssModule").CSSModuleCreateData} CSSModuleCreateData */
  54. /** @typedef {import("../Module")} Module */
  55. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  56. /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
  57. /** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */
  58. /** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */
  59. /** @typedef {import("../util/Hash")} Hash */
  60. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  61. /**
  62. * Defines the render context type used by this module.
  63. * @typedef {object} RenderContext
  64. * @property {Chunk} chunk the chunk
  65. * @property {ChunkGraph} chunkGraph the chunk graph
  66. * @property {CodeGenerationResults} codeGenerationResults results of code generation
  67. * @property {RuntimeTemplate} runtimeTemplate the runtime template
  68. * @property {string} uniqueName the unique name
  69. * @property {string} undoPath undo path to css file
  70. * @property {CssModule[]} modules modules
  71. */
  72. /**
  73. * Defines the chunk render context type used by this module.
  74. * @typedef {object} ChunkRenderContext
  75. * @property {Chunk=} chunk the chunk
  76. * @property {ChunkGraph=} chunkGraph the chunk graph
  77. * @property {CodeGenerationResults=} codeGenerationResults results of code generation
  78. * @property {RuntimeTemplate} runtimeTemplate the runtime template
  79. * @property {string} undoPath undo path to css file
  80. * @property {WeakMap<Source, ModuleFactoryCacheEntry>} moduleFactoryCache moduleFactoryCache
  81. * @property {Source} moduleSourceContent content
  82. */
  83. /**
  84. * Defines the compilation hooks type used by this module.
  85. * @typedef {object} CompilationHooks
  86. * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage
  87. * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash
  88. */
  89. /**
  90. * Defines the module factory cache entry type used by this module.
  91. * @typedef {object} ModuleFactoryCacheEntry
  92. * @property {string} undoPath - The undo path to the CSS file
  93. * @property {Inheritance} inheritance - The inheritance chain
  94. * @property {CachedSource} source - The cached source
  95. */
  96. const getCssLoadingRuntimeModule = memoize(() =>
  97. require("./CssLoadingRuntimeModule")
  98. );
  99. const getCssMergeStyleSheetsRuntimeModule = memoize(() =>
  100. require("./CssMergeStyleSheetsRuntimeModule")
  101. );
  102. const getCssInjectStyleRuntimeModule = memoize(() =>
  103. require("./CssInjectStyleRuntimeModule")
  104. );
  105. /**
  106. * Returns ], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema.
  107. * @param {string} name name
  108. * @returns {{ oneOf: [{ $ref: string }], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema
  109. */
  110. const getSchema = (name) => {
  111. const { definitions } = require("../../schemas/WebpackOptions.json");
  112. return {
  113. definitions,
  114. oneOf: [{ $ref: `#/definitions/${name}` }]
  115. };
  116. };
  117. const parserValidationOptions = {
  118. name: "Css Modules Plugin",
  119. baseDataPath: "parser"
  120. };
  121. const generatorValidationOptions = {
  122. name: "Css Modules Plugin",
  123. baseDataPath: "generator"
  124. };
  125. /** @type {WeakMap<Compilation, CompilationHooks>} */
  126. const compilationHooksMap = new WeakMap();
  127. const PLUGIN_NAME = "CssModulesPlugin";
  128. class CssModulesPlugin {
  129. /**
  130. * Returns the attached hooks.
  131. * @param {Compilation} compilation the compilation
  132. * @returns {CompilationHooks} the attached hooks
  133. */
  134. static getCompilationHooks(compilation) {
  135. if (!(compilation instanceof Compilation)) {
  136. throw new TypeError(
  137. "The 'compilation' argument must be an instance of Compilation"
  138. );
  139. }
  140. let hooks = compilationHooksMap.get(compilation);
  141. if (hooks === undefined) {
  142. hooks = {
  143. renderModulePackage: new SyncWaterfallHook([
  144. "source",
  145. "module",
  146. "renderContext"
  147. ]),
  148. chunkHash: new SyncHook(["chunk", "hash", "context"])
  149. };
  150. compilationHooksMap.set(compilation, hooks);
  151. }
  152. return hooks;
  153. }
  154. constructor() {
  155. /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
  156. this._moduleFactoryCache = new WeakMap();
  157. }
  158. /**
  159. * Applies the plugin by registering its hooks on the compiler.
  160. * @param {Compiler} compiler the compiler instance
  161. * @returns {void}
  162. */
  163. apply(compiler) {
  164. compiler.hooks.compilation.tap(
  165. PLUGIN_NAME,
  166. (compilation, { normalModuleFactory }) => {
  167. const hooks = CssModulesPlugin.getCompilationHooks(compilation);
  168. compilation.dependencyFactories.set(
  169. CssImportDependency,
  170. normalModuleFactory
  171. );
  172. compilation.dependencyTemplates.set(
  173. CssImportDependency,
  174. new CssImportDependency.Template()
  175. );
  176. compilation.dependencyFactories.set(
  177. CssUrlDependency,
  178. normalModuleFactory
  179. );
  180. compilation.dependencyTemplates.set(
  181. CssUrlDependency,
  182. new CssUrlDependency.Template()
  183. );
  184. compilation.dependencyFactories.set(
  185. CssIcssImportDependency,
  186. normalModuleFactory
  187. );
  188. compilation.dependencyTemplates.set(
  189. CssIcssImportDependency,
  190. new CssIcssImportDependency.Template()
  191. );
  192. compilation.dependencyTemplates.set(
  193. CssIcssExportDependency,
  194. new CssIcssExportDependency.Template()
  195. );
  196. compilation.dependencyTemplates.set(
  197. CssIcssSymbolDependency,
  198. new CssIcssSymbolDependency.Template()
  199. );
  200. compilation.dependencyTemplates.set(
  201. StaticExportsDependency,
  202. new StaticExportsDependency.Template()
  203. );
  204. for (const type of [
  205. CSS_MODULE_TYPE,
  206. CSS_MODULE_TYPE_GLOBAL,
  207. CSS_MODULE_TYPE_MODULE,
  208. CSS_MODULE_TYPE_AUTO
  209. ]) {
  210. normalModuleFactory.hooks.createParser
  211. .for(type)
  212. .tap(PLUGIN_NAME, (parserOptions) => {
  213. /** @type {undefined | "global" | "local" | "auto"} */
  214. let defaultMode;
  215. switch (type) {
  216. case CSS_MODULE_TYPE: {
  217. compiler.validate(
  218. () => getSchema("CssParserOptions"),
  219. parserOptions,
  220. parserValidationOptions,
  221. (options) =>
  222. require("../../schemas/plugins/css/CssParserOptions.check")(
  223. options
  224. )
  225. );
  226. break;
  227. }
  228. case CSS_MODULE_TYPE_GLOBAL: {
  229. defaultMode = "global";
  230. compiler.validate(
  231. () => getSchema("CssModuleParserOptions"),
  232. parserOptions,
  233. parserValidationOptions,
  234. (options) =>
  235. require("../../schemas/plugins/css/CssModuleParserOptions.check")(
  236. options
  237. )
  238. );
  239. break;
  240. }
  241. case CSS_MODULE_TYPE_MODULE: {
  242. defaultMode = "local";
  243. compiler.validate(
  244. () => getSchema("CssModuleParserOptions"),
  245. parserOptions,
  246. parserValidationOptions,
  247. (options) =>
  248. require("../../schemas/plugins/css/CssModuleParserOptions.check")(
  249. options
  250. )
  251. );
  252. break;
  253. }
  254. case CSS_MODULE_TYPE_AUTO: {
  255. defaultMode = "auto";
  256. compiler.validate(
  257. () => getSchema("CssModuleParserOptions"),
  258. parserOptions,
  259. parserValidationOptions,
  260. (options) =>
  261. require("../../schemas/plugins/css/CssModuleParserOptions.check")(
  262. options
  263. )
  264. );
  265. break;
  266. }
  267. }
  268. return new CssParser({
  269. defaultMode,
  270. ...parserOptions
  271. });
  272. });
  273. normalModuleFactory.hooks.createGenerator
  274. .for(type)
  275. .tap(PLUGIN_NAME, (generatorOptions) => {
  276. switch (type) {
  277. case CSS_MODULE_TYPE: {
  278. compiler.validate(
  279. () => getSchema("CssGeneratorOptions"),
  280. generatorOptions,
  281. generatorValidationOptions,
  282. (options) =>
  283. require("../../schemas/plugins/css/CssGeneratorOptions.check")(
  284. options
  285. )
  286. );
  287. break;
  288. }
  289. case CSS_MODULE_TYPE_GLOBAL: {
  290. compiler.validate(
  291. () => getSchema("CssModuleGeneratorOptions"),
  292. generatorOptions,
  293. generatorValidationOptions,
  294. (options) =>
  295. require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
  296. options
  297. )
  298. );
  299. break;
  300. }
  301. case CSS_MODULE_TYPE_MODULE: {
  302. compiler.validate(
  303. () => getSchema("CssModuleGeneratorOptions"),
  304. generatorOptions,
  305. generatorValidationOptions,
  306. (options) =>
  307. require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
  308. options
  309. )
  310. );
  311. break;
  312. }
  313. case CSS_MODULE_TYPE_AUTO: {
  314. compiler.validate(
  315. () => getSchema("CssModuleGeneratorOptions"),
  316. generatorOptions,
  317. generatorValidationOptions,
  318. (options) =>
  319. require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
  320. options
  321. )
  322. );
  323. break;
  324. }
  325. }
  326. return new CssGenerator(
  327. generatorOptions,
  328. compilation.moduleGraph
  329. );
  330. });
  331. normalModuleFactory.hooks.createModuleClass
  332. .for(type)
  333. .tap(PLUGIN_NAME, (createData, resolveData) => {
  334. const exportType =
  335. /** @type {CssParser} */
  336. (createData.parser).options.exportType;
  337. if (resolveData.dependencies.length > 0) {
  338. // When CSS is imported from CSS there is only one dependency
  339. const dependency = resolveData.dependencies[0];
  340. if (dependency instanceof CssImportDependency) {
  341. const parent =
  342. /** @type {CssModule} */
  343. (compilation.moduleGraph.getParentModule(dependency));
  344. if (parent instanceof CssModule) {
  345. /** @type {Inheritance | undefined} */
  346. let inheritance;
  347. if (
  348. parent.cssLayer !== undefined ||
  349. parent.supports ||
  350. parent.media
  351. ) {
  352. if (!inheritance) {
  353. inheritance = [];
  354. }
  355. inheritance.push([
  356. parent.cssLayer,
  357. parent.supports,
  358. parent.media
  359. ]);
  360. }
  361. if (parent.inheritance) {
  362. if (!inheritance) {
  363. inheritance = [];
  364. }
  365. inheritance.push(...parent.inheritance);
  366. }
  367. return new CssModule(
  368. /** @type {CSSModuleCreateData} */
  369. ({
  370. ...createData,
  371. cssLayer: dependency.layer,
  372. supports: dependency.supports,
  373. media: dependency.media,
  374. inheritance,
  375. exportType: parent.exportType || exportType
  376. })
  377. );
  378. }
  379. return new CssModule(
  380. /** @type {CSSModuleCreateData} */
  381. ({
  382. ...createData,
  383. cssLayer: dependency.layer,
  384. supports: dependency.supports,
  385. media: dependency.media,
  386. exportType
  387. })
  388. );
  389. }
  390. }
  391. return new CssModule(
  392. /** @type {CSSModuleCreateData} */
  393. (
  394. /** @type {unknown} */ ({
  395. ...createData,
  396. exportType
  397. })
  398. )
  399. );
  400. });
  401. NormalModule.getCompilationHooks(compilation).processResult.tap(
  402. PLUGIN_NAME,
  403. (result, module) => {
  404. if (module.type === type) {
  405. const [source, ...rest] = result;
  406. return [removeBOM(source), ...rest];
  407. }
  408. return result;
  409. }
  410. );
  411. }
  412. JavascriptModulesPlugin.getCompilationHooks(
  413. compilation
  414. ).renderModuleContent.tap(PLUGIN_NAME, (source, module) => {
  415. if (module instanceof CssModule && module.hot) {
  416. const exportType = module.exportType;
  417. // When exportType !== "link", modules behave like JavaScript modules
  418. if (exportType && !["link", "style"].includes(exportType)) {
  419. return source;
  420. }
  421. // For exportType === "link", we can optimize with self-acceptance
  422. const cssData = /** @type {BuildInfo} */ (module.buildInfo).cssData;
  423. if (!cssData) {
  424. return source;
  425. }
  426. const exports = cssData.exports;
  427. /** @type {Record<string, string>} */
  428. const exportsObj = {};
  429. for (const [key, value] of exports) {
  430. exportsObj[key] = value;
  431. }
  432. const stringifiedExports = JSON.stringify(
  433. JSON.stringify(exportsObj)
  434. );
  435. const hmrCode = Template.asString([
  436. "",
  437. `var __webpack_css_exports__ = ${stringifiedExports};`,
  438. "// only invalidate when locals change",
  439. "if (module.hot.data && module.hot.data.__webpack_css_exports__ && module.hot.data.__webpack_css_exports__ != __webpack_css_exports__) {",
  440. Template.indent("module.hot.invalidate();"),
  441. "} else {",
  442. Template.indent("module.hot.accept();"),
  443. "}",
  444. "module.hot.dispose(function(data) {",
  445. Template.indent([
  446. "data.__webpack_css_exports__ = __webpack_css_exports__;"
  447. ]),
  448. "});"
  449. ]);
  450. return new ConcatSource(source, "\n", new RawSource(hmrCode));
  451. }
  452. return source;
  453. });
  454. /** @type {WeakMap<Chunk, CssModule[]>} */
  455. const orderedCssModulesPerChunk = new WeakMap();
  456. compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => {
  457. const { chunkGraph } = compilation;
  458. for (const chunk of compilation.chunks) {
  459. if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
  460. orderedCssModulesPerChunk.set(
  461. chunk,
  462. this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
  463. );
  464. }
  465. }
  466. });
  467. compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => {
  468. hooks.chunkHash.call(chunk, hash, context);
  469. });
  470. compilation.hooks.contentHash.tap(PLUGIN_NAME, (chunk) => {
  471. const {
  472. chunkGraph,
  473. moduleGraph,
  474. runtimeTemplate,
  475. outputOptions: {
  476. hashSalt,
  477. hashDigest,
  478. hashDigestLength,
  479. hashFunction
  480. }
  481. } = compilation;
  482. const hash = createHash(hashFunction);
  483. if (hashSalt) hash.update(hashSalt);
  484. const codeGenerationResults =
  485. /** @type {CodeGenerationResults} */
  486. (compilation.codeGenerationResults);
  487. hooks.chunkHash.call(chunk, hash, {
  488. chunkGraph,
  489. codeGenerationResults,
  490. moduleGraph,
  491. runtimeTemplate
  492. });
  493. const modules = orderedCssModulesPerChunk.get(chunk);
  494. if (modules) {
  495. for (const module of modules) {
  496. hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
  497. }
  498. }
  499. const digest = hash.digest(hashDigest);
  500. chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
  501. });
  502. compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
  503. const { chunkGraph } = compilation;
  504. const { hash, chunk, codeGenerationResults, runtimeTemplate } =
  505. options;
  506. if (chunk instanceof HotUpdateChunk) return result;
  507. /** @type {CssModule[] | undefined} */
  508. const modules = orderedCssModulesPerChunk.get(chunk);
  509. if (modules !== undefined) {
  510. const { path: filename, info } = compilation.getPathWithInfo(
  511. CssModulesPlugin.getChunkFilenameTemplate(
  512. chunk,
  513. compilation.outputOptions
  514. ),
  515. {
  516. hash,
  517. runtime: chunk.runtime,
  518. chunk,
  519. contentHashType: "css"
  520. }
  521. );
  522. const undoPath = getUndoPath(
  523. filename,
  524. compilation.outputOptions.path,
  525. false
  526. );
  527. result.push({
  528. render: () =>
  529. this.renderChunk(
  530. {
  531. chunk,
  532. chunkGraph,
  533. codeGenerationResults,
  534. uniqueName: compilation.outputOptions.uniqueName,
  535. undoPath,
  536. modules,
  537. runtimeTemplate
  538. },
  539. hooks
  540. ),
  541. filename,
  542. info,
  543. identifier: `css${chunk.id}`,
  544. hash: chunk.contentHash.css
  545. });
  546. }
  547. return result;
  548. });
  549. const globalChunkLoading = compilation.outputOptions.chunkLoading;
  550. /**
  551. * Checks whether this css modules plugin is enabled for chunk.
  552. * @param {Chunk} chunk the chunk
  553. * @returns {boolean} true, when enabled
  554. */
  555. const isEnabledForChunk = (chunk) => {
  556. const options = chunk.getEntryOptions();
  557. const chunkLoading =
  558. options && options.chunkLoading !== undefined
  559. ? options.chunkLoading
  560. : globalChunkLoading;
  561. return chunkLoading === "jsonp" || chunkLoading === "import";
  562. };
  563. /** @type {WeakSet<Chunk>} */
  564. const onceForChunkSet = new WeakSet();
  565. /**
  566. * Handles the hook callback for this code path.
  567. * @param {Chunk} chunk chunk to check
  568. * @param {RuntimeRequirements} set runtime requirements
  569. */
  570. const handler = (chunk, set) => {
  571. if (onceForChunkSet.has(chunk)) return;
  572. onceForChunkSet.add(chunk);
  573. if (!isEnabledForChunk(chunk)) return;
  574. const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
  575. compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
  576. };
  577. compilation.hooks.runtimeRequirementInTree
  578. .for(RuntimeGlobals.hasCssModules)
  579. .tap(PLUGIN_NAME, handler);
  580. compilation.hooks.runtimeRequirementInTree
  581. .for(RuntimeGlobals.ensureChunkHandlers)
  582. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  583. if (!isEnabledForChunk(chunk)) return;
  584. if (
  585. !chunkGraph.hasModuleInGraph(
  586. chunk,
  587. (m) =>
  588. m.type === CSS_MODULE_TYPE ||
  589. m.type === CSS_MODULE_TYPE_GLOBAL ||
  590. m.type === CSS_MODULE_TYPE_MODULE ||
  591. m.type === CSS_MODULE_TYPE_AUTO
  592. )
  593. ) {
  594. return;
  595. }
  596. set.add(RuntimeGlobals.hasOwnProperty);
  597. set.add(RuntimeGlobals.publicPath);
  598. set.add(RuntimeGlobals.getChunkCssFilename);
  599. });
  600. compilation.hooks.runtimeRequirementInTree
  601. .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
  602. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  603. if (!isEnabledForChunk(chunk)) return;
  604. if (
  605. !chunkGraph.hasModuleInGraph(
  606. chunk,
  607. (m) =>
  608. m.type === CSS_MODULE_TYPE ||
  609. m.type === CSS_MODULE_TYPE_GLOBAL ||
  610. m.type === CSS_MODULE_TYPE_MODULE ||
  611. m.type === CSS_MODULE_TYPE_AUTO
  612. )
  613. ) {
  614. return;
  615. }
  616. set.add(RuntimeGlobals.publicPath);
  617. set.add(RuntimeGlobals.getChunkCssFilename);
  618. });
  619. compilation.hooks.runtimeRequirementInTree
  620. .for(RuntimeGlobals.cssMergeStyleSheets)
  621. .tap(PLUGIN_NAME, (chunk) => {
  622. const CssMergeStyleSheetsRuntimeModule =
  623. getCssMergeStyleSheetsRuntimeModule();
  624. compilation.addRuntimeModule(
  625. chunk,
  626. new CssMergeStyleSheetsRuntimeModule()
  627. );
  628. });
  629. compilation.hooks.runtimeRequirementInTree
  630. .for(RuntimeGlobals.cssInjectStyle)
  631. .tap(PLUGIN_NAME, (chunk, set) => {
  632. const CssInjectStyleRuntimeModule =
  633. getCssInjectStyleRuntimeModule();
  634. compilation.addRuntimeModule(
  635. chunk,
  636. new CssInjectStyleRuntimeModule(set)
  637. );
  638. });
  639. }
  640. );
  641. }
  642. /**
  643. * Gets modules in order.
  644. * @param {Chunk} chunk chunk
  645. * @param {Iterable<Module> | undefined} modules unordered modules
  646. * @param {Compilation} compilation compilation
  647. * @returns {Module[]} ordered modules
  648. */
  649. getModulesInOrder(chunk, modules, compilation) {
  650. if (!modules) return [];
  651. /** @type {Module[]} */
  652. const modulesList = [...modules];
  653. // Get ordered list of modules per chunk group
  654. // Lists are in reverse order to allow to use Array.pop()
  655. const modulesByChunkGroup = Array.from(
  656. chunk.groupsIterable,
  657. (chunkGroup) => {
  658. const sortedModules = modulesList
  659. .map((module) => ({
  660. module,
  661. index: chunkGroup.getModulePostOrderIndex(module)
  662. }))
  663. .filter((item) => item.index !== undefined)
  664. .sort(
  665. (a, b) =>
  666. /** @type {number} */ (b.index) - /** @type {number} */ (a.index)
  667. )
  668. .map((item) => item.module);
  669. return { list: sortedModules, set: new Set(sortedModules) };
  670. }
  671. );
  672. if (modulesByChunkGroup.length === 1) {
  673. return modulesByChunkGroup[0].list.reverse();
  674. }
  675. const boundCompareModulesByFullName = compareModulesByFullName(
  676. compilation.compiler
  677. );
  678. /**
  679. * Compares module lists.
  680. * @param {{ list: Module[] }} a a
  681. * @param {{ list: Module[] }} b b
  682. * @returns {-1 | 0 | 1} result
  683. */
  684. const compareModuleLists = ({ list: a }, { list: b }) => {
  685. if (a.length === 0) {
  686. return b.length === 0 ? 0 : 1;
  687. }
  688. if (b.length === 0) return -1;
  689. return boundCompareModulesByFullName(a[a.length - 1], b[b.length - 1]);
  690. };
  691. modulesByChunkGroup.sort(compareModuleLists);
  692. /** @type {Module[]} */
  693. const finalModules = [];
  694. for (;;) {
  695. /** @type {Set<Module>} */
  696. const failedModules = new Set();
  697. const list = modulesByChunkGroup[0].list;
  698. if (list.length === 0) {
  699. // done, everything empty
  700. break;
  701. }
  702. /** @type {Module} */
  703. let selectedModule = list[list.length - 1];
  704. /** @type {undefined | false | Module} */
  705. let hasFailed;
  706. outer: for (;;) {
  707. for (const { list, set } of modulesByChunkGroup) {
  708. if (list.length === 0) continue;
  709. const lastModule = list[list.length - 1];
  710. if (lastModule === selectedModule) continue;
  711. if (!set.has(selectedModule)) continue;
  712. failedModules.add(selectedModule);
  713. if (failedModules.has(lastModule)) {
  714. // There is a conflict, try other alternatives
  715. hasFailed = lastModule;
  716. continue;
  717. }
  718. selectedModule = lastModule;
  719. hasFailed = false;
  720. continue outer; // restart
  721. }
  722. break;
  723. }
  724. if (hasFailed) {
  725. const fallbackModule = /** @type {Module} */ (hasFailed);
  726. const fallbackIssuers = [
  727. ...compilation.moduleGraph
  728. .getIncomingConnectionsByOriginModule(fallbackModule)
  729. .keys()
  730. ].filter(Boolean);
  731. const selectedIssuers = [
  732. ...compilation.moduleGraph
  733. .getIncomingConnectionsByOriginModule(selectedModule)
  734. .keys()
  735. ].filter(Boolean);
  736. const allIssuers = [
  737. ...new Set([...fallbackIssuers, ...selectedIssuers])
  738. ]
  739. .map((m) =>
  740. /** @type {Module} */ (m).readableIdentifier(
  741. compilation.requestShortener
  742. )
  743. )
  744. .sort();
  745. // There is a not resolve-able conflict with the selectedModule
  746. compilation.warnings.push(
  747. new WebpackError(
  748. `chunk ${
  749. chunk.name || chunk.id
  750. }\nConflicting order between ${fallbackModule.readableIdentifier(
  751. compilation.requestShortener
  752. )} and ${selectedModule.readableIdentifier(
  753. compilation.requestShortener
  754. )}\nCSS modules are imported in:\n - ${allIssuers.join("\n - ")}`
  755. )
  756. );
  757. selectedModule = fallbackModule;
  758. }
  759. // Insert the selected module into the final modules list
  760. finalModules.push(selectedModule);
  761. // Remove the selected module from all lists
  762. for (const { list, set } of modulesByChunkGroup) {
  763. const lastModule = list[list.length - 1];
  764. if (lastModule === selectedModule) {
  765. list.pop();
  766. } else if (hasFailed && set.has(selectedModule)) {
  767. const idx = list.indexOf(selectedModule);
  768. if (idx >= 0) list.splice(idx, 1);
  769. }
  770. }
  771. modulesByChunkGroup.sort(compareModuleLists);
  772. }
  773. return finalModules;
  774. }
  775. /**
  776. * Gets ordered chunk css modules.
  777. * @param {Chunk} chunk chunk
  778. * @param {ChunkGraph} chunkGraph chunk graph
  779. * @param {Compilation} compilation compilation
  780. * @returns {CssModule[]} ordered css modules
  781. */
  782. getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
  783. /** @type {string | undefined} */
  784. let charset;
  785. return /** @type {CssModule[]} */ ([
  786. ...this.getModulesInOrder(
  787. chunk,
  788. chunkGraph.getOrderedChunkModulesIterableBySourceType(
  789. chunk,
  790. CSS_IMPORT_TYPE,
  791. compareModulesByFullName(compilation.compiler)
  792. ),
  793. compilation
  794. ),
  795. ...this.getModulesInOrder(
  796. chunk,
  797. chunkGraph.getOrderedChunkModulesIterableBySourceType(
  798. chunk,
  799. CSS_TYPE,
  800. compareModulesByFullName(compilation.compiler)
  801. ),
  802. compilation
  803. ).map((module) => {
  804. if (
  805. typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
  806. "undefined"
  807. ) {
  808. if (
  809. typeof charset !== "undefined" &&
  810. charset !== /** @type {BuildInfo} */ (module.buildInfo).charset
  811. ) {
  812. const err = new WebpackError(
  813. `Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
  814. compilation.requestShortener
  815. )} (in chunk ${chunk.name || chunk.id}) specifies "${
  816. /** @type {BuildInfo} */ (module.buildInfo).charset
  817. }", but "${charset}" was expected, all modules must use the same character set`
  818. );
  819. err.chunk = chunk;
  820. err.module = module;
  821. err.hideStack = true;
  822. compilation.warnings.push(err);
  823. }
  824. if (typeof charset === "undefined") {
  825. charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
  826. }
  827. }
  828. return module;
  829. })
  830. ]);
  831. }
  832. /**
  833. * Renders css module source.
  834. * @param {CssModule} module css module
  835. * @param {ChunkRenderContext} renderContext options object
  836. * @param {CompilationHooks} hooks hooks
  837. * @returns {Source | null} css module source
  838. */
  839. static renderModule(module, renderContext, hooks) {
  840. const { undoPath, moduleFactoryCache, moduleSourceContent } = renderContext;
  841. const cacheEntry = moduleFactoryCache.get(moduleSourceContent);
  842. /** @type {Inheritance} */
  843. const inheritance = [[module.cssLayer, module.supports, module.media]];
  844. if (module.inheritance) {
  845. inheritance.push(...module.inheritance);
  846. }
  847. /** @type {CachedSource} */
  848. let source;
  849. if (
  850. cacheEntry &&
  851. cacheEntry.undoPath === undoPath &&
  852. cacheEntry.inheritance.length === inheritance.length &&
  853. cacheEntry.inheritance.every(([layer, supports, media], i) => {
  854. const item = inheritance[i];
  855. if (Array.isArray(item)) {
  856. return layer === item[0] && supports === item[1] && media === item[2];
  857. }
  858. return false;
  859. })
  860. ) {
  861. source = cacheEntry.source;
  862. } else {
  863. if (!moduleSourceContent) return null;
  864. const moduleSourceCode =
  865. /** @type {string} */
  866. (moduleSourceContent.source());
  867. publicPathAutoRegex.lastIndex = 0;
  868. /** @type {Source} */
  869. let moduleSource = new ReplaceSource(moduleSourceContent);
  870. /** @type {null | RegExpExecArray} */
  871. let match;
  872. while ((match = publicPathAutoRegex.exec(moduleSourceCode))) {
  873. /** @type {ReplaceSource} */ (moduleSource).replace(
  874. match.index,
  875. match.index + match[0].length - 1,
  876. undoPath
  877. );
  878. }
  879. for (let i = 0; i < inheritance.length; i++) {
  880. const layer = inheritance[i][0];
  881. const supports = inheritance[i][1];
  882. const media = inheritance[i][2];
  883. if (media) {
  884. moduleSource = new ConcatSource(
  885. `@media ${media} {\n`,
  886. new PrefixSource("\t", moduleSource),
  887. "}\n"
  888. );
  889. }
  890. if (supports) {
  891. moduleSource = new ConcatSource(
  892. `@supports (${supports}) {\n`,
  893. new PrefixSource("\t", moduleSource),
  894. "}\n"
  895. );
  896. }
  897. // Layer can be anonymous
  898. if (layer !== undefined && layer !== null) {
  899. moduleSource = new ConcatSource(
  900. `@layer${layer ? ` ${layer}` : ""} {\n`,
  901. new PrefixSource("\t", moduleSource),
  902. "}\n"
  903. );
  904. }
  905. }
  906. if (moduleSource) {
  907. moduleSource = new ConcatSource(moduleSource, "\n");
  908. }
  909. source = new CachedSource(moduleSource);
  910. moduleFactoryCache.set(moduleSourceContent, {
  911. inheritance,
  912. undoPath,
  913. source
  914. });
  915. }
  916. return tryRunOrWebpackError(
  917. () => hooks.renderModulePackage.call(source, module, renderContext),
  918. "CssModulesPlugin.getCompilationHooks().renderModulePackage"
  919. );
  920. }
  921. /**
  922. * Renders generated source.
  923. * @param {RenderContext} renderContext the render context
  924. * @param {CompilationHooks} hooks hooks
  925. * @returns {Source} generated source
  926. */
  927. renderChunk(
  928. {
  929. undoPath,
  930. chunk,
  931. codeGenerationResults,
  932. modules,
  933. runtimeTemplate,
  934. chunkGraph
  935. },
  936. hooks
  937. ) {
  938. const source = new ConcatSource();
  939. /** @type {string | undefined} */
  940. let charset;
  941. for (const module of modules) {
  942. if (
  943. typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
  944. "undefined" &&
  945. typeof charset === "undefined"
  946. ) {
  947. charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
  948. }
  949. try {
  950. const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
  951. const moduleSourceContent =
  952. /** @type {Source} */
  953. (
  954. codeGenResult.sources.get(CSS_TYPE) ||
  955. codeGenResult.sources.get(CSS_IMPORT_TYPE)
  956. );
  957. const moduleSource = CssModulesPlugin.renderModule(
  958. module,
  959. {
  960. undoPath,
  961. chunk,
  962. chunkGraph,
  963. codeGenerationResults,
  964. moduleSourceContent,
  965. moduleFactoryCache: this._moduleFactoryCache,
  966. runtimeTemplate
  967. },
  968. hooks
  969. );
  970. if (moduleSource) {
  971. source.add(moduleSource);
  972. }
  973. } catch (err) {
  974. /** @type {Error} */
  975. (err).message += `\nduring rendering of css ${module.identifier()}`;
  976. throw err;
  977. }
  978. }
  979. chunk.rendered = true;
  980. if (charset) {
  981. return new ConcatSource(`@charset "${charset}";\n`, source);
  982. }
  983. return source;
  984. }
  985. /**
  986. * Gets chunk filename template.
  987. * @param {Chunk} chunk chunk
  988. * @param {OutputOptions} outputOptions output options
  989. * @returns {TemplatePath} used filename template
  990. */
  991. static getChunkFilenameTemplate(chunk, outputOptions) {
  992. if (chunk.cssFilenameTemplate) {
  993. return chunk.cssFilenameTemplate;
  994. } else if (chunk.canBeInitial()) {
  995. return outputOptions.cssFilename;
  996. }
  997. return outputOptions.cssChunkFilename;
  998. }
  999. /**
  1000. * Returns true, when the chunk has css.
  1001. * @param {Chunk} chunk chunk
  1002. * @param {ChunkGraph} chunkGraph chunk graph
  1003. * @returns {boolean} true, when the chunk has css
  1004. */
  1005. static chunkHasCss(chunk, chunkGraph) {
  1006. return (
  1007. Boolean(
  1008. chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)
  1009. ) ||
  1010. Boolean(
  1011. chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
  1012. )
  1013. );
  1014. }
  1015. }
  1016. module.exports = CssModulesPlugin;