webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,185 lines (1,092 loc) • 35.4 kB
JavaScript
/*
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;