add basic css modules support

This commit is contained in:
Tobias Koppers 2021-12-15 08:34:31 +01:00
parent 453e5cac05
commit bdf4d83aca
8 changed files with 243 additions and 16 deletions

View File

@ -10,6 +10,7 @@ const HotUpdateChunk = require("../HotUpdateChunk");
const RuntimeGlobals = require("../RuntimeGlobals");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const {
@ -61,7 +62,7 @@ const escapeCssIdentifierPart = (str, useOptionalUnderscore) => {
/[^a-zA-Z0-9_\u0081-\uffff-]/g,
s => `\\${s}`
);
return useOptionalUnderscore && /^[0-9_]/.test(escaped)
return useOptionalUnderscore && /^[0-9_-]/.test(escaped)
? `_${escaped}`
: escaped;
};
@ -86,6 +87,10 @@ class CssModulesPlugin {
CssUrlDependency,
new CssUrlDependency.Template()
);
compilation.dependencyTemplates.set(
CssLocalIdentifierDependency,
new CssLocalIdentifierDependency.Template()
);
compilation.dependencyTemplates.set(
CssExportDependency,
new CssExportDependency.Template()
@ -112,13 +117,18 @@ class CssModulesPlugin {
.for("css/global")
.tap(plugin, parserOptions => {
validateParserOptions(parserOptions);
return new CssParser({ allowPseudoBlocks: false });
return new CssParser({
allowPseudoBlocks: false,
allowModeSwitch: false
});
});
normalModuleFactory.hooks.createParser
.for("css/module")
.tap(plugin, parserOptions => {
validateParserOptions(parserOptions);
return new CssParser({ allowPseudoBlocks: true });
return new CssParser({
defaultMode: "local"
});
});
normalModuleFactory.hooks.createGenerator
.for("css")

View File

@ -9,6 +9,7 @@ const Parser = require("../Parser");
const ConstDependency = require("../dependencies/ConstDependency");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const walkCssTokens = require("./walkCssTokens");
@ -95,9 +96,15 @@ const explainMode = mode => {
};
class CssParser extends Parser {
constructor({ allowPseudoBlocks = true } = {}) {
constructor({
allowPseudoBlocks = true,
allowModeSwitch = true,
defaultMode = "global"
} = {}) {
super();
this.allowPseudoBlocks = allowPseudoBlocks;
this.allowModeSwitch = allowModeSwitch;
this.defaultMode = defaultMode;
}
/**
@ -340,6 +347,27 @@ class CssParser extends Parser {
}
return end;
},
class: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL: {
if (
modeData === "local" ||
(this.defaultMode === "local" && modeData === undefined)
) {
const dep = new CssLocalIdentifierDependency(
input.slice(start + 1, end),
[start + 1, end]
);
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(end);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
}
break;
}
}
return end;
},
leftParenthesis: (input, start, end) => {
return end;
},
@ -349,24 +377,30 @@ class CssParser extends Parser {
pseudoClass: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL: {
if (this.allowPseudoBlocks) {
const name = input.slice(start, end);
if (name === ":global") {
modeData = ":global";
} else if (name === ":local") {
modeData = ":local";
} else if (name === ":export") {
const pos = parseExports(input, end);
const dep = new ConstDependency("", [start, pos]);
module.addPresentationalDependency(dep);
return pos;
}
const name = input.slice(start, end);
if (this.allowModeSwitch && name === ":global") {
modeData = "global";
} else if (this.allowModeSwitch && name === ":local") {
modeData = "local";
} else if (this.allowPseudoBlocks && name === ":export") {
const pos = parseExports(input, end);
const dep = new ConstDependency("", [start, pos]);
module.addPresentationalDependency(dep);
return pos;
}
break;
}
}
return end;
},
pseudoFunction: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL: {
break;
}
}
return end;
},
comma: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL:

View File

@ -0,0 +1,104 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const makeSerializable = require("../util/makeSerializable");
const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
class CssLocalIdentifierDependency extends NullDependency {
/**
* @param {string} name name
* @param {[number, number]} range range
*/
constructor(name, range) {
super();
this.name = name;
this.range = range;
}
get type() {
return "css local identifier";
}
/**
* Returns the exported names
* @param {ModuleGraph} moduleGraph module graph
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
const name = this.name;
return {
exports: [
{
name,
canMangle: true
}
],
dependencies: undefined
};
}
serialize(context) {
const { write } = context;
write(this.name);
write(this.range);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this.name = read();
this.range = read();
super.deserialize(context);
}
}
const escapeCssIdentifier = str => {
const escaped = `${str}`.replace(
// cspell:word uffff
/[^a-zA-Z0-9_\u0081-\uffff-]/g,
s => `\\${s}`
);
return /^[0-9-]/.test(escaped) ? `_${escaped}` : escaped;
};
CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTemplate extends (
NullDependency.Template
) {
/**
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(
dependency,
source,
{ module, moduleGraph, chunkGraph, runtime, cssExports }
) {
const dep = /** @type {CssLocalIdentifierDependency} */ (dependency);
const used = moduleGraph
.getExportInfo(module, dep.name)
.getUsedName(dep.name, runtime);
const moduleId = chunkGraph.getModuleId(module);
const identifier = escapeCssIdentifier((used || "_") + "_" + moduleId);
source.replace(dep.range[0], dep.range[1] - 1, identifier);
if (used) cssExports.set(used, identifier);
}
};
makeSerializable(
CssLocalIdentifierDependency,
"webpack/lib/dependencies/CssLocalIdentifierDependency"
);
module.exports = CssLocalIdentifierDependency;

View File

@ -69,6 +69,8 @@ module.exports = {
require("../dependencies/CriticalDependencyWarning"),
"dependencies/CssImportDependency": () =>
require("../dependencies/CssImportDependency"),
"dependencies/CssLocalIdentifierDependency": () =>
require("../dependencies/CssLocalIdentifierDependency"),
"dependencies/CssExportDependency": () =>
require("../dependencies/CssExportDependency"),
"dependencies/CssUrlDependency": () =>

View File

@ -0,0 +1,20 @@
const prod = process.env.NODE_ENV === "production";
it("should allow to create css modules", done => {
prod
? __non_webpack_require__("./249.bundle1.js")
: __non_webpack_require__("./use-style_js.bundle0.js");
import("./use-style.js").then(({ default: x }) => {
try {
expect(x).toEqual({
class: prod ? "S_491" : "class_\\.\\/style\\.module\\.css",
local: prod
? "Zw_491 yl_491 J__491 gc_491"
: "local1_\\.\\/style\\.module\\.css local2_\\.\\/style\\.module\\.css local3_\\.\\/style\\.module\\.css local4_\\.\\/style\\.module\\.css"
});
} catch (e) {
return done(e);
}
done();
}, done);
});

View File

@ -0,0 +1,33 @@
.class {
color: red;
}
.local1,
.local2 :global .global,
.local3 {
color: green;
}
:global .global :local .local4 {
color: yellow;
}
/* .local5:global(.global).local6 {
color: blue;
} */
/* :global(:global(:local(.nested1)).nested2).nested3 {
color: pink;
} */
/* @keyframes localkeyframes {
0% {
}
100% {
}
}
.animation {
animation-name: localkeyframes;
animation: 3s ease-in 1s 2 reverse both paused localkeyframes, localkeyframes;
} */

View File

@ -0,0 +1,7 @@
import * as style from "./style.module.css";
import { local1, local2, local3, local4 } from "./style.module.css";
export default {
class: style.class,
local: `${local1} ${local2} ${local3} ${local4}`
};

View File

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