| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const { ConcatSource } = require("webpack-sources");const HotUpdateChunk = require("../HotUpdateChunk");const RuntimeGlobals = require("../RuntimeGlobals");const SelfModuleFactory = require("../SelfModuleFactory");const CssExportDependency = require("../dependencies/CssExportDependency");const CssImportDependency = require("../dependencies/CssImportDependency");const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");const CssUrlDependency = require("../dependencies/CssUrlDependency");const StaticExportsDependency = require("../dependencies/StaticExportsDependency");const { compareModulesByIdentifier } = require("../util/comparators");const createSchemaValidation = require("../util/create-schema-validation");const createHash = require("../util/createHash");const memoize = require("../util/memoize");const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");const CssExportsGenerator = require("./CssExportsGenerator");const CssGenerator = require("./CssGenerator");const CssParser = require("./CssParser");/** @typedef {import("webpack-sources").Source} Source *//** @typedef {import("../../declarations/WebpackOptions").CssExperimentOptions} CssExperimentOptions *//** @typedef {import("../Chunk")} Chunk *//** @typedef {import("../Compiler")} Compiler *//** @typedef {import("../Module")} Module */const getCssLoadingRuntimeModule = memoize(() =>	require("./CssLoadingRuntimeModule"));const getSchema = name => {	const { definitions } = require("../../schemas/WebpackOptions.json");	return {		definitions,		oneOf: [{ $ref: `#/definitions/${name}` }]	};};const validateGeneratorOptions = createSchemaValidation(	require("../../schemas/plugins/css/CssGeneratorOptions.check.js"),	() => getSchema("CssGeneratorOptions"),	{		name: "Css Modules Plugin",		baseDataPath: "parser"	});const validateParserOptions = createSchemaValidation(	require("../../schemas/plugins/css/CssParserOptions.check.js"),	() => getSchema("CssParserOptions"),	{		name: "Css Modules Plugin",		baseDataPath: "parser"	});const escapeCss = (str, omitOptionalUnderscore) => {	const escaped = `${str}`.replace(		// cspell:word uffff		/[^a-zA-Z0-9_\u0081-\uffff-]/g,		s => `\\${s}`	);	return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)		? `_${escaped}`		: escaped;};const plugin = "CssModulesPlugin";class CssModulesPlugin {	/**	 * @param {CssExperimentOptions} options options	 */	constructor({ exportsOnly = false }) {		this._exportsOnly = exportsOnly;	}	/**	 * Apply the plugin	 * @param {Compiler} compiler the compiler instance	 * @returns {void}	 */	apply(compiler) {		compiler.hooks.compilation.tap(			plugin,			(compilation, { normalModuleFactory }) => {				const selfFactory = new SelfModuleFactory(compilation.moduleGraph);				compilation.dependencyFactories.set(					CssUrlDependency,					normalModuleFactory				);				compilation.dependencyTemplates.set(					CssUrlDependency,					new CssUrlDependency.Template()				);				compilation.dependencyTemplates.set(					CssLocalIdentifierDependency,					new CssLocalIdentifierDependency.Template()				);				compilation.dependencyFactories.set(					CssSelfLocalIdentifierDependency,					selfFactory				);				compilation.dependencyTemplates.set(					CssSelfLocalIdentifierDependency,					new CssSelfLocalIdentifierDependency.Template()				);				compilation.dependencyTemplates.set(					CssExportDependency,					new CssExportDependency.Template()				);				compilation.dependencyFactories.set(					CssImportDependency,					normalModuleFactory				);				compilation.dependencyTemplates.set(					CssImportDependency,					new CssImportDependency.Template()				);				compilation.dependencyTemplates.set(					StaticExportsDependency,					new StaticExportsDependency.Template()				);				normalModuleFactory.hooks.createParser					.for("css")					.tap(plugin, parserOptions => {						validateParserOptions(parserOptions);						return new CssParser();					});				normalModuleFactory.hooks.createParser					.for("css/global")					.tap(plugin, parserOptions => {						validateParserOptions(parserOptions);						return new CssParser({							allowPseudoBlocks: false,							allowModeSwitch: false						});					});				normalModuleFactory.hooks.createParser					.for("css/module")					.tap(plugin, parserOptions => {						validateParserOptions(parserOptions);						return new CssParser({							defaultMode: "local"						});					});				normalModuleFactory.hooks.createGenerator					.for("css")					.tap(plugin, generatorOptions => {						validateGeneratorOptions(generatorOptions);						return this._exportsOnly							? new CssExportsGenerator()							: new CssGenerator();					});				normalModuleFactory.hooks.createGenerator					.for("css/global")					.tap(plugin, generatorOptions => {						validateGeneratorOptions(generatorOptions);						return this._exportsOnly							? new CssExportsGenerator()							: new CssGenerator();					});				normalModuleFactory.hooks.createGenerator					.for("css/module")					.tap(plugin, generatorOptions => {						validateGeneratorOptions(generatorOptions);						return this._exportsOnly							? new CssExportsGenerator()							: new CssGenerator();					});				const orderedCssModulesPerChunk = new WeakMap();				compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {					const { chunkGraph } = compilation;					for (const chunk of compilation.chunks) {						if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {							orderedCssModulesPerChunk.set(								chunk,								this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)							);						}					}				});				compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {					const {						chunkGraph,						outputOptions: {							hashSalt,							hashDigest,							hashDigestLength,							hashFunction						}					} = compilation;					const modules = orderedCssModulesPerChunk.get(chunk);					if (modules === undefined) return;					const hash = createHash(hashFunction);					if (hashSalt) hash.update(hashSalt);					for (const module of modules) {						hash.update(chunkGraph.getModuleHash(module, chunk.runtime));					}					const digest = /** @type {string} */ (hash.digest(hashDigest));					chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);				});				compilation.hooks.renderManifest.tap(plugin, (result, options) => {					const { chunkGraph } = compilation;					const { hash, chunk, codeGenerationResults } = options;					if (chunk instanceof HotUpdateChunk) return result;					const modules = orderedCssModulesPerChunk.get(chunk);					if (modules !== undefined) {						result.push({							render: () =>								this.renderChunk({									chunk,									chunkGraph,									codeGenerationResults,									uniqueName: compilation.outputOptions.uniqueName,									modules								}),							filenameTemplate: CssModulesPlugin.getChunkFilenameTemplate(								chunk,								compilation.outputOptions							),							pathOptions: {								hash,								runtime: chunk.runtime,								chunk,								contentHashType: "css"							},							identifier: `css${chunk.id}`,							hash: chunk.contentHash.css						});					}					return result;				});				const enabledChunks = new WeakSet();				const handler = (chunk, set) => {					if (enabledChunks.has(chunk)) {						return;					}					enabledChunks.add(chunk);					set.add(RuntimeGlobals.publicPath);					set.add(RuntimeGlobals.getChunkCssFilename);					set.add(RuntimeGlobals.hasOwnProperty);					set.add(RuntimeGlobals.moduleFactoriesAddOnly);					set.add(RuntimeGlobals.makeNamespaceObject);					const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();					compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));				};				compilation.hooks.runtimeRequirementInTree					.for(RuntimeGlobals.hasCssModules)					.tap(plugin, handler);				compilation.hooks.runtimeRequirementInTree					.for(RuntimeGlobals.ensureChunkHandlers)					.tap(plugin, handler);				compilation.hooks.runtimeRequirementInTree					.for(RuntimeGlobals.hmrDownloadUpdateHandlers)					.tap(plugin, handler);			}		);	}	getModulesInOrder(chunk, modules, compilation) {		if (!modules) return [];		const modulesList = [...modules];		// Get ordered list of modules per chunk group		// Lists are in reverse order to allow to use Array.pop()		const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {			const sortedModules = modulesList				.map(module => {					return {						module,						index: chunkGroup.getModulePostOrderIndex(module)					};				})				.filter(item => item.index !== undefined)				.sort((a, b) => b.index - a.index)				.map(item => item.module);			return { list: sortedModules, set: new Set(sortedModules) };		});		if (modulesByChunkGroup.length === 1)			return modulesByChunkGroup[0].list.reverse();		const compareModuleLists = ({ list: a }, { list: b }) => {			if (a.length === 0) {				return b.length === 0 ? 0 : 1;			} else {				if (b.length === 0) return -1;				return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]);			}		};		modulesByChunkGroup.sort(compareModuleLists);		const finalModules = [];		for (;;) {			const failedModules = new Set();			const list = modulesByChunkGroup[0].list;			if (list.length === 0) {				// done, everything empty				break;			}			let selectedModule = list[list.length - 1];			let hasFailed = undefined;			outer: for (;;) {				for (const { list, set } of modulesByChunkGroup) {					if (list.length === 0) continue;					const lastModule = list[list.length - 1];					if (lastModule === selectedModule) continue;					if (!set.has(selectedModule)) continue;					failedModules.add(selectedModule);					if (failedModules.has(lastModule)) {						// There is a conflict, try other alternatives						hasFailed = lastModule;						continue;					}					selectedModule = lastModule;					hasFailed = false;					continue outer; // restart				}				break;			}			if (hasFailed) {				// There is a not resolve-able conflict with the selectedModule				if (compilation) {					// TODO print better warning					compilation.warnings.push(						new Error(							`chunk ${								chunk.name || chunk.id							}\nConflicting order between ${hasFailed.readableIdentifier(								compilation.requestShortener							)} and ${selectedModule.readableIdentifier(								compilation.requestShortener							)}`						)					);				}				selectedModule = hasFailed;			}			// Insert the selected module into the final modules list			finalModules.push(selectedModule);			// Remove the selected module from all lists			for (const { list, set } of modulesByChunkGroup) {				const lastModule = list[list.length - 1];				if (lastModule === selectedModule) list.pop();				else if (hasFailed && set.has(selectedModule)) {					const idx = list.indexOf(selectedModule);					if (idx >= 0) list.splice(idx, 1);				}			}			modulesByChunkGroup.sort(compareModuleLists);		}		return finalModules;	}	getOrderedChunkCssModules(chunk, chunkGraph, compilation) {		return [			...this.getModulesInOrder(				chunk,				chunkGraph.getOrderedChunkModulesIterableBySourceType(					chunk,					"css-import",					compareModulesByIdentifier				),				compilation			),			...this.getModulesInOrder(				chunk,				chunkGraph.getOrderedChunkModulesIterableBySourceType(					chunk,					"css",					compareModulesByIdentifier				),				compilation			)		];	}	renderChunk({		uniqueName,		chunk,		chunkGraph,		codeGenerationResults,		modules	}) {		const source = new ConcatSource();		const metaData = [];		for (const module of modules) {			try {				const codeGenResult = codeGenerationResults.get(module, chunk.runtime);				const s =					codeGenResult.sources.get("css") ||					codeGenResult.sources.get("css-import");				if (s) {					source.add(s);					source.add("\n");				}				const exports =					codeGenResult.data && codeGenResult.data.get("css-exports");				const moduleId = chunkGraph.getModuleId(module) + "";				metaData.push(					`${						exports							? Array.from(exports, ([n, v]) => {									const shortcutValue = `${										uniqueName ? uniqueName + "-" : ""									}${moduleId}-${n}`;									return v === shortcutValue										? `${escapeCss(n)}/`										: v === "--" + shortcutValue										? `${escapeCss(n)}%`										: `${escapeCss(n)}(${escapeCss(v)})`;							  }).join("")							: ""					}${escapeCss(moduleId)}`				);			} catch (e) {				e.message += `\nduring rendering of css ${module.identifier()}`;				throw e;			}		}		source.add(			`head{--webpack-${escapeCss(				(uniqueName ? uniqueName + "-" : "") + chunk.id,				true			)}:${metaData.join(",")};}`		);		return source;	}	static getChunkFilenameTemplate(chunk, outputOptions) {		if (chunk.cssFilenameTemplate) {			return chunk.cssFilenameTemplate;		} else if (chunk.canBeInitial()) {			return outputOptions.cssFilename;		} else {			return outputOptions.cssChunkFilename;		}	}	static chunkHasCss(chunk, chunkGraph) {		return (			!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css") ||			!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import")		);	}}module.exports = CssModulesPlugin;
 |