source: frontend/node_modules/webpack/lib/css/CssGenerator.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: 29.5 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Sergey Melyukov @smelukov
4*/
5
6"use strict";
7
8const {
9 ConcatSource,
10 OriginalSource,
11 RawSource,
12 ReplaceSource,
13 SourceMapSource
14} = require("webpack-sources");
15const { UsageState } = require("../ExportsInfo");
16const Generator = require("../Generator");
17const InitFragment = require("../InitFragment");
18const {
19 CSS_TEXT_TYPE,
20 CSS_TEXT_TYPES,
21 CSS_TYPE,
22 CSS_TYPES,
23 JAVASCRIPT_AND_CSS_TEXT_TYPES,
24 JAVASCRIPT_AND_CSS_TYPES,
25 JAVASCRIPT_TYPE,
26 JAVASCRIPT_TYPES
27} = require("../ModuleSourceTypeConstants");
28const RuntimeGlobals = require("../RuntimeGlobals");
29const Template = require("../Template");
30const CssImportDependency = require("../dependencies/CssImportDependency");
31const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
32
33const { encodeMappings } = require("../util/createMappings");
34const memoize = require("../util/memoize");
35
36/** @typedef {import("webpack-sources").Source} Source */
37/** @typedef {import("../../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */
38/** @typedef {import("../Compilation").DependencyConstructor} DependencyConstructor */
39/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
40/** @typedef {import("../Dependency")} Dependency */
41/** @typedef {import("../DependencyTemplate").CssData} CssData */
42/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
43/** @typedef {import("../Generator").GenerateContext} GenerateContext */
44/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
45/** @typedef {import("../Module").BuildInfo} BuildInfo */
46/** @typedef {import("../Module").BuildMeta} BuildMeta */
47/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
48/** @typedef {import("../Module").SourceType} SourceType */
49/** @typedef {import("../Module").SourceTypes} SourceTypes */
50/** @typedef {import("../ModuleGraph")} ModuleGraph */
51/** @typedef {import("../NormalModule")} NormalModule */
52/** @typedef {import("../util/Hash")} Hash */
53/** @typedef {import("./CssModulesPlugin").ModuleFactoryCacheEntry} ModuleFactoryCacheEntry */
54/** @typedef {import("./CssModule")} CssModule */
55/** @typedef {import("../Compilation")} Compilation */
56/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
57/** @typedef {import("../../declarations/WebpackOptions").CssParserExportType} CssParserExportType */
58
59/** @typedef {{ line: number, column: number }} SourcePosition */
60/** @typedef {Map<string, SourcePosition>} ExportLocsMap */
61
62const getPropertyName = memoize(() => require("../util/property"));
63const getCssModulesPlugin = memoize(() => require("./CssModulesPlugin"));
64
65/** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */
66
67/**
68 * Build a v3 source map that maps each line in `generatedJs` containing a
69 * known CSS-class export entry back to the corresponding selector position
70 * in the original CSS. Lines without an associated export are left
71 * unmapped — devtools simply shows them as part of the bundled JS.
72 * @param {string} generatedJs the generated JS string
73 * @param {ExportLocsMap} exportLocs map of export names to CSS source location
74 * @param {string} cssContent original CSS source content
75 * @param {string} sourceName source identifier to use in the map
76 * @returns {RawSourceMap} a v3 RawSourceMap
77 */
78const buildExportsSourceMap = (
79 generatedJs,
80 exportLocs,
81 cssContent,
82 sourceName
83) => {
84 const lines = generatedJs.split("\n");
85
86 const lineByExport = new Map();
87 for (const [exportName] of exportLocs) {
88 const needle = `${JSON.stringify(exportName)}:`;
89 for (let i = 0; i < lines.length; i++) {
90 if (lines[i].includes(needle)) {
91 lineByExport.set(exportName, i);
92 break;
93 }
94 }
95 }
96
97 /** @type {(import("../util/createMappings").LineMappings)[]} */
98 const perLine = lines.map(() => null);
99 for (const [exportName, genLine] of lineByExport) {
100 const pos = /** @type {SourcePosition} */ (exportLocs.get(exportName));
101 // Source-map V3 uses 0-based lines and 0-based columns. webpack's
102 // dependency `loc` uses 1-based lines and 0-based columns, so subtract
103 // one from the line.
104 perLine[genLine] = {
105 generatedColumn: 0,
106 sourceIndex: 0,
107 originalLine: pos.line - 1,
108 originalColumn: pos.column
109 };
110 }
111
112 return {
113 version: 3,
114 file: "",
115 sources: [sourceName],
116 sourcesContent: [cssContent],
117 names: [],
118 mappings: encodeMappings(perLine)
119 };
120};
121
122class CssGenerator extends Generator {
123 /**
124 * Creates an instance of CssGenerator.
125 * @param {CssModuleGeneratorOptions} options options
126 * @param {ModuleGraph} moduleGraph the module graph
127 */
128 constructor(options, moduleGraph) {
129 super();
130 this.options = options;
131 this._exportsOnly = options.exportsOnly;
132 this._esModule = options.esModule;
133 this._moduleGraph = moduleGraph;
134 /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
135 this._moduleFactoryCache = new WeakMap();
136 }
137
138 /**
139 * Returns the reason this module cannot be concatenated, when one exists.
140 * @param {NormalModule} module module for which the bailout reason should be determined
141 * @param {ConcatenationBailoutReasonContext} context context
142 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
143 */
144 getConcatenationBailoutReason(module, context) {
145 if (!this._esModule) {
146 return "Module is not an ECMAScript module";
147 }
148
149 return undefined;
150 }
151
152 /**
153 * Returns the `@charset` that will appear at the start of this module's
154 * default export, walking through text imports when the module has no
155 * local `@charset` of its own.
156 * @param {NormalModule} module the module
157 * @param {ModuleGraph} moduleGraph module graph
158 * @param {WeakSet<NormalModule>=} visited cycle guard
159 * @returns {string | undefined} the effective charset
160 */
161 _getEffectiveCharset(module, moduleGraph, visited = new WeakSet()) {
162 if (!module || visited.has(module)) return undefined;
163 const exportType = /** @type {CssModule} */ (module).exportType;
164 if (exportType !== "text" && exportType !== "css-style-sheet") {
165 return undefined;
166 }
167 visited.add(module);
168 const own =
169 module.buildInfo && /** @type {BuildInfo} */ (module.buildInfo).charset;
170 if (own !== undefined) return own;
171 if (exportType !== "text") return undefined;
172 for (const dep of module.dependencies) {
173 if (dep instanceof CssImportDependency) {
174 const depModule = /** @type {NormalModule} */ (
175 moduleGraph.getModule(dep)
176 );
177 const inherited = this._getEffectiveCharset(
178 depModule,
179 moduleGraph,
180 visited
181 );
182 if (inherited !== undefined) return inherited;
183 }
184 }
185 return undefined;
186 }
187
188 /**
189 * Generate JavaScript expressions that evaluate each `@import`'d module
190 * for side effects. Only used by `style` exportType, where each imported
191 * style module injects its own `<style>` element independently — no
192 * content merging happens at the parent. `text` and `css-style-sheet`
193 * instead inline their imports at build time via
194 * {@link CssGenerator#_generateMergedContentSource}.
195 * @param {NormalModule} module the module to generate CSS text for
196 * @param {GenerateContext} generateContext the generate context
197 * @returns {string[]} JS expressions, one per `@import` dependency
198 */
199 _generateImportSideEffects(module, generateContext) {
200 const { moduleGraph, concatenationScope } = generateContext;
201 const parts = [];
202
203 for (const dep of module.dependencies) {
204 if (!(dep instanceof CssImportDependency)) continue;
205 const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
206 // Concat-scoped deps are inlined into the same module; their side
207 // effect (own `<style>` injection) is emitted at the dep's own
208 // site, so no explicit reference is needed here.
209 if (concatenationScope && concatenationScope.isModuleInScope(depModule)) {
210 continue;
211 }
212 parts.push(
213 generateContext.runtimeTemplate.moduleExports({
214 module: depModule,
215 chunkGraph: generateContext.chunkGraph,
216 request: depModule.userRequest,
217 weak: false,
218 runtimeRequirements: generateContext.runtimeRequirements
219 })
220 );
221 }
222
223 return parts;
224 }
225
226 /**
227 * Build a single CSS `Source` that contains, in source order, the rendered
228 * CSS text of every transitively `@import`'d module followed by the
229 * current module's own CSS text. Imports are inlined at build time so
230 * the resulting `Source` carries a single, accurate source map covering
231 * every contributing file — no runtime merge helper required.
232 *
233 * Only `text` / `css-style-sheet` imports contribute CSS text; `link` and
234 * `style` imports are emitted separately (own `.css` file or own
235 * `<style>` injection) and are skipped here.
236 *
237 * `ancestors` tracks the path from the top-level caller down to the
238 * current module — not every module ever visited. A module reappearing
239 * along a sibling branch (a "diamond import" like two different files
240 * each `@import`'ing the same shared module) must be inlined every time,
241 * matching the prior runtime behavior where each `default` getter was
242 * invoked at every import site.
243 * @param {NormalModule} module the module to render
244 * @param {GenerateContext} generateContext the generate context
245 * @param {Set<NormalModule>} ancestors modules on the current path
246 * @returns {Source | null} merged CSS source, or null when the module has no content
247 */
248 _generateMergedContentSource(module, generateContext, ancestors) {
249 if (ancestors.has(module)) return null;
250 ancestors.add(module);
251 try {
252 const { moduleGraph } = generateContext;
253 /** @type {Source[]} */
254 const parts = [];
255
256 for (const dep of module.dependencies) {
257 if (!(dep instanceof CssImportDependency)) continue;
258 const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
259 if (!depModule) continue;
260 const depExportType = depModule.exportType;
261 if (depExportType !== "text" && depExportType !== "css-style-sheet") {
262 continue;
263 }
264 const depMerged = this._generateMergedContentSource(
265 depModule,
266 generateContext,
267 ancestors
268 );
269 if (depMerged) parts.push(depMerged);
270 }
271
272 const own = this._generateContentSource(module, generateContext);
273 if (own) parts.push(own);
274
275 if (parts.length === 0) return null;
276 if (parts.length === 1) return parts[0];
277 return new ConcatSource(...parts);
278 } finally {
279 ancestors.delete(module);
280 }
281 }
282
283 /**
284 * Generate CSS source for the current module
285 * @param {NormalModule} module the module to generate CSS source for
286 * @param {GenerateContext} generateContext the generate context
287 * @returns {Source | null} the CSS source
288 */
289 _generateContentSource(module, generateContext) {
290 const moduleSourceContent = /** @type {Source} */ (
291 this.generate(module, {
292 ...generateContext,
293 type: CSS_TYPE
294 })
295 );
296
297 if (!moduleSourceContent) {
298 return null;
299 }
300
301 const compilation = generateContext.runtimeTemplate.compilation;
302 // For non-link exportTypes (style, text, css-style-sheet), url() in the CSS
303 // is resolved relative to the document URL (for <style> tags and CSSStyleSheet),
304 // not relative to any output file. Use empty undoPath so urls are relative to
305 // the output root.
306 const undoPath = "";
307
308 const CssModulesPlugin = getCssModulesPlugin();
309 const hooks = CssModulesPlugin.getCompilationHooks(compilation);
310 return CssModulesPlugin.renderModule(
311 /** @type {CssModule} */ (module),
312 {
313 undoPath,
314 moduleSourceContent,
315 moduleFactoryCache: this._moduleFactoryCache,
316 runtimeTemplate: generateContext.runtimeTemplate
317 },
318 hooks
319 );
320 }
321
322 /**
323 * Serialize a CSS Source into a JS string literal with an optional
324 * inline `sourceMappingURL` data URI so DevTools can resolve the
325 * original sources at runtime.
326 * @param {Source} cssSource the CSS source
327 * @param {import("../../declarations/WebpackOptions").DevTool | undefined} devtool the devtool option
328 * @returns {Source} a Source representing a JS string literal
329 */
330 _cssToJsLiteral(cssSource, devtool) {
331 const { source, map } = cssSource.sourceAndMap();
332 let content = /** @type {string} */ (source);
333 if (map) {
334 const inlineMap =
335 typeof devtool === "string" && devtool.includes("nosources")
336 ? { ...map, sourcesContent: undefined }
337 : map;
338 const base64Map = Buffer.from(JSON.stringify(inlineMap), "utf8").toString(
339 "base64"
340 );
341 const trailingNewline = content.endsWith("\n") ? "" : "\n";
342 content += `${trailingNewline}/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}*/`;
343 }
344 return new RawSource(JSON.stringify(content));
345 }
346
347 /**
348 * Processes the provided module.
349 * @param {NormalModule} module the current module
350 * @param {Dependency} dependency the dependency to generate
351 * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
352 * @param {ReplaceSource} source the current replace source which can be modified
353 * @param {GenerateContext & { cssData: CssData }} generateContext the render context
354 * @returns {void}
355 */
356 sourceDependency(module, dependency, initFragments, source, generateContext) {
357 const constructor =
358 /** @type {DependencyConstructor} */
359 (dependency.constructor);
360 const template = generateContext.dependencyTemplates.get(constructor);
361 if (!template) {
362 throw new Error(
363 `No template for dependency: ${dependency.constructor.name}`
364 );
365 }
366
367 /** @type {DependencyTemplateContext} */
368 /** @type {InitFragment<GenerateContext>[] | undefined} */
369 let chunkInitFragments;
370 /** @type {DependencyTemplateContext} */
371 const templateContext = {
372 runtimeTemplate: generateContext.runtimeTemplate,
373 dependencyTemplates: generateContext.dependencyTemplates,
374 moduleGraph: generateContext.moduleGraph,
375 chunkGraph: generateContext.chunkGraph,
376 module,
377 runtime: generateContext.runtime,
378 runtimeRequirements: generateContext.runtimeRequirements,
379 concatenationScope: generateContext.concatenationScope,
380 codeGenerationResults:
381 /** @type {CodeGenerationResults} */
382 (generateContext.codeGenerationResults),
383 initFragments,
384 cssData: generateContext.cssData,
385 type: generateContext.type,
386 get chunkInitFragments() {
387 if (!chunkInitFragments) {
388 const data =
389 /** @type {NonNullable<GenerateContext["getData"]>} */
390 (generateContext.getData)();
391 chunkInitFragments = data.get("chunkInitFragments");
392 if (!chunkInitFragments) {
393 chunkInitFragments = [];
394 data.set("chunkInitFragments", chunkInitFragments);
395 }
396 }
397
398 return chunkInitFragments;
399 }
400 };
401
402 template.apply(dependency, source, templateContext);
403 }
404
405 /**
406 * Processes the provided module.
407 * @param {NormalModule} module the module to generate
408 * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
409 * @param {ReplaceSource} source the current replace source which can be modified
410 * @param {GenerateContext & { cssData: CssData }} generateContext the generateContext
411 * @returns {void}
412 */
413 sourceModule(module, initFragments, source, generateContext) {
414 for (const dependency of module.dependencies) {
415 this.sourceDependency(
416 module,
417 dependency,
418 initFragments,
419 source,
420 generateContext
421 );
422 }
423
424 if (module.presentationalDependencies !== undefined) {
425 for (const dependency of module.presentationalDependencies) {
426 this.sourceDependency(
427 module,
428 dependency,
429 initFragments,
430 source,
431 generateContext
432 );
433 }
434 }
435 }
436
437 /**
438 * Generates generated code for this runtime module.
439 * @param {NormalModule} module module for which the code should be generated
440 * @param {GenerateContext} generateContext context for generate
441 * @returns {Source | null} generated code
442 */
443 generate(module, generateContext) {
444 const exportType = /** @type {CssModule} */ (module).exportType || "link";
445 const source =
446 generateContext.type === JAVASCRIPT_TYPE && exportType === "link"
447 ? new ReplaceSource(new RawSource(""))
448 : new ReplaceSource(/** @type {Source} */ (module.originalSource()));
449 /** @type {InitFragment<GenerateContext>[]} */
450 const initFragments = [];
451 /** @type {CssData} */
452 const cssData = {
453 esModule: /** @type {boolean} */ (this._esModule),
454 exports: new Map(),
455 exportLocs: new Map()
456 };
457
458 this.sourceModule(module, initFragments, source, {
459 ...generateContext,
460 cssData
461 });
462
463 switch (generateContext.type) {
464 case JAVASCRIPT_TYPE: {
465 const compilation = generateContext.runtimeTemplate.compilation;
466 const devtool = compilation.options.devtool;
467 const isCssModule = /** @type {BuildMeta} */ (module.buildMeta)
468 .isCssModule;
469
470 const generateContentCode = () => {
471 switch (exportType) {
472 case "style": {
473 const cssSource = this._generateContentSource(
474 module,
475 generateContext
476 );
477 if (!cssSource) return "";
478
479 generateContext.runtimeRequirements.add(
480 RuntimeGlobals.cssInjectStyle
481 );
482
483 const moduleId = generateContext.chunkGraph.getModuleId(module);
484
485 if (generateContext.concatenationScope) {
486 return new ConcatSource(
487 `__webpack_css_styles__.push([${JSON.stringify(moduleId)}, `,
488 this._cssToJsLiteral(cssSource, devtool),
489 "]);"
490 );
491 }
492
493 return new ConcatSource(
494 `${RuntimeGlobals.cssInjectStyle}(${JSON.stringify(moduleId)}, `,
495 this._cssToJsLiteral(cssSource, devtool),
496 ");"
497 );
498 }
499
500 default:
501 return "";
502 }
503 };
504 const generateImportCode = () => {
505 switch (exportType) {
506 case "style": {
507 return this._generateImportSideEffects(module, generateContext)
508 .map((expr) => `${expr};`)
509 .join("\n");
510 }
511 default:
512 return "";
513 }
514 };
515 const generateExportCode = () => {
516 /** @returns {Source} generated CSS text as JS expression */
517 const generateCssText = () => {
518 const cssSource = this._generateMergedContentSource(
519 module,
520 generateContext,
521 new Set()
522 );
523
524 let jsLiteral = cssSource
525 ? this._cssToJsLiteral(cssSource, devtool)
526 : new RawSource('""');
527
528 const effectiveCharset =
529 exportType === "css-style-sheet" || exportType === "text"
530 ? this._getEffectiveCharset(module, generateContext.moduleGraph)
531 : undefined;
532 if (effectiveCharset !== undefined) {
533 jsLiteral = new ConcatSource(
534 `'@charset "${effectiveCharset}";\\n' + `,
535 jsLiteral
536 );
537 }
538
539 return jsLiteral;
540 };
541 /**
542 * Generates js default export.
543 * @returns {Source | null} the default export
544 */
545 const generateJSDefaultExport = () => {
546 switch (exportType) {
547 case "text": {
548 return generateCssText();
549 }
550 case "css-style-sheet": {
551 // Build a constructable stylesheet from the statically
552 // merged CSS text. The merged literal carries a single
553 // inline source map covering every contributing module.
554 const fnPrefix =
555 generateContext.runtimeTemplate.supportsArrowFunction()
556 ? "() => {\n"
557 : "function() {\n";
558 const constOrVar =
559 generateContext.runtimeTemplate.renderConst();
560 return new ConcatSource(
561 `(${fnPrefix}${constOrVar} sheet = new CSSStyleSheet();\nsheet.replaceSync(`,
562 generateCssText(),
563 ");\nreturn sheet;\n})()"
564 );
565 }
566 default:
567 return null;
568 }
569 };
570
571 /** @type {Source | null} */
572 const defaultExport = generateJSDefaultExport();
573
574 /** @type {BuildInfo} */
575 (module.buildInfo).cssData = cssData;
576
577 // Required for HMR
578 if (module.hot) {
579 generateContext.runtimeRequirements.add(RuntimeGlobals.module);
580 }
581
582 if (!defaultExport && cssData.exports.size === 0 && !isCssModule) {
583 return new RawSource("");
584 }
585
586 if (generateContext.concatenationScope) {
587 const source = new ConcatSource();
588 /** @type {Set<string>} */
589 const usedIdentifiers = new Set();
590 const { RESERVED_IDENTIFIER } = getPropertyName();
591
592 if (defaultExport) {
593 const usedName = generateContext.moduleGraph
594 .getExportInfo(module, "default")
595 .getUsedName("default", generateContext.runtime);
596 if (usedName) {
597 let identifier = Template.toIdentifier(usedName);
598 if (RESERVED_IDENTIFIER.has(identifier)) {
599 identifier = `_${identifier}`;
600 }
601 usedIdentifiers.add(identifier);
602 generateContext.concatenationScope.registerExport(
603 "default",
604 identifier
605 );
606 source.add(
607 `${generateContext.runtimeTemplate.renderConst()} ${identifier} = `
608 );
609 source.add(defaultExport);
610 source.add(";\n");
611 }
612 }
613
614 for (const [name, v] of cssData.exports) {
615 const usedName = generateContext.moduleGraph
616 .getExportInfo(module, name)
617 .getUsedName(name, generateContext.runtime);
618 if (!usedName) {
619 continue;
620 }
621
622 let identifier = Template.toIdentifier(usedName);
623 if (RESERVED_IDENTIFIER.has(identifier)) {
624 identifier = `_${identifier}`;
625 }
626 let i = 0;
627 while (usedIdentifiers.has(identifier)) {
628 identifier = Template.toIdentifier(name + i);
629 i += 1;
630 }
631 usedIdentifiers.add(identifier);
632 generateContext.concatenationScope.registerExport(
633 name,
634 identifier
635 );
636 source.add(
637 `${generateContext.runtimeTemplate.renderConst()} ${identifier} = ${JSON.stringify(v)};\n`
638 );
639 }
640 return source;
641 }
642
643 const needNsObj =
644 this._esModule &&
645 generateContext.moduleGraph
646 .getExportsInfo(module)
647 .otherExportsInfo.getUsed(generateContext.runtime) !==
648 UsageState.Unused;
649
650 if (needNsObj) {
651 generateContext.runtimeRequirements.add(
652 RuntimeGlobals.makeNamespaceObject
653 );
654 }
655
656 // Should be after `concatenationScope` to allow module inlining
657 generateContext.runtimeRequirements.add(RuntimeGlobals.module);
658
659 if (!isCssModule && !needNsObj) {
660 return new ConcatSource(
661 `${module.moduleArgument}.exports = `,
662 /** @type {Source} */ (defaultExport)
663 );
664 }
665
666 const result = new ConcatSource();
667 result.add(
668 `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
669 module.moduleArgument
670 }.exports = {\n`
671 );
672
673 if (defaultExport) {
674 result.add('\t"default": ');
675 result.add(defaultExport);
676 if (cssData.exports.size > 0) {
677 result.add(",\n");
678 }
679 }
680
681 /** @type {string[]} */
682 const exportEntries = [];
683 for (const [name, v] of cssData.exports) {
684 exportEntries.push(
685 `\t${JSON.stringify(name)}: ${JSON.stringify(v)}`
686 );
687 }
688 if (exportEntries.length > 0) {
689 result.add(exportEntries.join(",\n"));
690 }
691
692 result.add(`\n}${needNsObj ? ")" : ""};`);
693 return result;
694 };
695
696 const codeParts = this._exportsOnly
697 ? [generateExportCode()]
698 : [generateImportCode(), generateContentCode(), generateExportCode()];
699
700 const source = new ConcatSource();
701 for (const part of codeParts) {
702 if (part) {
703 source.add(part);
704 source.add("\n");
705 }
706 }
707 // For link-type modules without any JS emit, skip source wrapping
708 if (
709 exportType === "link" &&
710 !isCssModule &&
711 cssData.exports.size === 0
712 ) {
713 return source;
714 }
715
716 const generatedJs = /** @type {string} */ (source.source());
717 const sourceName = module.readableIdentifier(
718 compilation.requestShortener
719 );
720
721 // When per-export source positions are available, emit a
722 // SourceMapSource mapping each export line back to its CSS
723 // selector; otherwise fall back to OriginalSource.
724 if (
725 /** @type {ExportLocsMap} */
726 (cssData.exportLocs).size > 0
727 ) {
728 const cssOriginal = module.originalSource();
729 if (cssOriginal) {
730 const sourceMap = buildExportsSourceMap(
731 generatedJs,
732 /** @type {ExportLocsMap} */
733 (cssData.exportLocs),
734 /** @type {string} */ (cssOriginal.source()),
735 sourceName
736 );
737 return new SourceMapSource(generatedJs, sourceName, sourceMap);
738 }
739 }
740 return new OriginalSource(generatedJs, sourceName);
741 }
742 case CSS_TYPE: {
743 if (!(this._exportsOnly || (exportType && exportType !== "link"))) {
744 generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);
745 }
746
747 return InitFragment.addToSource(source, initFragments, generateContext);
748 }
749 case CSS_TEXT_TYPE: {
750 // The merged CSS text — what consumers like
751 // `HtmlInlineStyleDependency.Template` need when they want to
752 // drop the processed CSS straight into an inline `<style>`
753 // tag. Mirrors the JS-side `generateCssText()` (charset
754 // prefix included), without the JS string-literal wrapper.
755 const cssSource = this._generateMergedContentSource(
756 module,
757 generateContext,
758 new Set()
759 );
760
761 const effectiveCharset = this._getEffectiveCharset(
762 module,
763 generateContext.moduleGraph
764 );
765 const charsetPrefix =
766 effectiveCharset !== undefined
767 ? `@charset "${effectiveCharset}";\n`
768 : "";
769
770 if (!cssSource) {
771 return charsetPrefix
772 ? new RawSource(charsetPrefix)
773 : new RawSource("");
774 }
775 return charsetPrefix
776 ? new ConcatSource(charsetPrefix, cssSource)
777 : cssSource;
778 }
779 default:
780 return null;
781 }
782 }
783
784 /**
785 * Generates fallback output for the provided error condition.
786 * @param {Error} error the error
787 * @param {NormalModule} module module for which the code should be generated
788 * @param {GenerateContext} generateContext context for generate
789 * @returns {Source | null} generated code
790 */
791 generateError(error, module, generateContext) {
792 switch (generateContext.type) {
793 case JAVASCRIPT_TYPE: {
794 return new RawSource(
795 `throw new Error(${JSON.stringify(error.message)});`
796 );
797 }
798 case CSS_TYPE: {
799 return new RawSource(`/**\n ${error.message} \n**/`);
800 }
801 default:
802 return null;
803 }
804 }
805
806 /**
807 * Returns the source types available for this module.
808 * @param {NormalModule} module fresh module
809 * @returns {SourceTypes} available types (do not mutate)
810 */
811 getTypes(module) {
812 const exportType = /** @type {CssModule} */ (module).exportType || "link";
813 if (exportType === "style") {
814 return JAVASCRIPT_TYPES;
815 }
816
817 const sourceTypes = new Set();
818 const connections = this._moduleGraph.getIncomingConnections(module);
819
820 for (const connection of connections) {
821 if (
822 exportType === "link" &&
823 connection.dependency instanceof CssImportDependency
824 ) {
825 continue;
826 }
827
828 // when no hmr required, css module js output contains no sideEffects at all
829 // js sideeffect connection doesn't require js type output
830 if (connection.dependency instanceof HarmonyImportSideEffectDependency) {
831 continue;
832 }
833
834 // Inline `<style>` blocks in HTML modules read the merged CSS
835 // text directly via the `css-text` source type — they don't go
836 // through the JS-string wrapper that other consumers use.
837 // Matched by dependency category so the CSS package doesn't
838 // have to import HtmlInlineStyleDependency.
839 if (
840 connection.dependency &&
841 connection.dependency.category === "html-style"
842 ) {
843 sourceTypes.add(CSS_TEXT_TYPE);
844 continue;
845 }
846
847 if (!connection.originModule) {
848 continue;
849 }
850
851 if (connection.originModule.type.split("/")[0] !== CSS_TYPE) {
852 sourceTypes.add(JAVASCRIPT_TYPE);
853 } else {
854 const originModule = /** @type {CssModule} */ connection.originModule;
855 const originExportType = /** @type {CssModule} */ (originModule)
856 .exportType;
857 if (
858 /** @type {boolean} */ (
859 originExportType && originExportType !== "link"
860 )
861 ) {
862 sourceTypes.add(JAVASCRIPT_TYPE);
863 }
864 }
865 }
866 if (
867 this._exportsOnly ||
868 /** @type {boolean} */ (exportType && exportType !== "link")
869 ) {
870 const hasJs = sourceTypes.has(JAVASCRIPT_TYPE);
871 const hasCssText = sourceTypes.has(CSS_TEXT_TYPE);
872 if (hasJs && hasCssText) return JAVASCRIPT_AND_CSS_TEXT_TYPES;
873 if (hasJs) return JAVASCRIPT_TYPES;
874 if (hasCssText) return CSS_TEXT_TYPES;
875 return new Set();
876 }
877 if (sourceTypes.has(JAVASCRIPT_TYPE)) {
878 return JAVASCRIPT_AND_CSS_TYPES;
879 }
880 return CSS_TYPES;
881 }
882
883 /**
884 * Returns the estimated size for the requested source type.
885 * @param {NormalModule} module the module
886 * @param {SourceType=} type source type
887 * @returns {number} estimate size of the module
888 */
889 getSize(module, type) {
890 switch (type) {
891 case JAVASCRIPT_TYPE: {
892 const cssData = /** @type {BuildInfo} */ (module.buildInfo).cssData;
893 if (!cssData) {
894 return 42;
895 }
896 if (cssData.exports.size === 0) {
897 if (/** @type {BuildMeta} */ (module.buildMeta).isCssModule) {
898 return 42;
899 }
900 return 0;
901 }
902 const exports = cssData.exports;
903 /** @type {Record<string, string>} */
904 const exportsObj = {};
905 for (const [key, value] of exports) {
906 exportsObj[key] = value;
907 }
908 const stringifiedExports = JSON.stringify(exportsObj);
909
910 return stringifiedExports.length + 42;
911 }
912 case CSS_TYPE: {
913 const originalSource = module.originalSource();
914
915 if (!originalSource) {
916 return 0;
917 }
918
919 return originalSource.size();
920 }
921 default:
922 return 0;
923 }
924 }
925
926 /**
927 * Updates the hash with the data contributed by this instance.
928 * @param {Hash} hash hash that will be modified
929 * @param {UpdateHashContext} updateHashContext context for updating hash
930 */
931 updateHash(hash, { module }) {
932 hash.update(/** @type {boolean} */ (this._esModule).toString());
933 hash.update(/** @type {boolean} */ (this._exportsOnly).toString());
934 }
935}
936
937module.exports = CssGenerator;
Note: See TracBrowser for help on using the repository browser.