add basic css modules support
This commit is contained in:
parent
453e5cac05
commit
bdf4d83aca
|
@ -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")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
|
@ -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": () =>
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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;
|
||||
} */
|
|
@ -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}`
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/** @type {import("../../../../").Configuration[]} */
|
||||
module.exports = [
|
||||
{
|
||||
target: "web",
|
||||
mode: "development",
|
||||
experiments: {
|
||||
css: true
|
||||
}
|
||||
},
|
||||
{
|
||||
target: "web",
|
||||
mode: "production",
|
||||
experiments: {
|
||||
css: true
|
||||
}
|
||||
}
|
||||
];
|
Loading…
Reference in New Issue