source: frontend/node_modules/webpack/lib/css/CssModulesPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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