Skip to content

Commit 8c7700b

Browse files
fix: handle @charset at-rules in CSS modules
1 parent 2944286 commit 8c7700b

28 files changed

Lines changed: 908 additions & 43 deletions

.changeset/spicy-buckets-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Handle `@charset` at-rules in CSS modules.

lib/Module.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ const makeSerializable = require("./util/makeSerializable");
206206
* @property {boolean=} javascriptModule for external modules
207207
* @property {boolean=} active for lazy compilation modules
208208
* @property {CssData=} cssData for css modules
209+
* @property {string=} charset for css modules (charset at-rule)
209210
* @property {JsonData=} jsonData for json modules
210211
* @property {Set<string>=} topLevelDeclarations top level declaration names
211212
*/

lib/css/CssGenerator.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,11 @@ class CssGenerator extends Generator {
9999
// Iterate through module.dependencies to maintain source order
100100
for (const dep of module.dependencies) {
101101
if (dep instanceof CssImportDependency) {
102-
/** @type {CssModule} */
103102
const depModule = /** @type {CssModule} */ (moduleGraph.getModule(dep));
104103
const importVar = generateContext.runtimeTemplate.moduleExports({
105104
module: depModule,
106105
chunkGraph: generateContext.chunkGraph,
107-
request: /** @type {CssModule} */ (depModule).userRequest,
106+
request: depModule.userRequest,
108107
weak: false,
109108
runtimeRequirements: generateContext.runtimeRequirements
110109
});
@@ -115,16 +114,12 @@ class CssGenerator extends Generator {
115114
);
116115
parts.push({
117116
expr: `(${RuntimeGlobals.compatGetDefaultExport}(${importVar})() || "")`,
118-
type: /** @type {CssParserExportType} */ (
119-
/** @type {CssModule} */ (depModule).exportType
120-
)
117+
type: /** @type {CssParserExportType} */ (depModule.exportType)
121118
});
122119
} else {
123120
parts.push({
124121
expr: importVar,
125-
type: /** @type {CssParserExportType} */ (
126-
/** @type {CssModule} */ (depModule).exportType
127-
)
122+
type: /** @type {CssParserExportType} */ (depModule.exportType)
128123
});
129124
}
130125
}
@@ -353,7 +348,8 @@ class CssGenerator extends Generator {
353348
module,
354349
generateContext
355350
);
356-
const jsLiteral = cssSource
351+
352+
let jsLiteral = cssSource
357353
? this._cssSourceToJsStringLiteral(cssSource, module)
358354
: new RawSource('""');
359355

@@ -367,19 +363,33 @@ class CssGenerator extends Generator {
367363
);
368364

369365
const args = importCode.map((part) => part.expr);
370-
return new ConcatSource(
366+
367+
jsLiteral = new ConcatSource(
371368
`${RuntimeGlobals.cssMergeStyleSheets}([${args.join(", ")}, `,
372369
jsLiteral,
373370
"])"
374371
);
372+
} else {
373+
jsLiteral = new ConcatSource(
374+
`${generateContext.runtimeTemplate.concatenation(
375+
...importCode
376+
)} + `,
377+
jsLiteral
378+
);
375379
}
376-
return new ConcatSource(
377-
`${generateContext.runtimeTemplate.concatenation(
378-
...importCode
379-
)} + `,
380+
}
381+
382+
if (
383+
exportType === "css-style-sheet" &&
384+
typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
385+
"undefined"
386+
) {
387+
jsLiteral = new ConcatSource(
388+
`'@charset "${/** @type {BuildInfo} */ (module.buildInfo).charset}";\\n' + `,
380389
jsLiteral
381390
);
382391
}
392+
383393
return jsLiteral;
384394
};
385395
/**

lib/css/CssModulesPlugin.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,9 @@ class CssModulesPlugin {
843843
* @returns {CssModule[]} ordered css modules
844844
*/
845845
getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
846+
/** @type {string | undefined} */
847+
let charset;
848+
846849
return /** @type {CssModule[]} */ ([
847850
...this.getModulesInOrder(
848851
chunk,
@@ -861,7 +864,37 @@ class CssModulesPlugin {
861864
compareModulesByFullName(compilation.compiler)
862865
),
863866
compilation
864-
)
867+
).map((module) => {
868+
if (
869+
typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
870+
"undefined"
871+
) {
872+
if (
873+
typeof charset !== "undefined" &&
874+
charset !== /** @type {BuildInfo} */ (module.buildInfo).charset
875+
) {
876+
const err = new WebpackError(
877+
`Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
878+
compilation.requestShortener
879+
)} (in chunk ${chunk.name || chunk.id}) specifies "${
880+
/** @type {BuildInfo} */ (module.buildInfo).charset
881+
}", but "${charset}" was expected, all modules must use the same character set`
882+
);
883+
884+
err.chunk = chunk;
885+
err.module = module;
886+
err.hideStack = true;
887+
888+
compilation.warnings.push(err);
889+
}
890+
891+
if (typeof charset === "undefined") {
892+
charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
893+
}
894+
}
895+
896+
return module;
897+
})
865898
]);
866899
}
867900

@@ -982,7 +1015,19 @@ class CssModulesPlugin {
9821015
hooks
9831016
) {
9841017
const source = new ConcatSource();
1018+
1019+
/** @type {string | undefined} */
1020+
let charset;
1021+
9851022
for (const module of modules) {
1023+
if (
1024+
typeof (/** @type {BuildInfo} */ (module.buildInfo).charset) !==
1025+
"undefined" &&
1026+
typeof charset === "undefined"
1027+
) {
1028+
charset = /** @type {BuildInfo} */ (module.buildInfo).charset;
1029+
}
1030+
9861031
try {
9871032
const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
9881033
const moduleSourceContent =
@@ -1013,7 +1058,13 @@ class CssModulesPlugin {
10131058
throw err;
10141059
}
10151060
}
1061+
10161062
chunk.rendered = true;
1063+
1064+
if (charset) {
1065+
return new ConcatSource(`@charset "${charset}";\n`, source);
1066+
}
1067+
10171068
return source;
10181069
}
10191070

lib/css/CssParser.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2347,6 +2347,29 @@ class CssParser extends Parser {
23472347

23482348
return eatUntilSemi(input, start);
23492349
}
2350+
case "@charset": {
2351+
const atRuleEnd = eatUntilSemi(input, start);
2352+
2353+
if (/** @type {CssModule} */ (module).exportType === "style") {
2354+
return atRuleEnd;
2355+
}
2356+
2357+
const dep = new ConstDependency("", [start, atRuleEnd + 1]);
2358+
module.addPresentationalDependency(dep);
2359+
2360+
const value = walkCssTokens.eatString(input, end);
2361+
2362+
if (!value) {
2363+
return atRuleEnd;
2364+
}
2365+
2366+
/** @type {BuildInfo} */
2367+
(module.buildInfo).charset = input
2368+
.slice(value[0] + 1, value[1] - 1)
2369+
.toUpperCase();
2370+
2371+
return atRuleEnd;
2372+
}
23502373
case "@import": {
23512374
if (!this.options.import) {
23522375
return eatUntilSemi(input, end);
@@ -2360,7 +2383,7 @@ class CssParser extends Parser {
23602383
start,
23612384
end
23622385
);
2363-
return end;
2386+
return eatUntilSemi(input, end);
23642387
}
23652388

23662389
return processAtImport(input, start, end);

0 commit comments

Comments
 (0)