allow to generate only exports for css in node

add more options to the DeterministicModuleIdsPlugin to allow to opt-in into a fixed mode

optimize module iterations for module id assignment
This commit is contained in:
Tobias Koppers 2022-01-18 18:14:50 +01:00
parent e550b2c949
commit 1489b91a98
24 changed files with 617 additions and 126 deletions

View File

@ -2723,6 +2723,15 @@ export interface AssetResourceGeneratorOptions {
*/
publicPath?: RawPublicPath;
}
/**
* Options for css handling.
*/
export interface CssExperimentOptions {
/**
* Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.
*/
exportsOnly?: boolean;
}
/**
* Generator options for css modules.
*/
@ -2809,10 +2818,6 @@ export interface ExperimentsCommon {
* Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.
*/
cacheUnaffected?: boolean;
/**
* Enable css support.
*/
css?: boolean;
/**
* Apply defaults of next major version.
*/
@ -3453,6 +3458,10 @@ export interface ExperimentsExtra {
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriAllowedUris | HttpUriOptions;
/**
* Enable css support.
*/
css?: boolean | CssExperimentOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
@ -3466,6 +3475,10 @@ export interface ExperimentsNormalizedExtra {
* Build http(s): urls using a lockfile and resource content cache.
*/
buildHttp?: HttpUriOptions;
/**
* Enable css support.
*/
css?: CssExperimentOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/

View File

@ -291,7 +291,7 @@ class WebpackOptionsApply extends OptionsApply {
if (options.experiments.css) {
const CssModulesPlugin = require("./css/CssModulesPlugin");
new CssModulesPlugin().apply(compiler);
new CssModulesPlugin(options.experiments.css).apply(compiler);
}
if (options.experiments.lazyCompilation) {

View File

@ -16,6 +16,7 @@ const {
} = require("./target");
/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptions */
/** @typedef {import("../../declarations/WebpackOptions").CssExperimentOptions} CssExperimentOptions */
/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */
/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */
/** @typedef {import("../../declarations/WebpackOptions").Experiments} Experiments */
@ -160,7 +161,11 @@ const applyWebpackOptionsDefaults = options => {
D(options, "recordsInputPath", false);
D(options, "recordsOutputPath", false);
applyExperimentsDefaults(options.experiments, { production, development });
applyExperimentsDefaults(options.experiments, {
production,
development,
targetProperties
});
const futureDefaults = options.experiments.futureDefaults;
@ -265,9 +270,13 @@ const applyWebpackOptionsDefaults = options => {
* @param {Object} options options
* @param {boolean} options.production is production
* @param {boolean} options.development is development mode
* @param {TargetProperties | false} options.targetProperties target properties
* @returns {void}
*/
const applyExperimentsDefaults = (experiments, { production, development }) => {
const applyExperimentsDefaults = (
experiments,
{ production, development, targetProperties }
) => {
D(experiments, "futureDefaults", false);
D(experiments, "backCompat", !experiments.futureDefaults);
D(experiments, "topLevelAwait", experiments.futureDefaults);
@ -278,12 +287,20 @@ const applyExperimentsDefaults = (experiments, { production, development }) => {
D(experiments, "lazyCompilation", undefined);
D(experiments, "buildHttp", undefined);
D(experiments, "cacheUnaffected", experiments.futureDefaults);
D(experiments, "css", experiments.futureDefaults);
F(experiments, "css", () => (experiments.futureDefaults ? {} : undefined));
if (typeof experiments.buildHttp === "object") {
D(experiments.buildHttp, "frozen", production);
D(experiments.buildHttp, "upgrade", false);
}
if (typeof experiments.css === "object") {
D(
experiments.css,
"exportsOnly",
!targetProperties || !targetProperties.document
);
}
};
/**
@ -461,7 +478,7 @@ const applyJavascriptParserOptionsDefaults = (
* @param {boolean} options.cache is caching enabled
* @param {boolean} options.syncWebAssembly is syncWebAssembly enabled
* @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled
* @param {boolean} options.css is css enabled
* @param {CssExperimentOptions} options.css is css enabled
* @param {boolean} options.futureDefaults is future defaults enabled
* @returns {void}
*/
@ -1083,7 +1100,7 @@ const applyPerformanceDefaults = (performance, { production }) => {
* @param {Object} options options
* @param {boolean} options.production is production
* @param {boolean} options.development is development
* @param {boolean} options.css is css enabled
* @param {CssExperimentOptions} options.css is css enabled
* @param {boolean} options.records using records
* @returns {void}
*/

View File

@ -180,6 +180,9 @@ const getNormalizedWebpackOptions = config => {
experiments.lazyCompilation,
options =>
options === true ? {} : options === false ? undefined : options
),
css: optionalNestedConfig(experiments.css, options =>
options === true ? {} : options === false ? undefined : options
)
})),
externals: config.externals,

View File

@ -0,0 +1,145 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Sergey Melyukov @smelukov
*/
"use strict";
const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources");
const { UsageState } = require("../ExportsInfo");
const Generator = require("../Generator");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../util/Hash")} Hash */
const TYPES = new Set(["javascript"]);
class CssExportsGenerator extends Generator {
constructor() {
super();
}
/**
* @param {NormalModule} module module for which the bailout reason should be determined
* @param {ConcatenationBailoutReasonContext} context context
* @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
*/
getConcatenationBailoutReason(module, context) {
return undefined;
}
/**
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
* @returns {Source} generated code
*/
generate(module, generateContext) {
const source = new ReplaceSource(new RawSource(""));
const initFragments = [];
const cssExports = new Map();
generateContext.runtimeRequirements.add(RuntimeGlobals.module);
const runtimeRequirements = new Set();
const templateContext = {
runtimeTemplate: generateContext.runtimeTemplate,
dependencyTemplates: generateContext.dependencyTemplates,
moduleGraph: generateContext.moduleGraph,
chunkGraph: generateContext.chunkGraph,
module,
runtime: generateContext.runtime,
runtimeRequirements: runtimeRequirements,
concatenationScope: generateContext.concatenationScope,
codeGenerationResults: generateContext.codeGenerationResults,
initFragments,
cssExports
};
const handleDependency = dependency => {
const constructor = /** @type {new (...args: any[]) => Dependency} */ (
dependency.constructor
);
const template = generateContext.dependencyTemplates.get(constructor);
if (!template) {
throw new Error(
"No template for dependency: " + dependency.constructor.name
);
}
template.apply(dependency, source, templateContext);
};
module.dependencies.forEach(handleDependency);
if (generateContext.concatenationScope) {
const source = new ConcatSource();
const usedIdentifiers = new Set();
for (const [k, v] of cssExports) {
let identifier = Template.toIdentifier(k);
let i = 0;
while (usedIdentifiers.has(identifier)) {
identifier = Template.toIdentifier(k + i);
}
usedIdentifiers.add(identifier);
generateContext.concatenationScope.registerExport(k, identifier);
source.add(
`${
generateContext.runtimeTemplate.supportsConst ? "const" : "var"
} ${identifier} = ${JSON.stringify(v)};\n`
);
}
return source;
} else {
const otherUsed =
generateContext.moduleGraph
.getExportsInfo(module)
.otherExportsInfo.getUsed(generateContext.runtime) !==
UsageState.Unused;
if (otherUsed) {
generateContext.runtimeRequirements.add(
RuntimeGlobals.makeNamespaceObject
);
}
return new RawSource(
`${otherUsed ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${
module.moduleArgument
}.exports = {\n${Array.from(
cssExports,
([k, v]) => `\t${JSON.stringify(k)}: ${JSON.stringify(v)}`
).join(",\n")}\n}${otherUsed ? ")" : ""};`
);
}
}
/**
* @param {NormalModule} module fresh module
* @returns {Set<string>} available types (do not mutate)
*/
getTypes(module) {
return TYPES;
}
/**
* @param {NormalModule} module the module
* @param {string=} type source type
* @returns {number} estimate size of the module
*/
getSize(module, type) {
return 42;
}
/**
* @param {Hash} hash hash that will be modified
* @param {UpdateHashContext} updateHashContext context for updating hash
*/
updateHash(hash, { module }) {}
}
module.exports = CssExportsGenerator;

View File

@ -192,9 +192,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
"exports, module",
`module.exports = exports;`
)}).bind(null, exports); ${
withHmr
? "moduleIds.push(token); target[token].cssExports = exports; "
: ""
withHmr ? "moduleIds.push(token); " : ""
}token = ""; exports = {}; exportsWithId.length = 0; }`,
`else if(cc == ${cc("\\")}) { token += data[++i] }`,
`else { token += data[i]; }`

View File

@ -19,10 +19,12 @@ const { compareModulesByIdentifier } = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
const memoize = require("../util/memoize");
const CssExportsGenerator = require("./CssExportsGenerator");
const CssGenerator = require("./CssGenerator");
const CssParser = require("./CssParser");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").CssExperimentOptions} CssExperimentOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
@ -70,6 +72,12 @@ const escapeCss = (str, omitOptionalUnderscore) => {
const plugin = "CssModulesPlugin";
class CssModulesPlugin {
/**
* @param {CssExperimentOptions} options options
*/
constructor({ exportsOnly = false }) {
this._exportsOnly = exportsOnly;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
@ -143,19 +151,25 @@ class CssModulesPlugin {
.for("css")
.tap(plugin, generatorOptions => {
validateGeneratorOptions(generatorOptions);
return new CssGenerator();
return this._exportsOnly
? new CssExportsGenerator()
: new CssGenerator();
});
normalModuleFactory.hooks.createGenerator
.for("css/global")
.tap(plugin, generatorOptions => {
validateGeneratorOptions(generatorOptions);
return new CssGenerator();
return this._exportsOnly
? new CssExportsGenerator()
: new CssGenerator();
});
normalModuleFactory.hooks.createGenerator
.for("css/module")
.tap(plugin, generatorOptions => {
validateGeneratorOptions(generatorOptions);
return new CssGenerator();
return this._exportsOnly
? new CssExportsGenerator()
: new CssGenerator();
});
const orderedCssModulesPerChunk = new WeakMap();
compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {

View File

@ -9,7 +9,7 @@ const {
compareModulesByPreOrderIndexOrIdentifier
} = require("../util/comparators");
const {
getUsedModuleIds,
getUsedModuleIdsAndModules,
getFullModuleName,
assignDeterministicIds
} = require("./IdHelpers");
@ -18,8 +18,17 @@ const {
/** @typedef {import("../Module")} Module */
class DeterministicModuleIdsPlugin {
constructor(options) {
this.options = options || {};
/**
* @param {Object} options options
* @param {string=} options.context context relative to which module identifiers are computed
* @param {function(Module): boolean=} options.test selector function for modules
* @param {number=} options.maxLength maximum id length in digits (used as starting point)
* @param {number=} options.salt hash salt for ids
* @param {boolean=} options.fixedLength do not increase the maxLength to find an optimal id space size
* @param {boolean=} options.failOnConflict throw an error when id conflicts occur (instead of rehashing)
*/
constructor(options = {}) {
this.options = options;
}
/**
@ -31,40 +40,51 @@ class DeterministicModuleIdsPlugin {
compiler.hooks.compilation.tap(
"DeterministicModuleIdsPlugin",
compilation => {
compilation.hooks.moduleIds.tap(
"DeterministicModuleIdsPlugin",
modules => {
const chunkGraph = compilation.chunkGraph;
const context = this.options.context
? this.options.context
: compiler.context;
const maxLength = this.options.maxLength || 3;
compilation.hooks.moduleIds.tap("DeterministicModuleIdsPlugin", () => {
const chunkGraph = compilation.chunkGraph;
const context = this.options.context
? this.options.context
: compiler.context;
const maxLength = this.options.maxLength || 3;
const failOnConflict = this.options.failOnConflict || false;
const fixedLength = this.options.fixedLength || false;
const salt = this.options.salt || 0;
let conflicts = 0;
const usedIds = getUsedModuleIds(compilation);
assignDeterministicIds(
Array.from(modules).filter(module => {
if (!module.needId) return false;
if (chunkGraph.getNumberOfModuleChunks(module) === 0)
return false;
return chunkGraph.getModuleId(module) === null;
}),
module => getFullModuleName(module, context, compiler.root),
compareModulesByPreOrderIndexOrIdentifier(
compilation.moduleGraph
),
(module, id) => {
const size = usedIds.size;
usedIds.add(`${id}`);
if (size === usedIds.size) return false;
chunkGraph.setModuleId(module, id);
return true;
},
[Math.pow(10, maxLength)],
10,
usedIds.size
const [usedIds, modules] = getUsedModuleIdsAndModules(
compilation,
this.options.test
);
assignDeterministicIds(
modules,
module => getFullModuleName(module, context, compiler.root),
failOnConflict
? () => 0
: compareModulesByPreOrderIndexOrIdentifier(
compilation.moduleGraph
),
(module, id) => {
const size = usedIds.size;
usedIds.add(`${id}`);
if (size === usedIds.size) {
conflicts++;
return false;
}
chunkGraph.setModuleId(module, id);
return true;
},
[Math.pow(10, maxLength)],
fixedLength ? 0 : 10,
usedIds.size,
salt
);
if (failOnConflict && conflicts)
throw new Error(
`Assigning deterministic module ids has lead to ${conflicts} conflict${
conflicts > 1 ? "s" : ""
}.\nIncrease the 'maxLength' to increase the id space and make conflicts less likely (recommended when there are many conflicts or application is expected to grow), or add an 'salt' number to try another hash starting value in the same id space (recommended when there is only a single conflict).`
);
}
);
});
}
);
}

View File

@ -10,7 +10,10 @@ const {
} = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
const { getUsedModuleIds, getFullModuleName } = require("./IdHelpers");
const {
getUsedModuleIdsAndModules,
getFullModuleName
} = require("./IdHelpers");
/** @typedef {import("../../declarations/plugins/HashedModuleIdsPlugin").HashedModuleIdsPluginOptions} HashedModuleIdsPluginOptions */
@ -43,22 +46,16 @@ class HashedModuleIdsPlugin {
apply(compiler) {
const options = this.options;
compiler.hooks.compilation.tap("HashedModuleIdsPlugin", compilation => {
compilation.hooks.moduleIds.tap("HashedModuleIdsPlugin", modules => {
compilation.hooks.moduleIds.tap("HashedModuleIdsPlugin", () => {
const chunkGraph = compilation.chunkGraph;
const context = this.options.context
? this.options.context
: compiler.context;
const usedIds = getUsedModuleIds(compilation);
const modulesInNaturalOrder = Array.from(modules)
.filter(m => {
if (!m.needId) return false;
if (chunkGraph.getNumberOfModuleChunks(m) === 0) return false;
return chunkGraph.getModuleId(module) === null;
})
.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
);
const [usedIds, modules] = getUsedModuleIdsAndModules(compilation);
const modulesInNaturalOrder = modules.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
);
for (const module of modulesInNaturalOrder) {
const ident = getFullModuleName(module, context, compiler.root);
const hash = createHash(options.hashFunction);

View File

@ -234,11 +234,14 @@ const addToMapOfItems = (map, key, value) => {
/**
* @param {Compilation} compilation the compilation
* @returns {Set<string>} used module ids as strings
* @param {function(Module): boolean=} filter filter modules
* @returns {[Set<string>, Module[]]} used module ids as strings and modules without id matching the filter
*/
const getUsedModuleIds = compilation => {
const getUsedModuleIdsAndModules = (compilation, filter) => {
const chunkGraph = compilation.chunkGraph;
const modules = [];
/** @type {Set<string>} */
const usedIds = new Set();
if (compilation.usedModuleIds) {
@ -248,15 +251,23 @@ const getUsedModuleIds = compilation => {
}
for (const module of compilation.modules) {
if (!module.needId) continue;
const moduleId = chunkGraph.getModuleId(module);
if (moduleId !== null) {
usedIds.add(moduleId + "");
} else {
if (
(!filter || filter(module)) &&
chunkGraph.getNumberOfModuleChunks(module) !== 0
) {
modules.push(module);
}
}
}
return usedIds;
return [usedIds, modules];
};
exports.getUsedModuleIds = getUsedModuleIds;
exports.getUsedModuleIdsAndModules = getUsedModuleIdsAndModules;
/**
* @param {Compilation} compilation the compilation
@ -359,6 +370,7 @@ exports.assignNames = assignNames;
* @param {number[]} ranges usable ranges for ids
* @param {number} expandFactor factor to create more ranges
* @param {number} extraSpace extra space to allocate, i. e. when some ids are already used
* @param {number} salt salting number to initialize hashing
* @returns {void}
*/
const assignDeterministicIds = (
@ -368,7 +380,8 @@ const assignDeterministicIds = (
assignId,
ranges = [10],
expandFactor = 10,
extraSpace = 0
extraSpace = 0,
salt = 0
) => {
items.sort(comparator);
@ -384,15 +397,17 @@ const assignDeterministicIds = (
i++;
if (i < ranges.length) {
range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER);
} else {
} else if (expandFactor) {
range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER);
} else {
break;
}
}
for (const item of items) {
const ident = getName(item);
let id;
let i = 0;
let i = salt;
do {
id = numberHash(ident + i++, range);
} while (!assignId(item, id));
@ -401,15 +416,14 @@ const assignDeterministicIds = (
exports.assignDeterministicIds = assignDeterministicIds;
/**
* @param {Set<string>} usedIds used ids
* @param {Iterable<Module>} modules the modules
* @param {Compilation} compilation the compilation
* @returns {void}
*/
const assignAscendingModuleIds = (modules, compilation) => {
const assignAscendingModuleIds = (usedIds, modules, compilation) => {
const chunkGraph = compilation.chunkGraph;
const usedIds = getUsedModuleIds(compilation);
let nextId = 0;
let assignId;
if (usedIds.size > 0) {

View File

@ -10,7 +10,7 @@ const {
getShortModuleName,
getLongModuleName,
assignNames,
getUsedModuleIds,
getUsedModuleIdsAndModules,
assignAscendingModuleIds
} = require("./IdHelpers");
@ -31,27 +31,24 @@ class NamedModuleIdsPlugin {
const { root } = compiler;
compiler.hooks.compilation.tap("NamedModuleIdsPlugin", compilation => {
const { hashFunction } = compilation.outputOptions;
compilation.hooks.moduleIds.tap("NamedModuleIdsPlugin", modules => {
compilation.hooks.moduleIds.tap("NamedModuleIdsPlugin", () => {
const chunkGraph = compilation.chunkGraph;
const context = this.options.context
? this.options.context
: compiler.context;
const [usedIds, modules] = getUsedModuleIdsAndModules(compilation);
const unnamedModules = assignNames(
Array.from(modules).filter(module => {
if (!module.needId) return false;
if (chunkGraph.getNumberOfModuleChunks(module) === 0) return false;
return chunkGraph.getModuleId(module) === null;
}),
modules,
m => getShortModuleName(m, context, root),
(m, shortName) =>
getLongModuleName(shortName, m, context, hashFunction, root),
compareModulesByIdentifier,
getUsedModuleIds(compilation),
usedIds,
(m, name) => chunkGraph.setModuleId(m, name)
);
if (unnamedModules.length > 0) {
assignAscendingModuleIds(unnamedModules, compilation);
assignAscendingModuleIds(usedIds, unnamedModules, compilation);
}
});
});

View File

@ -8,7 +8,10 @@
const {
compareModulesByPreOrderIndexOrIdentifier
} = require("../util/comparators");
const { assignAscendingModuleIds } = require("./IdHelpers");
const {
assignAscendingModuleIds,
getUsedModuleIdsAndModules
} = require("./IdHelpers");
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
@ -22,18 +25,12 @@ class NaturalModuleIdsPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("NaturalModuleIdsPlugin", compilation => {
compilation.hooks.moduleIds.tap("NaturalModuleIdsPlugin", modules => {
const chunkGraph = compilation.chunkGraph;
const modulesInNaturalOrder = Array.from(modules)
.filter(
m =>
m.needId &&
chunkGraph.getNumberOfModuleChunks(m) > 0 &&
chunkGraph.getModuleId(m) === null
)
.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
);
assignAscendingModuleIds(modulesInNaturalOrder, compilation);
const [usedIds, modulesInNaturalOrder] =
getUsedModuleIdsAndModules(compilation);
modulesInNaturalOrder.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
);
assignAscendingModuleIds(usedIds, modulesInNaturalOrder, compilation);
});
});
}

View File

@ -9,7 +9,10 @@ const {
compareModulesByPreOrderIndexOrIdentifier
} = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const { assignAscendingModuleIds } = require("./IdHelpers");
const {
assignAscendingModuleIds,
getUsedModuleIdsAndModules
} = require("./IdHelpers");
/** @typedef {import("../../declarations/plugins/ids/OccurrenceModuleIdsPlugin").OccurrenceModuleIdsPluginOptions} OccurrenceModuleIdsPluginOptions */
/** @typedef {import("../Compiler")} Compiler */
@ -44,15 +47,11 @@ class OccurrenceModuleIdsPlugin {
compiler.hooks.compilation.tap("OccurrenceModuleIdsPlugin", compilation => {
const moduleGraph = compilation.moduleGraph;
compilation.hooks.moduleIds.tap("OccurrenceModuleIdsPlugin", modules => {
compilation.hooks.moduleIds.tap("OccurrenceModuleIdsPlugin", () => {
const chunkGraph = compilation.chunkGraph;
const modulesInOccurrenceOrder = Array.from(modules).filter(
m =>
m.needId &&
chunkGraph.getNumberOfModuleChunks(m) > 0 &&
chunkGraph.getModuleId(m) === null
);
const [usedIds, modulesInOccurrenceOrder] =
getUsedModuleIdsAndModules(compilation);
const occursInInitialChunksMap = new Map();
const occursInAllChunksMap = new Map();
@ -121,7 +120,7 @@ class OccurrenceModuleIdsPlugin {
}
}
for (const m of modules) {
for (const m of modulesInOccurrenceOrder) {
const result =
countOccurs(m) +
chunkGraph.getNumberOfModuleChunks(m) +
@ -147,7 +146,11 @@ class OccurrenceModuleIdsPlugin {
return naturalCompare(a, b);
});
assignAscendingModuleIds(modulesInOccurrenceOrder, compilation);
assignAscendingModuleIds(
usedIds,
modulesInOccurrenceOrder,
compilation
);
});
});
}

File diff suppressed because one or more lines are too long

View File

@ -362,6 +362,17 @@
}
]
},
"CssExperimentOptions": {
"description": "Options for css handling.",
"type": "object",
"additionalProperties": false,
"properties": {
"exportsOnly": {
"description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.",
"type": "boolean"
}
}
},
"CssFilename": {
"description": "Specifies the filename template of output css files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.",
"oneOf": [
@ -769,7 +780,14 @@
},
"css": {
"description": "Enable css support.",
"type": "boolean"
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/definitions/CssExperimentOptions"
}
]
},
"futureDefaults": {
"description": "Apply defaults of next major version.",
@ -821,10 +839,6 @@
"description": "Enable additional in memory caching of modules that are unchanged and reference only unchanged modules.",
"type": "boolean"
},
"css": {
"description": "Enable css support.",
"type": "boolean"
},
"futureDefaults": {
"description": "Apply defaults of next major version.",
"type": "boolean"
@ -875,7 +889,11 @@
},
"css": {
"description": "Enable css support.",
"type": "boolean"
"oneOf": [
{
"$ref": "#/definitions/CssExperimentOptions"
}
]
},
"futureDefaults": {
"description": "Apply defaults of next major version.",

View File

@ -95,7 +95,7 @@ describe("Defaults", () => {
"backCompat": true,
"buildHttp": undefined,
"cacheUnaffected": false,
"css": false,
"css": undefined,
"futureDefaults": false,
"layers": false,
"lazyCompilation": undefined,
@ -1916,10 +1916,12 @@ describe("Defaults", () => {
+ "backCompat": false,
@@ ... @@
- "cacheUnaffected": false,
- "css": false,
- "css": undefined,
- "futureDefaults": false,
+ "cacheUnaffected": true,
+ "css": true,
+ "css": Object {
+ "exportsOnly": false,
+ },
+ "futureDefaults": true,
@@ ... @@
- "topLevelAwait": false,
@ -1979,15 +1981,14 @@ describe("Defaults", () => {
+ "fullySpecified": true,
+ },
+ "type": "css/module",
@@ ... @@
+ },
+ Object {
+ "mimetype": "text/css",
+ "resolve": Object {
+ "fullySpecified": true,
+ "preferRelative": true,
+ },
+ "type": "css",
+ },
+ Object {
@@ ... @@
+ "exportsPresence": "error",
@@ ... @@

View File

@ -607,6 +607,19 @@ Object {
"multiple": false,
"simpleType": "boolean",
},
"experiments-css-exports-only": Object {
"configs": Array [
Object {
"description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.",
"multiple": false,
"path": "experiments.css.exportsOnly",
"type": "boolean",
},
],
"description": "Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.",
"multiple": false,
"simpleType": "boolean",
},
"experiments-future-defaults": Object {
"configs": Array [
Object {

View File

@ -0,0 +1,48 @@
const prod = process.env.NODE_ENV === "production";
it("should allow to create css modules", done => {
import("../css-modules/use-style.js").then(({ default: x }) => {
try {
expect(x).toEqual({
global: undefined,
class: prod ? "my-app-491-S" : "./style.module.css-class",
local: prod
? "my-app-491-Zw my-app-491-yl my-app-491-J_ my-app-491-gc"
: "./style.module.css-local1 ./style.module.css-local2 ./style.module.css-local3 ./style.module.css-local4",
local2: prod
? "my-app-491-Xg my-app-491-AY"
: "./style.module.css-local5 ./style.module.css-local6",
nested: prod
? "my-app-491-RX undefined my-app-491-X2"
: "./style.module.css-nested1 undefined ./style.module.css-nested3",
ident: prod ? "my-app-491-yR" : "./style.module.css-ident",
keyframes: prod ? "my-app-491-y3" : "./style.module.css-localkeyframes",
animation: prod ? "my-app-491-oQ" : "./style.module.css-animation",
vars: prod
? "--my-app-491-y4 my-app-491-gR undefined my-app-491-xk"
: "--./style.module.css-local-color ./style.module.css-vars undefined ./style.module.css-globalVars"
});
} catch (e) {
return done(e);
}
done();
}, done);
});
import * as style from "../css-modules/style.module.css";
it("should allow to import css modules", () => {
expect(style.class).toBe(prod ? "my-app-491-S" : "./style.module.css-class");
expect(style.local1).toBe(
prod ? "my-app-491-Zw" : "./style.module.css-local1"
);
expect(style.local2).toBe(
prod ? "my-app-491-yl" : "./style.module.css-local2"
);
expect(style.local3).toBe(
prod ? "my-app-491-J_" : "./style.module.css-local3"
);
expect(style.local4).toBe(
prod ? "my-app-491-gc" : "./style.module.css-local4"
);
});

View File

@ -0,0 +1 @@
module.exports = require("../css-modules/warnings");

View File

@ -0,0 +1,35 @@
const path = require("path");
const webpack = require("../../../../");
/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
context: path.join(__dirname, "../css-modules"),
entry: "../css-modules-in-node/index.js",
target: "node",
mode: "development",
experiments: {
css: true
}
},
{
context: path.join(__dirname, "../css-modules"),
entry: "../css-modules-in-node/index.js",
target: "node",
mode: "production",
output: {
uniqueName: "my-app"
},
experiments: {
css: true
},
plugins: [
new webpack.ids.DeterministicModuleIdsPlugin({
maxLength: 3,
failOnConflict: true,
fixedLength: true,
test: m => m.type.startsWith("css")
})
]
}
];

View File

@ -1,3 +1,5 @@
const webpack = require("../../../../");
/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
@ -15,6 +17,14 @@ module.exports = [
},
experiments: {
css: true
}
},
plugins: [
new webpack.ids.DeterministicModuleIdsPlugin({
maxLength: 3,
failOnConflict: true,
fixedLength: true,
test: m => m.type.startsWith("css")
})
]
}
];

View File

@ -0,0 +1,74 @@
import * as style from "../exports/style.module.css?ns";
import { a, abc } from "../exports/style.module.css?picked";
import def from "../exports/style.module.css?default";
it("should allow to import a css module", () => {
expect(style).toEqual(
nsObj({
a: "a",
abc: "a b c",
comments: "abc def",
"white space": "abc\n\tdef",
default: "default"
})
);
expect(a).toBe("a");
expect(abc).toBe("a b c");
expect(def).toBe("default");
});
it("should allow to dynamic import a css module", done => {
import("../exports/style.module.css").then(x => {
try {
expect(x).toEqual(
nsObj({
a: "a",
abc: "a b c",
comments: "abc def",
"white space": "abc\n\tdef",
default: "default"
})
);
} catch (e) {
return done(e);
}
done();
}, done);
});
it("should allow to reexport a css module", done => {
import("../exports/reexported").then(x => {
try {
expect(x).toEqual(
nsObj({
a: "a",
abc: "a b c",
comments: "abc def",
"white space": "abc\n\tdef"
})
);
} catch (e) {
return done(e);
}
done();
}, done);
});
it("should allow to import a css module", done => {
import("../exports/imported").then(({ default: x }) => {
try {
expect(x).toEqual(
nsObj({
a: "a",
abc: "a b c",
comments: "abc def",
"white space": "abc\n\tdef",
default: "default"
})
);
} catch (e) {
return done(e);
}
done();
}, done);
});

View File

@ -0,0 +1,8 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "node",
mode: "development",
experiments: {
css: true
}
};

79
types.d.ts vendored
View File

@ -2546,6 +2546,16 @@ declare interface ContextTimestampAndHash {
}
type CreateStatsOptionsContext = KnownCreateStatsOptionsContext &
Record<string, any>;
/**
* Options for css handling.
*/
declare interface CssExperimentOptions {
/**
* Avoid generating and loading a stylesheet and only embed exports from css into output javascript files.
*/
exportsOnly?: boolean;
}
type Declaration = FunctionDeclaration | VariableDeclaration | ClassDeclaration;
declare class DefinePlugin {
/**
@ -2759,8 +2769,58 @@ declare class DeterministicChunkIdsPlugin {
apply(compiler: Compiler): void;
}
declare class DeterministicModuleIdsPlugin {
constructor(options?: any);
options: any;
constructor(options?: {
/**
* context relative to which module identifiers are computed
*/
context?: string;
/**
* selector function for modules
*/
test?: (arg0: Module) => boolean;
/**
* maximum id length in digits (used as starting point)
*/
maxLength?: number;
/**
* hash salt for ids
*/
salt?: number;
/**
* do not increase the maxLength to find an optimal id space size
*/
fixedLength?: boolean;
/**
* throw an error when id conflicts occur (instead of rehashing)
*/
failOnConflict?: boolean;
});
options: {
/**
* context relative to which module identifiers are computed
*/
context?: string;
/**
* selector function for modules
*/
test?: (arg0: Module) => boolean;
/**
* maximum id length in digits (used as starting point)
*/
maxLength?: number;
/**
* hash salt for ids
*/
salt?: number;
/**
* do not increase the maxLength to find an optimal id space size
*/
fixedLength?: boolean;
/**
* throw an error when id conflicts occur (instead of rehashing)
*/
failOnConflict?: boolean;
};
/**
* Apply the plugin
@ -3379,11 +3439,6 @@ declare interface ExperimentsCommon {
*/
cacheUnaffected?: boolean;
/**
* Enable css support.
*/
css?: boolean;
/**
* Apply defaults of next major version.
*/
@ -3419,6 +3474,11 @@ declare interface ExperimentsExtra {
*/
buildHttp?: HttpUriOptions | (string | RegExp | ((uri: string) => boolean))[];
/**
* Enable css support.
*/
css?: boolean | CssExperimentOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/
@ -3435,6 +3495,11 @@ declare interface ExperimentsNormalizedExtra {
*/
buildHttp?: HttpUriOptions;
/**
* Enable css support.
*/
css?: CssExperimentOptions;
/**
* Compile entrypoints and import()s only when they are accessed.
*/