Skip to content

Commit 72598ef

Browse files
feat: new features for CSS modules
1 parent d9e1148 commit 72598ef

57 files changed

Lines changed: 5992 additions & 45 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/hip-flies-rescue.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"webpack": minor
3+
---
4+
5+
Added the `localIdentHashFunction` option to configure the hash function to be used for hashing.
6+
Additionally, the `localIdentName` option can now be a function.

declarations/WebpackOptions.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,9 @@ export type CssGeneratorExportsOnly = boolean;
785785
/**
786786
* Configure the generated local ident name.
787787
*/
788-
export type CssGeneratorLocalIdentName = string;
788+
export type CssGeneratorLocalIdentName =
789+
| string
790+
| import("../lib/TemplatedPathPlugin").TemplatePathFn;
789791
/**
790792
* Configure how CSS content is exported as default.
791793
*/
@@ -3061,6 +3063,10 @@ export interface CssModuleGeneratorOptions {
30613063
* Number of chars which are used for the hash.
30623064
*/
30633065
localIdentHashDigestLength?: HashDigestLength;
3066+
/**
3067+
* Algorithm used for generation the hash (see node.js crypto package).
3068+
*/
3069+
localIdentHashFunction?: HashFunction;
30643070
/**
30653071
* Any string which is added to the hash to salt it.
30663072
*/

lib/Compilation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ const { isSourceEqual } = require("./util/source");
349349
* @property {HashWithLengthFunction=} contentHashWithLength
350350
* @property {boolean=} noChunkHash
351351
* @property {string=} url
352+
* @property {string=} local
352353
* @property {PrepareIdFunction=} prepareId
353354
*/
354355

lib/TemplatedPathPlugin.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,10 @@ const deprecated = (fn, message, code) => {
138138
/**
139139
* @param {TemplatePath} path the raw path
140140
* @param {PathData} data context data
141-
* @param {AssetInfo | undefined} assetInfo extra info about the asset (will be written to)
141+
* @param {AssetInfo=} assetInfo extra info about the asset (will be written to)
142142
* @returns {string} the interpolated path
143143
*/
144-
const replacePathVariables = (path, data, assetInfo) => {
144+
const interpolate = (path, data, assetInfo) => {
145145
const chunkGraph = data.chunkGraph;
146146

147147
/** @type {Map<string, Replacer>} */
@@ -392,9 +392,10 @@ class TemplatedPathPlugin {
392392
*/
393393
apply(compiler) {
394394
compiler.hooks.compilation.tap(plugin, (compilation) => {
395-
compilation.hooks.assetPath.tap(plugin, replacePathVariables);
395+
compilation.hooks.assetPath.tap(plugin, interpolate);
396396
});
397397
}
398398
}
399399

400400
module.exports = TemplatedPathPlugin;
401+
module.exports.interpolate = interpolate;

lib/config/defaults.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ const {
5858
/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
5959
/** @typedef {import("../../declarations/WebpackOptions").Loader} Loader */
6060
/** @typedef {import("../../declarations/WebpackOptions").Mode} Mode */
61+
/** @typedef {import("../../declarations/WebpackOptions").HashFunction} HashFunction */
6162
/** @typedef {import("../../declarations/WebpackOptions").HashSalt} HashSalt */
62-
/** @typedef {import("../../declarations/WebpackOptions").HashSalt} HashDigest */
63+
/** @typedef {import("../../declarations/WebpackOptions").HashDigest} HashDigest */
6364
/** @typedef {import("../../declarations/WebpackOptions").HashDigestLength} HashDigestLength */
6465
/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
6566
/** @typedef {import("../../declarations/WebpackOptions").Node} WebpackNode */
@@ -407,11 +408,8 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
407408
hashSalt: /** @type {NonNullable<Output["hashSalt"]>} */ (
408409
options.output.hashSalt
409410
),
410-
hashDigest: /** @type {NonNullable<Output["hashDigest"]>} */ (
411-
options.output.hashDigest
412-
),
413-
hashDigestLength: /** @type {NonNullable<Output["hashDigestLength"]>} */ (
414-
options.output.hashDigestLength
411+
hashFunction: /** @type {NonNullable<Output["hashFunction"]>} */ (
412+
options.output.hashFunction
415413
),
416414
syncWebAssembly:
417415
/** @type {NonNullable<ExperimentsNormalized["syncWebAssembly"]>} */
@@ -794,17 +792,15 @@ const applyCssGeneratorOptionsDefaults = (
794792
* @param {TargetProperties | false} options.targetProperties target properties
795793
* @param {Mode | undefined} options.mode mode
796794
* @param {HashSalt} options.hashSalt hash salt
797-
* @param {HashDigest} options.hashDigest hash digest
798-
* @param {HashDigestLength} options.hashDigestLength hash digest length
795+
* @param {HashFunction} options.hashFunction hash function
799796
* @param {boolean} options.outputModule is output.module enabled
800797
* @returns {void}
801798
*/
802799
const applyModuleDefaults = (
803800
module,
804801
{
805802
hashSalt,
806-
hashDigest,
807-
hashDigestLength,
803+
hashFunction,
808804
cache,
809805
syncWebAssembly,
810806
asyncWebAssembly,
@@ -992,6 +988,13 @@ const applyModuleDefaults = (
992988
localIdentHashSalt
993989
);
994990

991+
D(
992+
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]>} */
993+
(module.generator[type]),
994+
"localIdentHashFunction",
995+
hashFunction
996+
);
997+
995998
D(
996999
/** @type {NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_AUTO]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]> | NonNullable<GeneratorOptionsByModuleTypeKnown[CSS_MODULE_TYPE_MODULE]>} */
9971000
(module.generator[type]),

lib/dependencies/CssIcssExportDependency.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"use strict";
77

88
const { CSS_TYPE, JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants");
9+
const { interpolate } = require("../TemplatedPathPlugin");
910
const WebpackError = require("../WebpackError");
1011
const { cssExportConvention } = require("../util/conventions");
1112
const createHash = require("../util/createHash");
@@ -19,6 +20,7 @@ const NullDependency = require("./NullDependency");
1920
const getCssParser = memoize(() => require("../css/CssParser"));
2021

2122
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
23+
/** @typedef {import("../../declarations/WebpackOptions").HashFunction} HashFunction */
2224
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */
2325
/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */
2426
/** @typedef {import("../CssModule")} CssModule */
@@ -62,13 +64,18 @@ const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => {
6264

6365
let localIdentHash = "";
6466

65-
if (/\[(?:fullhash|hash)\]/.test(localIdentName)) {
67+
if (
68+
typeof localIdentName === "function" ||
69+
/\[(?:fullhash|hash)\]/.test(localIdentName)
70+
) {
6671
const hashSalt = generator.options.localIdentHashSalt;
6772
const hashDigest =
6873
/** @type {string} */
6974
(generator.options.localIdentHashDigest);
7075
const hashDigestLength = generator.options.localIdentHashDigestLength;
71-
const { hashFunction } = runtimeTemplate.outputOptions;
76+
const hashFunction =
77+
/** @type {HashFunction} */
78+
(generator.options.localIdentHashFunction);
7279

7380
const hash = createHash(hashFunction);
7481

@@ -88,7 +95,10 @@ const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => {
8895

8996
let contentHash = "";
9097

91-
if (/\[contenthash\]/.test(localIdentName)) {
98+
if (
99+
typeof localIdentName === "function" ||
100+
/\[contenthash\]/.test(localIdentName)
101+
) {
92102
const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
93103
const source = module.originalSource();
94104

@@ -110,26 +120,32 @@ const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => {
110120
);
111121
}
112122

113-
let localIdent = runtimeTemplate.compilation.getPath(localIdentName, {
123+
let localIdent = interpolate(localIdentName, {
114124
prepareId: (id) => {
115125
if (typeof id !== "string") return id;
116126

117-
return id
118-
.replace(/^([.-]|[^a-z0-9_-])+/i, "")
119-
.replace(/[^a-z0-9_-]+/gi, "_");
127+
return (
128+
id
129+
.replace(/^([.-]|[^a-z0-9_-])+/i, "")
130+
// We keep the `@` symbol because it can be used in the package name (e.g. `@company/package`), and if we replace it with `_`, a class conflict may occur.
131+
// For example - `@import "@foo/package/style.module.css"` and `@import "foo/package/style.module.css"` (`foo` is a package, `package` is just a directory) will create a class conflict.
132+
.replace(/[^a-z0-9@_-]+/gi, "_")
133+
);
120134
},
121135
filename: relativeResourcePath,
122136
hash: localIdentHash,
137+
local,
123138
contentHash,
124139
chunkGraph,
125140
module
126141
});
127142

128-
if (/\[local\]/.test(localIdentName)) {
143+
// TODO move these things into interpolate
144+
if (/\[local\]/.test(localIdent)) {
129145
localIdent = localIdent.replace(/\[local\]/g, local);
130146
}
131147

132-
if (/\[uniqueName\]/.test(localIdentName)) {
148+
if (/\[uniqueName\]/.test(localIdent)) {
133149
localIdent = localIdent.replace(
134150
/\[uniqueName\]/g,
135151
/** @type {string} */ (uniqueName)

schemas/WebpackOptions.check.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schemas/WebpackOptions.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,15 @@
415415
},
416416
"CssGeneratorLocalIdentName": {
417417
"description": "Configure the generated local ident name.",
418-
"type": "string"
418+
"anyOf": [
419+
{
420+
"type": "string"
421+
},
422+
{
423+
"instanceof": "Function",
424+
"tsType": "import(\"../lib/TemplatedPathPlugin\").TemplatePathFn"
425+
}
426+
]
419427
},
420428
"CssGeneratorOptions": {
421429
"description": "Generator options for css modules.",
@@ -453,6 +461,9 @@
453461
"localIdentHashDigestLength": {
454462
"$ref": "#/definitions/HashDigestLength"
455463
},
464+
"localIdentHashFunction": {
465+
"$ref": "#/definitions/HashFunction"
466+
},
456467
"localIdentHashSalt": {
457468
"$ref": "#/definitions/HashSalt"
458469
},

schemas/plugins/css/CssModuleGeneratorOptions.check.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)