js • Lines: 1184/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { SyncBailHook, SyncHook, SyncWaterfallHook } = require("tapable");
const {
CachedSource,
ConcatSource,
PrefixSource,
RawSource,
ReplaceSource
} = require("webpack-sources");
const Compilation = require("../Compilation");
const HotUpdateChunk = require("../HotUpdateChunk");
const { CSS_IMPORT_TYPE, CSS_TYPE } = require("../ModuleSourceTypeConstants");
const {
CSS_MODULE_TYPE,
CSS_MODULE_TYPE_AUTO,
CSS_MODULE_TYPE_GLOBAL,
CSS_MODULE_TYPE_MODULE
} = require("../ModuleTypeConstants");
const NormalModule = require("../NormalModule");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const { tryRunOrWebpackError } = require("../errors/HookWebpackError");
const WebpackError = require("../errors/WebpackError");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const ConcatenatedModule = require("../optimize/ConcatenatedModule");
const { compareModulesByFullName } = require("../util/comparators");
const createHash = require("../util/createHash");
const { getUndoPath } = require("../util/identifier");
const memoize = require("../util/memoize");
const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
const removeBOM = require("../util/removeBOM");
const CssGenerator = require("./CssGenerator");
const CssModule = require("./CssModule");
const CssParser = require("./CssParser");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../config/defaults").OutputNormalizedWithDefaults} OutputOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./CssModule").Inheritance} Inheritance */
/** @typedef {import("./CssModule").CssModuleCreateData} CssModuleCreateData */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
/** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */
/** @typedef {import("../Chunk").ChunkFilenameTemplate} ChunkFilenameTemplate */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/**
* Defines the render context type used by this module.
* @typedef {object} RenderContext
* @property {Chunk} chunk the chunk
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {string} uniqueName the unique name
* @property {string} undoPath undo path to css file
* @property {string=} hash compilation hash
* @property {CssModule[]} modules modules
*/
/**
* Defines the chunk render context type used by this module.
* @typedef {object} ChunkRenderContext
* @property {Chunk=} chunk the chunk
* @property {ChunkGraph=} chunkGraph the chunk graph
* @property {CodeGenerationResults=} codeGenerationResults results of code generation
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {string} undoPath undo path to css file
* @property {string=} hash compilation hash
* @property {WeakMap<Source, ModuleFactoryCacheEntry>} moduleFactoryCache moduleFactoryCache
* @property {Source} moduleSourceContent content
*/
/**
* Defines the compilation hooks type used by this module.
* @typedef {object} CompilationHooks
* @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage
* @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash
* @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
*/
/**
* Defines the module factory cache entry type used by this module.
* @typedef {object} ModuleFactoryCacheEntry
* @property {string} undoPath - The undo path to the CSS file
* @property {string | undefined} hash - The compilation hash
* @property {Inheritance} inheritance - The inheritance chain
* @property {CachedSource} source - The cached source
*/
const getCssLoadingRuntimeModule = memoize(() =>
require("./CssLoadingRuntimeModule")
);
const getCssInjectStyleRuntimeModule = memoize(() =>
require("./CssInjectStyleRuntimeModule")
);
/**
* Returns ], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema.
* @param {string} name name
* @returns {{ oneOf: [{ $ref: string }], definitions: import("../../schemas/WebpackOptions.json")["definitions"] }} schema
*/
const getSchema = (name) => {
const { definitions } = require("../../schemas/WebpackOptions.json");
return {
definitions,
oneOf: [{ $ref: `#/definitions/${name}` }]
};
};
const parserValidationOptions = {
name: "Css Modules Plugin",
baseDataPath: "parser"
};
const generatorValidationOptions = {
name: "Css Modules Plugin",
baseDataPath: "generator"
};
/** @type {WeakMap<Compilation, CompilationHooks>} */
const compilationHooksMap = new WeakMap();
const PLUGIN_NAME = "CssModulesPlugin";
class CssModulesPlugin {
/**
* Returns the attached hooks.
* @param {Compilation} compilation the compilation
* @returns {CompilationHooks} the attached hooks
*/
static getCompilationHooks(compilation) {
if (!(compilation instanceof Compilation)) {
throw new TypeError(
"The 'compilation' argument must be an instance of Compilation"
);
}
let hooks = compilationHooksMap.get(compilation);
if (hooks === undefined) {
hooks = {
renderModulePackage: new SyncWaterfallHook([
"source",
"module",
"renderContext"
]),
chunkHash: new SyncHook(["chunk", "hash", "context"]),
orderModules: new SyncBailHook(["chunk", "modules", "compilation"])
};
compilationHooksMap.set(compilation, hooks);
}
return hooks;
}
constructor() {
/** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
this._moduleFactoryCache = new WeakMap();
}
/**
* Applies the plugin by registering its hooks on the compiler.
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
const hooks = CssModulesPlugin.getCompilationHooks(compilation);
compilation.dependencyFactories.set(
CssImportDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
CssImportDependency,
new CssImportDependency.Template()
);
compilation.dependencyFactories.set(
CssUrlDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
CssUrlDependency,
new CssUrlDependency.Template()
);
compilation.dependencyFactories.set(
CssIcssImportDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
CssIcssImportDependency,
new CssIcssImportDependency.Template()
);
compilation.dependencyTemplates.set(
CssIcssExportDependency,
new CssIcssExportDependency.Template()
);
compilation.dependencyTemplates.set(
CssIcssSymbolDependency,
new CssIcssSymbolDependency.Template()
);
compilation.dependencyTemplates.set(
StaticExportsDependency,
new StaticExportsDependency.Template()
);
for (const type of [
CSS_MODULE_TYPE,
CSS_MODULE_TYPE_GLOBAL,
CSS_MODULE_TYPE_MODULE,
CSS_MODULE_TYPE_AUTO
]) {
normalModuleFactory.hooks.createParser
.for(type)
.tap(PLUGIN_NAME, (parserOptions) => {
/** @type {undefined | "global" | "local" | "auto"} */
let defaultMode;
switch (type) {
case CSS_MODULE_TYPE: {
compiler.validate(
() => getSchema("CssParserOptions"),
parserOptions,
parserValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssParserOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_GLOBAL: {
defaultMode = "global";
compiler.validate(
() => getSchema("CssModuleParserOptions"),
parserOptions,
parserValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssModuleParserOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_MODULE: {
defaultMode = "local";
compiler.validate(
() => getSchema("CssAutoOrModuleParserOptions"),
parserOptions,
parserValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssAutoOrModuleParserOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_AUTO: {
defaultMode = "auto";
compiler.validate(
() => getSchema("CssAutoOrModuleParserOptions"),
parserOptions,
parserValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssAutoOrModuleParserOptions.check")(
options
)
);
break;
}
}
return new CssParser({
defaultMode,
...parserOptions
});
});
normalModuleFactory.hooks.createGenerator
.for(type)
.tap(PLUGIN_NAME, (generatorOptions) => {
switch (type) {
case CSS_MODULE_TYPE: {
compiler.validate(
() => getSchema("CssGeneratorOptions"),
generatorOptions,
generatorValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssGeneratorOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_GLOBAL: {
compiler.validate(
() => getSchema("CssModuleGeneratorOptions"),
generatorOptions,
generatorValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_MODULE: {
compiler.validate(
() => getSchema("CssModuleGeneratorOptions"),
generatorOptions,
generatorValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
options
)
);
break;
}
case CSS_MODULE_TYPE_AUTO: {
compiler.validate(
() => getSchema("CssModuleGeneratorOptions"),
generatorOptions,
generatorValidationOptions,
(options) =>
require("../../schemas/plugins/css/CssModuleGeneratorOptions.check")(
options
)
);
break;
}
}
return new CssGenerator(
generatorOptions,
compilation.moduleGraph
);
});
normalModuleFactory.hooks.createModuleClass
.for(type)
.tap(PLUGIN_NAME, (createData, resolveData) => {
const exportType =
/** @type {CssParser} */
(createData.parser).options.exportType;
if (resolveData.dependencies.length > 0) {
// When CSS is imported from CSS there is only one dependency
const dependency = resolveData.dependencies[0];
if (dependency instanceof CssImportDependency) {
const parent =
/** @type {CssModule} */
(compilation.moduleGraph.getParentModule(dependency));
if (parent instanceof CssModule) {
/** @type {Inheritance | undefined} */
let inheritance;
if (
parent.cssLayer !== undefined ||
parent.supports ||
parent.media
) {
if (!inheritance) {
inheritance = [];
}
inheritance.push([
parent.cssLayer,
parent.supports,
parent.media
]);
}
if (parent.inheritance) {
if (!inheritance) {
inheritance = [];
}
inheritance.push(...parent.inheritance);
}
return new CssModule(
/** @type {CssModuleCreateData} */
({
...createData,
cssLayer: dependency.layer,
supports: dependency.supports,
media: dependency.media,
inheritance,
exportType: parent.exportType || exportType
})
);
}
return new CssModule(
/** @type {CssModuleCreateData} */
({
...createData,
cssLayer: dependency.layer,
supports: dependency.supports,
media: dependency.media,
exportType
})
);
}
}
return new CssModule(
/** @type {CssModuleCreateData} */
(
/** @type {unknown} */ ({
...createData,
exportType
})
)
);
});
NormalModule.getCompilationHooks(compilation).processResult.tap(
PLUGIN_NAME,
(result, module) => {
if (module.type === type) {
const [source, ...rest] = result;
return [removeBOM(source), ...rest];
}
return result;
}
);
}
JavascriptModulesPlugin.getCompilationHooks(
compilation
).renderModuleContent.tap(PLUGIN_NAME, (source, module) => {
const injectCssStylesVar =
module instanceof ConcatenatedModule &&
module.modules.find(
(m) =>
m instanceof CssModule &&
m.exportType === "style" &&
!(/** @type {CssGenerator} */ (m.generator)._exportsOnly)
);
const injectHMRCode =
(module instanceof CssModule && module.hot) ||
(module instanceof ConcatenatedModule &&
module.rootModule instanceof CssModule &&
module.rootModule.hot);
if (injectCssStylesVar) {
source = new ConcatSource(
"var __webpack_css_styles__ = [];",
"\n",
source
);
}
if (injectHMRCode) {
const currentModule = /** @type {CssModule} */ (
module instanceof ConcatenatedModule ? module.rootModule : module
);
const exportType = currentModule.exportType || "link";
// When exportType !== "link", modules behave like JavaScript modules
if (["link", "style"].includes(exportType)) {
// For exportType === "link", we can optimize with self-acceptance
const cssData = /** @type {BuildInfo} */ (module.buildInfo)
.cssData;
if (!cssData) {
return source;
}
const exports = cssData.exports;
/** @type {Record<string, string>} */
const exportsObj = {};
for (const [key, value] of exports) {
exportsObj[key] = value;
}
const stringifiedExports = JSON.stringify(
JSON.stringify(exportsObj)
);
const hmrCode = Template.asString([
"",
`var __webpack_css_exports__ = ${stringifiedExports};`,
"// only invalidate when locals change",
"if (module.hot.data && module.hot.data.__webpack_css_exports__ && module.hot.data.__webpack_css_exports__ != __webpack_css_exports__) {",
Template.indent("module.hot.invalidate();"),
"} else {",
Template.indent("module.hot.accept();"),
"}",
"module.hot.dispose(function(data) {",
Template.indent([
"data.__webpack_css_exports__ = __webpack_css_exports__;"
]),
"});"
]);
source = new ConcatSource(source, "\n", new RawSource(hmrCode));
}
}
if (injectCssStylesVar) {
/** @type {ConcatSource} */
(source).add(
"for (let i = 0; i < __webpack_css_styles__.length; i++) {\n" +
`${RuntimeGlobals.cssInjectStyle}(__webpack_css_styles__[i][0], __webpack_css_styles__[i][1]);\n` +
"}"
);
}
return source;
});
/** @type {WeakMap<Chunk, CssModule[]>} */
const orderedCssModulesPerChunk = new WeakMap();
compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => {
const { chunkGraph } = compilation;
for (const chunk of compilation.chunks) {
if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
orderedCssModulesPerChunk.set(
chunk,
this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
);
}
}
});
compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => {
hooks.chunkHash.call(chunk, hash, context);
});
compilation.hooks.contentHash.tap(PLUGIN_NAME, (chunk) => {
const {
chunkGraph,
moduleGraph,
runtimeTemplate,
outputOptions: {
hashSalt,
hashDigest,
hashDigestLength,
hashFunction
}
} = compilation;
const hash = createHash(hashFunction);
if (hashSalt) hash.update(hashSalt);
const codeGenerationResults =
/** @type {CodeGenerationResults} */
(compilation.codeGenerationResults);
hooks.chunkHash.call(chunk, hash, {
chunkGraph,
codeGenerationResults,
moduleGraph,
runtimeTemplate
});
const modules = orderedCssModulesPerChunk.get(chunk);
if (modules) {
for (const module of modules) {
hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
}
}
const digest = hash.digest(hashDigest);
chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
});
compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
const { chunkGraph } = compilation;
const { hash, chunk, codeGenerationResults, runtimeTemplate } =
options;
if (chunk instanceof HotUpdateChunk) return result;
/** @type {CssModule[] | undefined} */
const modules = orderedCssModulesPerChunk.get(chunk);
if (modules !== undefined) {
const { path: filename, info } = compilation.getPathWithInfo(
CssModulesPlugin.getChunkFilenameTemplate(
chunk,
compilation.outputOptions
),
{
hash,
runtime: chunk.runtime,
chunk,
contentHashType: "css"
}
);
const undoPath = getUndoPath(
filename,
compilation.outputOptions.path,
false
);
result.push({
render: () =>
this.renderChunk(
{
chunk,
chunkGraph,
codeGenerationResults,
uniqueName: compilation.outputOptions.uniqueName,
undoPath,
hash,
modules,
runtimeTemplate
},
hooks
),
filename,
info,
identifier: `css${chunk.id}`,
hash: chunk.contentHash.css
});
}
return result;
});
const globalChunkLoading = compilation.outputOptions.chunkLoading;
/**
* Checks whether this css modules plugin is enabled for chunk.
* @param {Chunk} chunk the chunk
* @returns {boolean} true, when enabled
*/
const isEnabledForChunk = (chunk) => {
const options = chunk.getEntryOptions();
const chunkLoading =
options && options.chunkLoading !== undefined
? options.chunkLoading
: globalChunkLoading;
return chunkLoading === "jsonp" || chunkLoading === "import";
};
/** @type {WeakSet<Chunk>} */
const onceForChunkSet = new WeakSet();
/**
* Handles the hook callback for this code path.
* @param {Chunk} chunk chunk to check
* @param {RuntimeRequirements} set runtime requirements
*/
const handler = (chunk, set) => {
if (onceForChunkSet.has(chunk)) return;
onceForChunkSet.add(chunk);
if (!isEnabledForChunk(chunk)) return;
const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
};
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.hasCssModules)
.tap(PLUGIN_NAME, handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.ensureChunkHandlers)
.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
if (!isEnabledForChunk(chunk)) return;
if (
!chunkGraph.hasModuleInGraph(
chunk,
(m) =>
m.type === CSS_MODULE_TYPE ||
m.type === CSS_MODULE_TYPE_GLOBAL ||
m.type === CSS_MODULE_TYPE_MODULE ||
m.type === CSS_MODULE_TYPE_AUTO
)
) {
return;
}
set.add(RuntimeGlobals.hasOwnProperty);
set.add(RuntimeGlobals.publicPath);
set.add(RuntimeGlobals.getChunkCssFilename);
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.hmrDownloadUpdateHandlers)
.tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
if (!isEnabledForChunk(chunk)) return;
if (
!chunkGraph.hasModuleInGraph(
chunk,
(m) =>
m.type === CSS_MODULE_TYPE ||
m.type === CSS_MODULE_TYPE_GLOBAL ||
m.type === CSS_MODULE_TYPE_MODULE ||
m.type === CSS_MODULE_TYPE_AUTO
)
) {
return;
}
set.add(RuntimeGlobals.publicPath);
set.add(RuntimeGlobals.getChunkCssFilename);
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.cssInjectStyle)
.tap(PLUGIN_NAME, (chunk, set) => {
// Same as above: namespace stub is enough.
set.add(RuntimeGlobals.requireScope);
const CssInjectStyleRuntimeModule =
getCssInjectStyleRuntimeModule();
compilation.addRuntimeModule(
chunk,
new CssInjectStyleRuntimeModule(set)
);
});
}
);
}
/**
* Gets modules in order.
* @param {Chunk} chunk chunk
* @param {Iterable<Module> | undefined} modules unordered modules
* @param {Compilation} compilation compilation
* @returns {Module[]} ordered modules
*/
getModulesInOrder(chunk, modules, compilation) {
if (!modules) return [];
/** @type {Module[]} */
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) => ({
module,
index: chunkGroup.getModulePostOrderIndex(module)
}))
.filter((item) => item.index !== undefined)
.sort(
(a, b) =>
/** @type {number} */ (b.index) - /** @type {number} */ (a.index)
)
.map((item) => item.module);
return { list: sortedModules, set: new Set(sortedModules) };
}
);
if (modulesByChunkGroup.length === 1) {
return modulesByChunkGroup[0].list.reverse();
}
const boundCompareModulesByFullName = compareModulesByFullName(
compilation.compiler
);
/**
* Compares module lists.
* @param {{ list: Module[] }} a a
* @param {{ list: Module[] }} b b
* @returns {-1 | 0 | 1} result
*/
const compareModuleLists = ({ list: a }, { list: b }) => {
if (a.length === 0) {
return b.length === 0 ? 0 : 1;
}
if (b.length === 0) return -1;
return boundCompareModulesByFullName(a[a.length - 1], b[b.length - 1]);
};
modulesByChunkGroup.sort(compareModuleLists);
/** @type {Module[]} */
const finalModules = [];
for (;;) {
/** @type {Set<Module>} */
const failedModules = new Set();
const list = modulesByChunkGroup[0].list;
if (list.length === 0) {
// done, everything empty
break;
}
/** @type {Module} */
let selectedModule = list[list.length - 1];
/** @type {undefined | false | Module} */
let hasFailed;
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) {
const fallbackModule = /** @type {Module} */ (hasFailed);
const fallbackIssuers = [
...compilation.moduleGraph
.getIncomingConnectionsByOriginModule(fallbackModule)
.keys()
].filter(Boolean);
const selectedIssuers = [
...compilation.moduleGraph
.getIncomingConnectionsByOriginModule(selectedModule)
.keys()
].filter(Boolean);
const allIssuers = [
...new Set([...fallbackIssuers, ...selectedIssuers])
]
.map((m) =>
/** @type {Module} */ (m).readableIdentifier(
compilation.requestShortener
)
)
.sort();
// There is a not resolve-able conflict with the selectedModule
compilation.warnings.push(
new WebpackError(
`chunk ${
chunk.name || chunk.id
}\nConflicting order between ${fallbackModule.readableIdentifier(
compilation.requestShortener
)} and ${selectedModule.readableIdentifier(
compilation.requestShortener
)}\nCSS modules are imported in:\n - ${allIssuers.join("\n - ")}`
)
);
selectedModule = fallbackModule;
}
// 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;
}
/**
* Gets ordered chunk css modules.
* @param {Chunk} chunk chunk
* @param {ChunkGraph} chunkGraph chunk graph
* @param {Compilation} compilation compilation
* @returns {CssModule[]} ordered css modules
*/
getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
/** @type {string | undefined} */
let charset;
const hooks = CssModulesPlugin.getCompilationHooks(compilation);
/**
* @param {Iterable<Module> | undefined} iter modules pre-sorted by full module name
* @returns {Module[]} ordered modules
*/
const orderModules = (iter) => {
const modules = iter ? [...iter] : [];
const result = hooks.orderModules.call(chunk, modules, compilation);
if (result !== undefined) return result;
return this.getModulesInOrder(chunk, modules, compilation);
};
return /** @type {CssModule[]} */ ([
...orderModules(
chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
CSS_IMPORT_TYPE,
compareModulesByFullName(compilation.compiler)
)
),
...orderModules(
chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
CSS_TYPE,
compareModulesByFullName(compilation.compiler)
)
).map((module) => {
if (
typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
"undefined"
) {
if (
typeof charset !== "undefined" &&
charset !== /** @type {BuildInfo} */ (module.buildInfo).charset
) {
const err = new WebpackError(
`Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
compilation.requestShortener
)} (in chunk ${chunk.name || chunk.id}) specifies "${
/** @type {BuildInfo} */ (module.buildInfo).charset
}", but "${charset}" was expected, all modules must use the same character set`
);
err.chunk = chunk;
err.module = module;
err.hideStack = true;
compilation.warnings.push(err);
}
if (typeof charset === "undefined") {
charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
}
}
return module;
})
]);
}
/**
* Renders css module source.
* @param {CssModule} module css module
* @param {ChunkRenderContext} renderContext options object
* @param {CompilationHooks} hooks hooks
* @returns {Source | null} css module source
*/
static renderModule(module, renderContext, hooks) {
const { undoPath, hash, moduleFactoryCache, moduleSourceContent } =
renderContext;
const cacheEntry = moduleFactoryCache.get(moduleSourceContent);
/** @type {Inheritance} */
const inheritance = [[module.cssLayer, module.supports, module.media]];
if (module.inheritance) {
inheritance.push(...module.inheritance);
}
/** @type {CachedSource} */
let source;
if (
cacheEntry &&
cacheEntry.undoPath === undoPath &&
cacheEntry.hash === hash &&
cacheEntry.inheritance.length === inheritance.length &&
cacheEntry.inheritance.every(([layer, supports, media], i) => {
const item = inheritance[i];
if (Array.isArray(item)) {
return layer === item[0] && supports === item[1] && media === item[2];
}
return false;
})
) {
source = cacheEntry.source;
} else {
if (!moduleSourceContent) return null;
const moduleSourceCode =
/** @type {string} */
(moduleSourceContent.source());
const replaceSource = new ReplaceSource(moduleSourceContent);
const autoPlaceholder = CssUrlDependency.PUBLIC_PATH_AUTO;
const autoPlaceholderLen = autoPlaceholder.length;
for (
let idx = moduleSourceCode.indexOf(autoPlaceholder);
idx !== -1;
idx = moduleSourceCode.indexOf(
autoPlaceholder,
idx + autoPlaceholderLen
)
) {
replaceSource.replace(idx, idx + autoPlaceholderLen - 1, undoPath);
}
if (hash) {
const hashPrefix = CssUrlDependency.PUBLIC_PATH_FULL_HASH;
const hashPrefixLen = hashPrefix.length;
const sourceLen = moduleSourceCode.length;
let idx = moduleSourceCode.indexOf(hashPrefix);
while (idx !== -1) {
let digitEnd = idx + hashPrefixLen;
while (digitEnd < sourceLen) {
const cc = moduleSourceCode.charCodeAt(digitEnd);
if (cc < 48 || cc > 57) break;
digitEnd++;
}
let nextSearch;
if (
digitEnd > idx + hashPrefixLen &&
digitEnd + 1 < sourceLen &&
moduleSourceCode.charCodeAt(digitEnd) === 95 &&
moduleSourceCode.charCodeAt(digitEnd + 1) === 95
) {
const length = Number.parseInt(
moduleSourceCode.slice(idx + hashPrefixLen, digitEnd),
10
);
replaceSource.replace(
idx,
digitEnd + 1,
length === 0 ? hash : hash.slice(0, length)
);
nextSearch = digitEnd + 2;
} else {
nextSearch = idx + hashPrefixLen;
}
idx = moduleSourceCode.indexOf(hashPrefix, nextSearch);
}
}
/** @type {Source} */
let moduleSource = replaceSource;
for (let i = 0; i < inheritance.length; i++) {
const layer = inheritance[i][0];
const supports = inheritance[i][1];
const media = inheritance[i][2];
if (media) {
moduleSource = new ConcatSource(
`@media ${media} {\n`,
new PrefixSource("\t", moduleSource),
"}\n"
);
}
if (supports) {
moduleSource = new ConcatSource(
`@supports (${supports}) {\n`,
new PrefixSource("\t", moduleSource),
"}\n"
);
}
// Layer can be anonymous
if (layer !== undefined && layer !== null) {
moduleSource = new ConcatSource(
`@layer${layer ? ` ${layer}` : ""} {\n`,
new PrefixSource("\t", moduleSource),
"}\n"
);
}
}
if (moduleSource) {
moduleSource = new ConcatSource(moduleSource, "\n");
}
source = new CachedSource(moduleSource);
moduleFactoryCache.set(moduleSourceContent, {
inheritance,
undoPath,
hash,
source
});
}
return tryRunOrWebpackError(
() => hooks.renderModulePackage.call(source, module, renderContext),
"CssModulesPlugin.getCompilationHooks().renderModulePackage"
);
}
/**
* Renders generated source.
* @param {RenderContext} renderContext the render context
* @param {CompilationHooks} hooks hooks
* @returns {Source} generated source
*/
renderChunk(
{
undoPath,
chunk,
codeGenerationResults,
modules,
runtimeTemplate,
chunkGraph,
hash
},
hooks
) {
const source = new ConcatSource();
/** @type {string | undefined} */
let charset;
for (const module of modules) {
if (
typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
"undefined" &&
typeof charset === "undefined"
) {
charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
}
try {
const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
const moduleSourceContent =
/** @type {Source} */
(
codeGenResult.sources.get(CSS_TYPE) ||
codeGenResult.sources.get(CSS_IMPORT_TYPE)
);
const moduleSource = CssModulesPlugin.renderModule(
module,
{
undoPath,
hash,
chunk,
chunkGraph,
codeGenerationResults,
moduleSourceContent,
moduleFactoryCache: this._moduleFactoryCache,
runtimeTemplate
},
hooks
);
if (moduleSource) {
source.add(moduleSource);
}
} catch (err) {
/** @type {Error} */
(err).message += `\nduring rendering of css ${module.identifier()}`;
throw err;
}
}
chunk.rendered = true;
if (charset) {
return new ConcatSource(`@charset "${charset}";\n`, source);
}
return source;
}
/**
* Gets chunk filename template.
* @param {Chunk} chunk chunk
* @param {OutputOptions} outputOptions output options
* @returns {ChunkFilenameTemplate} used filename template
*/
static getChunkFilenameTemplate(chunk, outputOptions) {
if (chunk.cssFilenameTemplate) {
return chunk.cssFilenameTemplate;
} else if (chunk.canBeInitial()) {
return outputOptions.cssFilename;
}
return outputOptions.cssChunkFilename;
}
/**
* Returns true, when the chunk has css.
* @param {Chunk} chunk chunk
* @param {ChunkGraph} chunkGraph chunk graph
* @returns {boolean} true, when the chunk has css
*/
static chunkHasCss(chunk, chunkGraph) {
return (
Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)
) ||
Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
)
);
}
}
module.exports = CssModulesPlugin;