UNPKG

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,274 lines (1,183 loc) 39.3 kB
/* 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"); /** @import Compilation, { ChunkHashContext } from "../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 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 createHooksRegistry = require("../util/createHooksRegistry"); const { getUndoPath } = require("../util/identifier"); const implicitTypeLoaderFallback = require("../util/implicitTypeLoaderFallback"); const lazyModule = require("../util/lazyModule"); const memoize = require("../util/memoize"); const { digestNonNumericOnlyWithFull } = require("../util/nonNumericOnlyHash"); const preloadModuleType = require("../util/preloadModuleType"); const { PUBLIC_PATH_AUTO, walkFullHashPlaceholders } = require("../util/publicPathPlaceholder"); const { removeBOMFromResult } = require("../util/removeBOM"); const CssModule = require("./CssModule"); /** @import { Source } from "webpack-sources" */ /** * @import { * OutputNormalizedWithDefaults as OutputOptions * } from "../config/defaults" */ /** @import Chunk, { ChunkFilenameTemplate } from "../Chunk" */ /** @import ChunkGraph from "../ChunkGraph" */ /** @import CodeGenerationResults from "../CodeGenerationResults" */ /** @import Compiler from "../Compiler" */ /** @import RuntimeTemplate from "../RuntimeTemplate" */ /** @import CssGenerator from "./CssGenerator" */ /** @import CssParser from "./CssParser" */ /** * @import { * Inheritance, * CssModuleCreateData, * CssModuleBuildInfo * } from "./CssModule" */ /** @import Module, { RuntimeRequirements } from "../Module" */ /** @import Hash from "../util/Hash" */ /** * 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 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 */ // Lazy: this plugin is applied on every build (`experiments.css` defaults on), // but the parser and generator are only reached once CSS is in the graph — and // the parser pulls in the CSS tokenizer. const getCssParser = lazyModule(() => require("./CssParser")); const getCssGenerator = lazyModule(() => require("./CssGenerator")); const CSS_MODULE_TYPES = [ CSS_MODULE_TYPE, CSS_MODULE_TYPE_GLOBAL, CSS_MODULE_TYPE_MODULE, CSS_MODULE_TYPE_AUTO ]; const getCssImportDependency = memoize(() => require("../dependencies/CssImportDependency") ); const getCssLoadingRuntimeModule = lazyModule(() => require("./CssLoadingRuntimeModule") ); const getCssInjectStyleRuntimeModule = lazyModule(() => require("./CssInjectStyleRuntimeModule") ); /** * @typedef {object} PublicPathPlaceholderPlan * @property {number[]} autos start offsets of each `PUBLIC_PATH_AUTO` placeholder * @property {{ start: number, end: number, length: number }[]} hashes `[start, end)` ranges of each `PUBLIC_PATH_FULL_HASH` placeholder and its requested hash length */ /** * Public-path placeholder offsets are invariant for a given module source — * they don't depend on `undoPath`/`hash`/`inheritance`/runtime — so scan once * and reuse across renders instead of re-materializing and re-scanning the * source on every cache miss. Weakly keyed so entries release with the source. * @type {WeakMap<Source, PublicPathPlaceholderPlan>} */ const publicPathPlaceholderPlans = new WeakMap(); /** * Memoized inheritance chain per module, revalidated against its source * fields — `updateCacheModule` reassigns them on watch rebuilds. * @type {WeakMap<CssModule, { cssLayer: CssModule["cssLayer"], supports: CssModule["supports"], media: CssModule["media"], inheritance: CssModule["inheritance"], chain: Inheritance }>} */ const moduleInheritanceCache = new WeakMap(); /** * Gets the memoized inheritance chain of a css module. * @param {CssModule} module css module * @returns {Inheritance} inheritance chain including the module's own entry */ const getModuleInheritance = (module) => { const entry = moduleInheritanceCache.get(module); if ( entry !== undefined && entry.cssLayer === module.cssLayer && entry.supports === module.supports && entry.media === module.media && entry.inheritance === module.inheritance ) { return entry.chain; } /** @type {Inheritance} */ const chain = [[module.cssLayer, module.supports, module.media]]; if (module.inheritance) chain.push(...module.inheritance); moduleInheritanceCache.set(module, { cssLayer: module.cssLayer, supports: module.supports, media: module.media, inheritance: module.inheritance, chain }); return chain; }; /** * Locate the public-path placeholders in a materialized module source. * @param {string} content materialized module source * @returns {PublicPathPlaceholderPlan} placeholder offsets */ const computePublicPathPlaceholderPlan = (content) => { /** @type {number[]} */ const autos = []; const autoLen = PUBLIC_PATH_AUTO.length; for ( let idx = content.indexOf(PUBLIC_PATH_AUTO); idx !== -1; idx = content.indexOf(PUBLIC_PATH_AUTO, idx + autoLen) ) { autos.push(idx); } /** @type {{ start: number, end: number, length: number }[]} */ const hashes = []; walkFullHashPlaceholders(content, (start, end, length) => { hashes.push({ start, end, length }); }); return { autos, hashes }; }; /** * 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" }; const PLUGIN_NAME = "CssModulesPlugin"; const createCompilationHooks = () => ({ /** * @type {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} * @since 5.94.0 */ renderModulePackage: new SyncWaterfallHook([ "source", "module", "renderContext" ]), /** * @type {SyncHook<[Chunk, Hash, ChunkHashContext]>} * @since 5.94.0 */ chunkHash: new SyncHook(["chunk", "hash", "context"]), /** * 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. * @type {SyncBailHook<[Chunk, Module[], Compilation], Module[] | undefined | void>} * @since 5.107.0 */ orderModules: new SyncBailHook(["chunk", "modules", "compilation"]) }); /** * Defines the compilation hooks type used by this module. * @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks */ class CssModulesPlugin { 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) { // `"auto"` marks implicit enablement (no user rule for CSS files) — see // `applyExperimentsDefaults`. Only then loaders win over the built-in type. // TODO webpack 6: css defaults to `true`, drop this implicit-only fallback. const implicitlyEnabled = compiler.options.experiments.css === "auto"; compiler.hooks.compilation.tap( PLUGIN_NAME, (compilation, { normalModuleFactory }) => { if (implicitlyEnabled) { implicitTypeLoaderFallback( normalModuleFactory, PLUGIN_NAME, /\.css$/i, CSS_MODULE_TYPE_AUTO ); } const hooks = CssModulesPlugin.getCompilationHooks(compilation); let dependenciesRegistered = false; // runs when the first css module is prepared, so a build without // css never loads these dependency classes const registerDependencies = () => { // every css type taps this — the registration is shared if (dependenciesRegistered) return; dependenciesRegistered = true; const CssImportDependency = require("../dependencies/CssImportDependency"); const CssUrlDependency = require("../dependencies/CssUrlDependency"); const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency"); const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency"); const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency"); 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() ); }; for (const type of CSS_MODULE_TYPES) { normalModuleFactory.hooks.prepareModuleType .for(type) .tap(PLUGIN_NAME, registerDependencies); } compilation.dependencyTemplates.set( StaticExportsDependency, new StaticExportsDependency.Template() ); preloadModuleType( normalModuleFactory, PLUGIN_NAME, CSS_MODULE_TYPES.map((type) => [ type, [getCssParser, getCssGenerator] ]) ); for (const type of CSS_MODULE_TYPES) { 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 (getCssParser.loaded())({ 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 (getCssGenerator.loaded())( generatorOptions, compilation.moduleGraph ); }); normalModuleFactory.hooks.createModuleClass .for(type) .tap(PLUGIN_NAME, (createData, resolveData) => { const exportType = /** @type {CssParser} */ (createData.parser).options.exportType; // When CSS is imported from CSS there is only one dependency const dependency = resolveData.dependencies.length > 0 ? resolveData.dependencies[0] : undefined; if (dependency instanceof getCssImportDependency()) { return new CssModule( /** @type {CssModuleCreateData} */ ({ ...createData, cssLayer: dependency.layer, supports: dependency.supports, media: dependency.media, inheritance: dependency.inheritance, exportType: dependency.exportType || exportType }) ); } return new CssModule( /** @type {CssModuleCreateData} */ ( /** @type {unknown} */ ({ ...createData, exportType }) ) ); }); NormalModule.getCompilationHooks(compilation).processResult.tap( PLUGIN_NAME, (result, module) => module.type === type ? removeBOMFromResult(result) : result ); } JavascriptModulesPlugin.getCompilationHooks( compilation ).renderModuleContent.tap(PLUGIN_NAME, (source, module, ctx) => { 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 {CssModuleBuildInfo} */ ( 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 (${ctx.runtimeTemplate.optionalChaining( "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)); } } [chunk.contentHash.css, chunk.contentHashFull.css] = digestNonNumericOnlyWithFull(hash, hashDigest, 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"; }; /** * The neutral platform's no-DOM branch stores styles on the global. * @param {RuntimeRequirements} set runtime requirements */ const addNoDomStyleRegistry = (set) => { if ( compilation.runtimeTemplate.isNeutralPlatform() && !compilation.outputOptions.environment.globalThis ) { set.add(RuntimeGlobals.global); } }; /** * Whether a reachable chunk carries css, which is what the loading branch is * rendered from — a css module in the graph is not enough. * @param {Chunk} chunk the chunk * @param {ChunkGraph} chunkGraph the chunk graph * @returns {boolean} true, when there is a stylesheet to load */ const hasCssToLoad = (chunk, chunkGraph) => { for (const referenced of chunk.getAllReferencedChunks()) { if (CssModulesPlugin.chunkHasCss(referenced, chunkGraph)) { return true; } } return false; }; /** @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; compilation.addLazyRuntimeModule( chunk, getCssLoadingRuntimeModule, (CssLoadingRuntimeModule) => 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 (!hasCssToLoad(chunk, chunkGraph)) return; set.add(RuntimeGlobals.hasOwnProperty); // A baked url carries where the stylesheet is, so neither the public // path nor the name lookup is read for it. Asked exactly as the // loading runtime module asks, so the two always agree. const baked = compilation.runtimeTemplate.analyzableCssChunkUrls( chunk, chunkGraph, set ); if (baked === null || !baked.complete) { set.add(RuntimeGlobals.publicPath); set.add(RuntimeGlobals.getChunkCssFilename); } // Asked here rather than for `hasCssModules`: the loading runtime // module renders nothing without loading or hmr, so declaring it // there ships the polyfill to a build that never reads it. addNoDomStyleRegistry(set); // A chunk whose only stylesheet is an `@import` external has no css // module, so `hasCssModules` never asks for the loading runtime. handler(chunk, set); }); 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); addNoDomStyleRegistry(set); }); compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.cssInjectStyle) .tap(PLUGIN_NAME, (chunk, set) => { // Same as above: namespace stub is enough. set.add(RuntimeGlobals.requireScope); addNoDomStyleRegistry(set); compilation.addLazyRuntimeModule( chunk, getCssInjectStyleRuntimeModule, (CssInjectStyleRuntimeModule) => 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); }; const comparator = compareModulesByFullName(compilation.compiler); const importModules = orderModules( chunkGraph.getOrderedChunkModulesIterableBySourceType( chunk, CSS_IMPORT_TYPE, comparator ) ); const cssModules = orderModules( chunkGraph.getOrderedChunkModulesIterableBySourceType( chunk, CSS_TYPE, comparator ) ); for (const module of cssModules) { if ( typeof ( /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset ) !== "undefined" ) { if ( typeof charset !== "undefined" && charset !== /** @type {CssModuleBuildInfo} */ (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 {CssModuleBuildInfo} */ (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 {CssModuleBuildInfo} */ (module.buildInfo) .charset; } } } return /** @type {CssModule[]} */ ([...importModules, ...cssModules]); } /** * 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); const inheritance = getModuleInheritance(module); /** @type {CachedSource} */ let source; if ( cacheEntry && cacheEntry.undoPath === undoPath && cacheEntry.hash === hash && // Memoized per module, so identity implies equal entries. cacheEntry.inheritance === inheritance ) { source = cacheEntry.source; } else { if (!moduleSourceContent) return null; let plan = publicPathPlaceholderPlans.get(moduleSourceContent); if (plan === undefined) { plan = computePublicPathPlaceholderPlan( /** @type {string} */ (moduleSourceContent.source()) ); publicPathPlaceholderPlans.set(moduleSourceContent, plan); } /** @type {Source} */ let moduleSource = moduleSourceContent; // Apply placeholder substitutions only when present; the common // (no-placeholder) case skips the ReplaceSource wrapper entirely. if (plan.autos.length > 0 || (hash && plan.hashes.length > 0)) { const replaceSource = new ReplaceSource(moduleSourceContent); const autoLen = PUBLIC_PATH_AUTO.length; for (let i = 0; i < plan.autos.length; i++) { const start = plan.autos[i]; replaceSource.replace(start, start + autoLen - 1, undoPath); } if (hash) { for (let i = 0; i < plan.hashes.length; i++) { const { start, end, length } = plan.hashes[i]; // `end` is exclusive; ReplaceSource.replace takes an inclusive end. replaceSource.replace( start, end - 1, length === 0 ? hash : hash.slice(0, length) ); } } 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 {CssModuleBuildInfo} */ (module.buildInfo).charset ) !== "undefined" && typeof charset === "undefined" ) { charset = /** @type {CssModuleBuildInfo} */ (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) ) ); } } CssModulesPlugin.getCompilationHooks = createHooksRegistry( createCompilationHooks ); module.exports = CssModulesPlugin;