| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Sergey Melyukov @smelukov
- */
- "use strict";
- const {
- ConcatSource,
- OriginalSource,
- RawSource,
- ReplaceSource,
- SourceMapSource
- } = require("webpack-sources");
- const { UsageState } = require("../ExportsInfo");
- const Generator = require("../Generator");
- const InitFragment = require("../InitFragment");
- const ModuleFilenameHelpers = require("../ModuleFilenameHelpers");
- const {
- CSS_TEXT_TYPE,
- CSS_TEXT_TYPES,
- CSS_TYPE,
- CSS_TYPES,
- JAVASCRIPT_AND_CSS_TEXT_TYPES,
- JAVASCRIPT_AND_CSS_TYPES,
- JAVASCRIPT_TYPE,
- JAVASCRIPT_TYPES,
- NO_TYPES
- } = require("../ModuleSourceTypeConstants");
- const RuntimeGlobals = require("../RuntimeGlobals");
- const Template = require("../Template");
- const { getPresentKinds } = require("../TemplatedPathPlugin");
- const CssImportDependency = require("../dependencies/CssImportDependency");
- const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
- const { encodeMappings } = require("../util/createMappings");
- const {
- contextifySourceUrl,
- makePathsAbsolute
- } = require("../util/identifier");
- const PropertyName = require("../util/property");
- const {
- PUBLIC_PATH_FULL_HASH,
- walkFullHashPlaceholders
- } = require("../util/publicPathPlaceholder");
- const CssModulesPlugin = require("./CssModulesPlugin");
- // Avoids `type.split("/")[0]` allocation on the `getTypes` hot path.
- const CSS_TYPE_PREFIX = `${CSS_TYPE}/`;
- const URL_SCHEME_REGEXP = /^(?:data|https?):/;
- /** @import { Source, RawSourceMap } from "webpack-sources" */
- /**
- * @import {
- * CssModuleGeneratorOptions
- * } from "../../declarations/WebpackOptions"
- */
- /** @import { DependencyConstructor } from "../Compilation" */
- /** @import CodeGenerationResults from "../CodeGenerationResults" */
- /** @import Dependency from "../Dependency" */
- /**
- * @import {
- * CssData,
- * DependencyTemplateContext,
- * CssDependencyTemplateContext
- * } from "../DependencyTemplate"
- */
- /** @import { GenerateContext, UpdateHashContext } from "../Generator" */
- /**
- * @import {
- * ConcatenationBailoutReasonContext,
- * SourceType,
- * SourceTypes
- * } from "../Module"
- */
- /** @import ModuleGraph from "../ModuleGraph" */
- /** @import NormalModule from "../NormalModule" */
- /** @import Hash from "../util/Hash" */
- /** @import { ModuleFactoryCacheEntry } from "./CssModulesPlugin" */
- /**
- * @import CssModule, {
- * CssModuleBuildInfo,
- * CssModuleBuildMeta
- * } from "./CssModule"
- */
- /** @typedef {{ line: number, column: number }} SourcePosition */
- /** @typedef {Map<string, SourcePosition>} ExportLocsMap */
- /**
- * Build a v3 source map mapping CSS-class export lines back to their selector positions.
- * @param {string} generatedJs the generated JS string
- * @param {ExportLocsMap} exportLocs map of export names to CSS source location
- * @param {string} cssContent original CSS source content
- * @param {string} sourceName source identifier to use in the map
- * @returns {RawSourceMap} a v3 RawSourceMap
- */
- const buildExportsSourceMap = (
- generatedJs,
- exportLocs,
- cssContent,
- sourceName
- ) => {
- const lines = generatedJs.split("\n");
- // O(L) pass: match `\t"<name>": <value>` lines against export names.
- const keyToName = new Map();
- for (const [exportName] of exportLocs) {
- keyToName.set(JSON.stringify(exportName), exportName);
- }
- const lineByExport = new Map();
- for (let i = 0; i < lines.length && lineByExport.size < keyToName.size; i++) {
- const line = lines[i];
- if (line.charCodeAt(0) !== 9 || line.charCodeAt(1) !== 34) continue;
- let j = 2;
- while (j < line.length) {
- const c = line.charCodeAt(j);
- if (c === 92) {
- j += 2;
- continue;
- }
- if (c === 34) break;
- j++;
- }
- if (line.charCodeAt(j + 1) !== 58) continue;
- const name = keyToName.get(line.slice(1, j + 1));
- if (name !== undefined && !lineByExport.has(name)) {
- lineByExport.set(name, i);
- }
- }
- /** @type {(import("../util/createMappings").LineMappings)[]} */
- const perLine = lines.map(() => null);
- for (const [exportName, genLine] of lineByExport) {
- const pos = /** @type {SourcePosition} */ (exportLocs.get(exportName));
- // V3 source maps are 0-based; webpack `loc` lines are 1-based.
- perLine[genLine] = {
- generatedColumn: 0,
- sourceIndex: 0,
- originalLine: pos.line - 1,
- originalColumn: pos.column
- };
- }
- return {
- version: 3,
- file: "",
- sources: [sourceName],
- sourcesContent: [cssContent],
- names: [],
- mappings: encodeMappings(perLine)
- };
- };
- class CssGenerator extends Generator {
- /**
- * Creates an instance of CssGenerator.
- * @param {CssModuleGeneratorOptions} options options
- * @param {ModuleGraph} moduleGraph the module graph
- */
- constructor(options, moduleGraph) {
- super();
- /** @type {CssModuleGeneratorOptions} */
- this.options = options;
- /** @type {boolean | undefined} */
- this._exportsOnly = options.exportsOnly;
- /** @type {boolean | undefined} */
- this._esModule = options.esModule;
- /** @type {ModuleGraph} */
- this._moduleGraph = moduleGraph;
- /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
- this._moduleFactoryCache = new WeakMap();
- const localIdentName = options.localIdentName;
- // Detect hash placeholders via the canonical template parser so this stays
- // in sync with `interpolate`'s grammar.
- const isFn = typeof localIdentName === "function";
- const kinds =
- typeof localIdentName === "string"
- ? getPresentKinds(localIdentName)
- : undefined;
- /** @type {boolean} */
- this._localIdentNeedsHash =
- isFn ||
- (kinds !== undefined && (kinds.has("fullhash") || kinds.has("hash")));
- /** @type {boolean} */
- this._localIdentNeedsContentHash =
- isFn || (kinds !== undefined && kinds.has("contenthash"));
- }
- /**
- * Returns the reason this module cannot be concatenated, when one exists.
- * @param {NormalModule} module module for which the bailout reason should be determined
- * @param {ConcatenationBailoutReasonContext} context context
- * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
- */
- getConcatenationBailoutReason(module, context) {
- if (!this._esModule) {
- return "Module is not an ECMAScript module";
- }
- return undefined;
- }
- /**
- * Processes the provided module.
- * @param {Dependency} dependency the dependency to generate
- * @param {ReplaceSource} source the current replace source which can be modified
- * @param {DependencyTemplateContext & { cssData: CssData, type: string }} templateContext the template context (shared across all dependencies of the module)
- * @returns {void}
- */
- sourceDependency(dependency, source, templateContext) {
- const constructor =
- /** @type {DependencyConstructor} */
- (dependency.constructor);
- const template = templateContext.dependencyTemplates.get(constructor);
- if (!template) {
- throw new Error(
- `No template for dependency: ${dependency.constructor.name}`
- );
- }
- template.apply(dependency, source, templateContext);
- }
- /**
- * Processes the provided module.
- * @param {NormalModule} module the module to generate
- * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
- * @param {ReplaceSource} source the current replace source which can be modified
- * @param {GenerateContext & { cssData: CssData }} generateContext the generateContext
- * @returns {void}
- */
- sourceModule(module, initFragments, source, generateContext) {
- // Only `dependency` varies across `template.apply` calls, so one context
- // (and one lazy `chunkInitFragments` getter) serves the whole module.
- /** @type {InitFragment<GenerateContext>[] | undefined} */
- let chunkInitFragments;
- /** @type {CssDependencyTemplateContext} */
- const templateContext = {
- runtimeTemplate: generateContext.runtimeTemplate,
- dependencyTemplates: generateContext.dependencyTemplates,
- moduleGraph: generateContext.moduleGraph,
- chunkGraph: generateContext.chunkGraph,
- module,
- runtime: generateContext.runtime,
- runtimeRequirements: generateContext.runtimeRequirements,
- concatenationScope: generateContext.concatenationScope,
- codeGenerationResults:
- /** @type {CodeGenerationResults} */
- (generateContext.codeGenerationResults),
- initFragments,
- cssData: generateContext.cssData,
- type: generateContext.type,
- get chunkInitFragments() {
- if (!chunkInitFragments) {
- const data =
- /** @type {NonNullable<GenerateContext["getData"]>} */
- (generateContext.getData)();
- chunkInitFragments = data.get("chunkInitFragments");
- if (!chunkInitFragments) {
- chunkInitFragments = [];
- data.set("chunkInitFragments", chunkInitFragments);
- }
- }
- return chunkInitFragments;
- }
- };
- for (const dependency of module.dependencies) {
- this.sourceDependency(dependency, source, templateContext);
- }
- if (module.presentationalDependencies !== undefined) {
- for (const dependency of module.presentationalDependencies) {
- this.sourceDependency(dependency, source, templateContext);
- }
- }
- }
- /**
- * Generates generated code for this runtime module.
- * @param {NormalModule} module module for which the code should be generated
- * @param {GenerateContext} generateContext context for generate
- * @returns {Source | null} generated code
- */
- generate(module, generateContext) {
- const exportType = /** @type {CssModule} */ (module).exportType || "link";
- const source =
- generateContext.type === JAVASCRIPT_TYPE && exportType === "link"
- ? new ReplaceSource(new RawSource(""))
- : new ReplaceSource(/** @type {Source} */ (module.originalSource()));
- /** @type {InitFragment<GenerateContext>[]} */
- const initFragments = [];
- /** @type {CssData} */
- const cssData = {
- esModule: /** @type {boolean} */ (this._esModule),
- exports: new Map(),
- exportLocs: new Map()
- };
- this.sourceModule(module, initFragments, source, {
- ...generateContext,
- cssData
- });
- switch (generateContext.type) {
- case JAVASCRIPT_TYPE: {
- const compilation = generateContext.runtimeTemplate.compilation;
- const devtool = compilation.options.devtool;
- const isCssModule = /** @type {CssModuleBuildMeta} */ (module.buildMeta)
- .isCssModule;
- const generateContentCode = () => {
- switch (exportType) {
- case "style": {
- const rendered = this._renderCss(module, generateContext);
- if (!rendered) return "";
- const cssSource = this._renderEmbeddedSource(
- rendered,
- module,
- generateContext
- );
- generateContext.runtimeRequirements.add(
- RuntimeGlobals.cssInjectStyle
- );
- const moduleId = generateContext.chunkGraph.getModuleId(module);
- if (generateContext.concatenationScope) {
- return new ConcatSource(
- `__webpack_css_styles__.push([${JSON.stringify(moduleId)}, `,
- this._cssToJsLiteral(cssSource, devtool, generateContext),
- "]);"
- );
- }
- return new ConcatSource(
- `${RuntimeGlobals.cssInjectStyle}(${JSON.stringify(
- moduleId
- )}, `,
- this._cssToJsLiteral(cssSource, devtool, generateContext),
- ");"
- );
- }
- default:
- return "";
- }
- };
- const generateImportCode = () => {
- switch (exportType) {
- case "style": {
- return this._generateImportExpressions(module, generateContext)
- .map((expr) => `${expr};`)
- .join("\n");
- }
- default:
- return "";
- }
- };
- const generateExportCode = () => {
- /**
- * Generates js default export.
- * @returns {Source | null} the default export
- */
- const generateJSDefaultExport = () => {
- switch (exportType) {
- case "text": {
- return this._generateCssText(module, generateContext, true);
- }
- case "css-style-sheet": {
- const rt = generateContext.runtimeTemplate;
- const fnPrefix = rt.supportsArrowFunction()
- ? "() => {\n"
- : "function() {\n";
- const constOrVar = rt.renderConst();
- const fallback =
- "{ cssText: css, replaceSync: function(value) { this.cssText = value; } }";
- if (rt.compilation.compiler.platform.node === true) {
- return new ConcatSource(
- `(${fnPrefix}${constOrVar} css = `,
- this._generateCssText(module, generateContext, true),
- `;\nreturn ${fallback};\n})()`
- );
- }
- if (rt.isUniversalTarget()) {
- return new ConcatSource(
- `(${fnPrefix}${constOrVar} css = `,
- this._generateCssText(module, generateContext, true),
- `;\nif (typeof CSSStyleSheet === 'undefined') return ${fallback};\n${constOrVar} sheet = new CSSStyleSheet();\nsheet.replaceSync(css);\nreturn sheet;\n})()`
- );
- }
- return new ConcatSource(
- `(${fnPrefix}${constOrVar} sheet = new CSSStyleSheet();\nsheet.replaceSync(`,
- this._generateCssText(module, generateContext, true),
- ");\nreturn sheet;\n})()"
- );
- }
- default:
- return null;
- }
- };
- /** @type {Source | null} */
- const defaultExport = generateJSDefaultExport();
- /** @type {CssModuleBuildInfo} */
- (module.buildInfo).cssData = cssData;
- // Required for HMR
- if (module.hot) {
- generateContext.runtimeRequirements.add(RuntimeGlobals.module);
- }
- if (!defaultExport && cssData.exports.size === 0 && !isCssModule) {
- return new RawSource("");
- }
- const exportsInfo =
- generateContext.moduleGraph.getExportsInfo(module);
- /**
- * @param {string} name export name
- * @returns {string | false} used export name when the export is referenced
- */
- const getUsedExportName = (name) =>
- /** @type {string | false} */ (
- exportsInfo
- .getExportInfo(name)
- .getUsedName(name, generateContext.runtime)
- );
- if (generateContext.concatenationScope) {
- const source = new ConcatSource();
- /** @type {Set<string>} */
- const usedIdentifiers = new Set();
- const { RESERVED_IDENTIFIER } = PropertyName;
- if (defaultExport) {
- const usedName = getUsedExportName("default");
- if (usedName) {
- let identifier = Template.toIdentifier(usedName);
- if (RESERVED_IDENTIFIER.has(identifier)) {
- identifier = `_${identifier}`;
- }
- usedIdentifiers.add(identifier);
- generateContext.concatenationScope.registerExport(
- "default",
- identifier
- );
- source.add(
- `${generateContext.runtimeTemplate.renderConst()} ${identifier} = `
- );
- source.add(defaultExport);
- source.add(";\n");
- }
- }
- for (const [name, v] of cssData.exports) {
- const usedName = getUsedExportName(name);
- let identifier = Template.toIdentifier(usedName || name);
- if (RESERVED_IDENTIFIER.has(identifier)) {
- identifier = `_${identifier}`;
- }
- let i = 0;
- while (usedIdentifiers.has(identifier)) {
- identifier = Template.toIdentifier(name + i);
- i += 1;
- }
- usedIdentifiers.add(identifier);
- generateContext.concatenationScope.registerExport(
- name,
- identifier
- );
- source.add(
- `${generateContext.runtimeTemplate.renderConst()} ${identifier} = ${JSON.stringify(
- v
- )};\n`
- );
- }
- return source;
- }
- const needNsObj =
- this._esModule &&
- exportsInfo.otherExportsInfo.getUsed(generateContext.runtime) !==
- UsageState.Unused;
- if (needNsObj) {
- generateContext.runtimeRequirements.add(
- RuntimeGlobals.makeNamespaceObject
- );
- }
- // Should be after `concatenationScope` to allow module inlining
- generateContext.runtimeRequirements.add(RuntimeGlobals.module);
- const defaultUsedName = defaultExport
- ? getUsedExportName("default")
- : false;
- /** @type {string[]} */
- const exportEntries = [];
- for (const [name, v] of cssData.exports) {
- const exportKey = getUsedExportName(name) || name;
- exportEntries.push(
- `\t${JSON.stringify(exportKey)}: ${JSON.stringify(v)}`
- );
- }
- if (
- !isCssModule &&
- !needNsObj &&
- exportEntries.length === 0 &&
- defaultUsedName
- ) {
- return new ConcatSource(
- `${module.moduleArgument}.exports = `,
- /** @type {Source} */ (defaultExport)
- );
- }
- const result = new ConcatSource();
- result.add(
- `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
- module.moduleArgument
- }.exports = {\n`
- );
- if (defaultUsedName) {
- result.add('\t"default": ');
- result.add(/** @type {Source} */ (defaultExport));
- if (exportEntries.length > 0) {
- result.add(",\n");
- }
- }
- if (exportEntries.length > 0) {
- result.add(exportEntries.join(",\n"));
- }
- result.add(`\n}${needNsObj ? ")" : ""};`);
- return result;
- };
- const codeParts = this._exportsOnly
- ? [generateExportCode()]
- : [generateImportCode(), generateContentCode(), generateExportCode()];
- const source = new ConcatSource();
- for (const part of codeParts) {
- if (part) {
- source.add(part);
- source.add("\n");
- }
- }
- if (
- exportType === "link" &&
- !isCssModule &&
- cssData.exports.size === 0
- ) {
- return source;
- }
- const generatedJs = /** @type {string} */ (source.source());
- // Context-relative identifier, like `NormalModule.createSource` — so
- // `SourceMapDevToolPlugin` can match the module and apply the
- // devtool filename template instead of leaking this raw name
- const sourceName = contextifySourceUrl(
- /** @type {string} */ (compilation.options.context),
- module.identifier(),
- compilation.compiler.root
- );
- if (
- /** @type {ExportLocsMap} */
- (cssData.exportLocs).size > 0
- ) {
- const cssOriginal = module.originalSource();
- if (cssOriginal) {
- const sourceMap = buildExportsSourceMap(
- generatedJs,
- /** @type {ExportLocsMap} */
- (cssData.exportLocs),
- /** @type {string} */ (cssOriginal.source()),
- sourceName
- );
- return new SourceMapSource(generatedJs, sourceName, sourceMap);
- }
- }
- return new OriginalSource(generatedJs, sourceName);
- }
- case CSS_TYPE: {
- if (!(this._exportsOnly || exportType !== "link")) {
- generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules);
- }
- return InitFragment.addToSource(source, initFragments, generateContext);
- }
- case CSS_TEXT_TYPE:
- return this._generateCssText(module, generateContext, false);
- default:
- return null;
- }
- }
- /**
- * Generates fallback output for the provided error condition.
- * @param {Error} error the error
- * @param {NormalModule} module module for which the code should be generated
- * @param {GenerateContext} generateContext context for generate
- * @returns {Source | null} generated code
- */
- generateError(error, module, generateContext) {
- switch (generateContext.type) {
- case JAVASCRIPT_TYPE: {
- return new RawSource(Generator.throwBuildErrorCode(error));
- }
- case CSS_TYPE: {
- return new RawSource(`/**\n ${error.message} \n**/`);
- }
- default:
- return null;
- }
- }
- /**
- * Returns the source types available for this module.
- * @param {NormalModule} module fresh module
- * @returns {SourceTypes} available types (do not mutate)
- */
- getTypes(module) {
- const exportType = /** @type {CssModule} */ (module).exportType || "link";
- if (exportType === "style") {
- return JAVASCRIPT_TYPES;
- }
- let hasJs = false;
- let hasCssText = false;
- // Uncached per call (#20800) — the common link case stops scanning once
- // js is seen; `hasCssText` only matters for exports-only / non-link.
- const needsCssText = this._exportsOnly || exportType !== "link";
- const connections = this._moduleGraph.getIncomingConnections(module);
- for (const connection of connections) {
- if (hasJs && (hasCssText || !needsCssText)) break;
- if (
- exportType === "link" &&
- connection.dependency instanceof CssImportDependency
- ) {
- continue;
- }
- if (connection.dependency instanceof HarmonyImportSideEffectDependency) {
- continue;
- }
- // HTML inline styles consume CSS_TEXT_TYPE directly.
- if (
- connection.dependency &&
- (connection.dependency.category === "html-style" ||
- connection.dependency.category === "html-style-attribute")
- ) {
- hasCssText = true;
- continue;
- }
- if (!connection.originModule) {
- continue;
- }
- if (!connection.originModule.type.startsWith(CSS_TYPE_PREFIX)) {
- hasJs = true;
- } else {
- const originModule = /** @type {CssModule} */ connection.originModule;
- const originExportType =
- /** @type {CssModule} */ (originModule).exportType || "link";
- if (originExportType !== "link") {
- hasJs = true;
- }
- }
- }
- if (this._exportsOnly || exportType !== "link") {
- if (hasJs && hasCssText) return JAVASCRIPT_AND_CSS_TEXT_TYPES;
- if (hasJs) return JAVASCRIPT_TYPES;
- if (hasCssText) return CSS_TEXT_TYPES;
- return NO_TYPES;
- }
- if (hasJs) {
- return JAVASCRIPT_AND_CSS_TYPES;
- }
- return CSS_TYPES;
- }
- /**
- * @returns {boolean} whether getTypes() depends on the module's incoming connections
- */
- getTypesDependOnIncomingConnections() {
- return true;
- }
- /**
- * Returns the estimated size for the requested source type.
- * @param {NormalModule} module the module
- * @param {SourceType=} type source type
- * @returns {number} estimate size of the module
- */
- getSize(module, type) {
- switch (type) {
- case JAVASCRIPT_TYPE: {
- const cssData = /** @type {CssModuleBuildInfo} */ (module.buildInfo)
- .cssData;
- if (!cssData) {
- return 42;
- }
- if (cssData.exports.size === 0) {
- if (
- /** @type {CssModuleBuildMeta} */ (module.buildMeta).isCssModule
- ) {
- return 42;
- }
- return 0;
- }
- // `JSON.stringify({...exports}).length` computed entry-wise, without
- // materializing the object or the full string.
- let length = 2 + (cssData.exports.size - 1);
- for (const [key, value] of cssData.exports) {
- length +=
- JSON.stringify(key).length + 1 + JSON.stringify(value).length;
- }
- return length + 42;
- }
- case CSS_TEXT_TYPE:
- case CSS_TYPE: {
- const originalSource = module.originalSource();
- if (!originalSource) {
- return 0;
- }
- return originalSource.size();
- }
- default:
- return 0;
- }
- }
- /**
- * Updates the hash with the data contributed by this instance.
- * @param {Hash} hash hash that will be modified
- * @param {UpdateHashContext} updateHashContext context for updating hash
- */
- updateHash(hash, { module, runtimeTemplate }) {
- hash.update(/** @type {boolean} */ (this._esModule).toString());
- hash.update(/** @type {boolean} */ (this._exportsOnly).toString());
- // Only a module embedded in JS gets an inlined map, which varies on devtool
- // and its filename options without changing the source. `String`: templates
- // may be functions.
- const exportType = /** @type {CssModule} */ (module).exportType || "link";
- if (runtimeTemplate && exportType !== "link") {
- // What a tap varies on has to reach the key, or a changed option replays.
- runtimeTemplate.compilation.hooks.embeddedSourceHash.call(module, hash);
- const { compilation } = runtimeTemplate;
- const devtool = compilation.options.devtool;
- const outputOptions = compilation.outputOptions;
- hash.update(
- `|${typeof devtool === "string" ? devtool : ""}|${
- outputOptions.devtoolNamespace || ""
- }|${String(outputOptions.devtoolModuleFilenameTemplate || "")}|${String(
- outputOptions.devtoolFallbackModuleFilenameTemplate || ""
- )}`
- );
- }
- }
- /**
- * Resolve the effective `@charset` by walking text imports.
- * @param {NormalModule} module the module
- * @param {ModuleGraph} moduleGraph module graph
- * @param {WeakSet<NormalModule>=} visited cycle guard
- * @returns {string | undefined} the effective charset
- */
- _getEffectiveCharset(module, moduleGraph, visited = new WeakSet()) {
- if (!module || visited.has(module)) return undefined;
- const exportType = /** @type {CssModule} */ (module).exportType;
- if (exportType !== "text" && exportType !== "css-style-sheet") {
- return undefined;
- }
- visited.add(module);
- const own =
- module.buildInfo &&
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset;
- if (own !== undefined) return own;
- if (exportType !== "text") return undefined;
- for (const dep of module.dependencies) {
- if (dep instanceof CssImportDependency) {
- const depModule = /** @type {NormalModule} */ (
- moduleGraph.getModule(dep)
- );
- const inherited = this._getEffectiveCharset(
- depModule,
- moduleGraph,
- visited
- );
- if (inherited !== undefined) return inherited;
- }
- }
- return undefined;
- }
- /**
- * Generate JS expressions for `@import` side effects (`style` exportType only).
- * @param {NormalModule} module the module to generate CSS text for
- * @param {GenerateContext} generateContext the generate context
- * @returns {string[]} JS expressions, one per `@import` dependency
- */
- _generateImportExpressions(module, generateContext) {
- const { moduleGraph, concatenationScope } = generateContext;
- const parts = [];
- for (const dep of module.dependencies) {
- if (!(dep instanceof CssImportDependency)) continue;
- const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
- if (concatenationScope && concatenationScope.isModuleInScope(depModule)) {
- continue;
- }
- parts.push(
- generateContext.runtimeTemplate.moduleExports({
- module: depModule,
- chunkGraph: generateContext.chunkGraph,
- request: depModule.userRequest,
- weak: false,
- runtimeRequirements: generateContext.runtimeRequirements
- })
- );
- }
- return parts;
- }
- /**
- * Recursively merge `@import`'d CSS into a single Source (text/css-style-sheet only).
- * @param {NormalModule} module the module to render
- * @param {GenerateContext} generateContext the generate context
- * @param {Set<NormalModule>} ancestors modules on the current path (cycle guard)
- * @returns {Source | null} merged CSS source, or null when the module has no content
- */
- _renderMergedCss(module, generateContext, ancestors) {
- if (ancestors.has(module)) return null;
- ancestors.add(module);
- try {
- const { moduleGraph } = generateContext;
- /** @type {Source[]} */
- const parts = [];
- for (const dep of module.dependencies) {
- if (!(dep instanceof CssImportDependency)) continue;
- const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
- if (!depModule) continue;
- const depExportType = depModule.exportType;
- if (depExportType !== "text" && depExportType !== "css-style-sheet") {
- continue;
- }
- const depMerged = this._renderMergedCss(
- depModule,
- generateContext,
- ancestors
- );
- if (depMerged) parts.push(depMerged);
- }
- const own = this._renderCss(module, generateContext);
- if (own) parts.push(own);
- if (parts.length === 0) return null;
- if (parts.length === 1) return parts[0];
- return new ConcatSource(...parts);
- } finally {
- ancestors.delete(module);
- }
- }
- /**
- * Generate CSS source for the current module
- * @param {NormalModule} module the module to generate CSS source for
- * @param {GenerateContext} generateContext the generate context
- * @returns {Source | null} the CSS source
- */
- _renderCss(module, generateContext) {
- const moduleSourceContent = /** @type {Source} */ (
- this.generate(module, {
- ...generateContext,
- type: CSS_TYPE
- })
- );
- if (!moduleSourceContent) {
- return null;
- }
- const compilation = generateContext.runtimeTemplate.compilation;
- const undoPath = "";
- const hooks = CssModulesPlugin.getCompilationHooks(compilation);
- return CssModulesPlugin.renderModule(
- /** @type {CssModule} */ (module),
- {
- undoPath,
- moduleSourceContent,
- moduleFactoryCache: this._moduleFactoryCache,
- runtimeTemplate: generateContext.runtimeTemplate
- },
- hooks
- );
- }
- /**
- * Applies the devtool filename template to the sources of a map that is inlined
- * into the module's own output: `SourceMapDevToolPlugin` only rewrites maps of
- * emitted assets, so this one would keep raw module identifiers.
- * @param {RawSourceMap} map the map to rename the sources of
- * @param {GenerateContext} generateContext the generate context
- * @returns {RawSourceMap} the map with devtool source names
- */
- _nameMapSources(map, generateContext) {
- const { chunkGraph, runtimeTemplate } = generateContext;
- const { compilation } = runtimeTemplate;
- const outputOptions = compilation.outputOptions;
- const absolutify = makePathsAbsolute.bindContextCache(
- /** @type {string} */ (compilation.options.context),
- compilation.compiler.root
- );
- const options = {
- moduleFilenameTemplate:
- outputOptions.devtoolModuleFilenameTemplate ||
- ModuleFilenameHelpers.DEFAULT_MODULE_FILENAME_TEMPLATE,
- namespace: outputOptions.devtoolNamespace || ""
- };
- const contextInfo = {
- requestShortener: compilation.requestShortener,
- chunkGraph,
- hashFunction: outputOptions.hashFunction
- };
- /** @type {Set<string>} */
- const usedNames = new Set();
- const sources = map.sources.map((source) => {
- if (!source.startsWith("webpack://")) return source;
- const absoluteSource = absolutify(source.slice(10));
- // Sources a loader reported for files that are not modules (e.g. a less
- // partial) are templated as a path, like `SourceMapDevToolPlugin` does
- const module = compilation.findModule(absoluteSource) || absoluteSource;
- if (typeof module === "string" && URL_SCHEME_REGEXP.test(module)) {
- return module;
- }
- let name = ModuleFilenameHelpers.createFilename(
- module,
- options,
- contextInfo
- );
- // Same disambiguation as `SourceMapDevToolPlugin`: modules sharing a
- // resource path (e.g. one file imported under two layers) collide
- if (usedNames.has(name)) {
- name = ModuleFilenameHelpers.createFilename(
- module,
- {
- moduleFilenameTemplate:
- outputOptions.devtoolFallbackModuleFilenameTemplate ||
- ModuleFilenameHelpers.DEFAULT_FALLBACK_MODULE_FILENAME_TEMPLATE,
- namespace: options.namespace
- },
- contextInfo
- );
- while (usedNames.has(name)) name += "*";
- }
- usedNames.add(name);
- return name;
- });
- return { ...map, sources };
- }
- /**
- * Offer CSS bound for a JS string literal to `renderEmbeddedSource`. No `.css`
- * asset carries this text, so an asset-level minimizer never sees it.
- * @param {Source} cssSource the CSS source
- * @param {NormalModule} module the module
- * @param {GenerateContext} generateContext the generate context
- * @returns {Source} the source a tap returned, or the original
- */
- _renderEmbeddedSource(cssSource, module, generateContext) {
- const { compilation } = generateContext.runtimeTemplate;
- return compilation._resolveEmbeddedSource(cssSource, {
- type: CSS_TYPE,
- hostType: JAVASCRIPT_TYPE,
- module
- });
- }
- /**
- * Convert a CSS Source into a JS string literal, splicing `__webpack_require__.h()` for `[fullhash]` placeholders.
- * @param {Source} cssSource the CSS source
- * @param {import("../../declarations/WebpackOptions").DevTool | undefined} devtool the devtool option
- * @param {GenerateContext} generateContext the generate context
- * @returns {Source} a Source representing a JS string literal
- */
- _cssToJsLiteral(cssSource, devtool, generateContext) {
- const { source, map } = cssSource.sourceAndMap();
- let content = /** @type {string} */ (source);
- // A `renderEmbeddedSource` tap may hand back a mapped source whatever
- // `devtool` says, so asking for no map at all is checked here too — this
- // data URI would ship the sources inside the bundle. `hidden` is the same
- // answer for the same reason.
- const wanted = typeof devtool === "string" && !devtool.includes("hidden");
- if (map && wanted) {
- const namedMap = this._nameMapSources(map, generateContext);
- const inlineMap =
- typeof devtool === "string" && devtool.includes("nosources")
- ? { ...namedMap, sourcesContent: undefined }
- : namedMap;
- const base64Map = Buffer.from(JSON.stringify(inlineMap), "utf8").toString(
- "base64"
- );
- const trailingNewline = content.endsWith("\n") ? "" : "\n";
- content += `${trailingNewline}/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}*/`;
- }
- if (!content.includes(PUBLIC_PATH_FULL_HASH)) {
- return new RawSource(JSON.stringify(content));
- }
- const result = new ConcatSource();
- let last = 0;
- walkFullHashPlaceholders(content, (start, end, length) => {
- result.add(JSON.stringify(content.slice(last, start)));
- result.add(
- length === 0
- ? ` + ${RuntimeGlobals.getFullHash}() + `
- : ` + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + `
- );
- last = end;
- });
- result.add(JSON.stringify(content.slice(last)));
- generateContext.runtimeRequirements.add(RuntimeGlobals.getFullHash);
- return result;
- }
- /**
- * Generate the merged CSS text for a module, optionally wrapped as a
- * JS string literal for embedding in JavaScript output.
- * @param {NormalModule} module the module
- * @param {GenerateContext} generateContext the generate context
- * @param {boolean} asJsLiteral wrap the result as a JS string literal
- * @returns {Source} the CSS text source
- */
- _generateCssText(module, generateContext, asJsLiteral) {
- const cssSource = this._renderMergedCss(module, generateContext, new Set());
- const effectiveCharset = this._getEffectiveCharset(
- module,
- generateContext.moduleGraph
- );
- let result;
- if (effectiveCharset !== undefined) {
- const prefix = `@charset "${effectiveCharset}";\n`;
- result = cssSource
- ? new ConcatSource(prefix, cssSource)
- : new RawSource(prefix);
- } else {
- result = cssSource;
- }
- // Only text bound for a JS literal. `CSS_TEXT_TYPE` is an HTML inline
- // `<style>` / `style=""`, and whether that is CSS at all depends on the
- // element's `type` — a call the HTML layer makes and `htmlMinify` honors,
- // which is not visible here. After the `@charset` prefix, so a tap sees a
- // whole stylesheet.
- if (result && asJsLiteral) {
- result = this._renderEmbeddedSource(result, module, generateContext);
- }
- if (asJsLiteral) {
- const devtool =
- generateContext.runtimeTemplate.compilation.options.devtool;
- return result
- ? this._cssToJsLiteral(result, devtool, generateContext)
- : new RawSource('""');
- }
- return result || new RawSource("");
- }
- }
- module.exports = CssGenerator;
|