add support for `@keyframes`, `animation`, CSS Variables

This commit is contained in:
Tobias Koppers 2021-12-16 20:42:44 +01:00
parent b5b92dc20e
commit 54fff4dd0a
16 changed files with 454 additions and 66 deletions

View File

@ -183,7 +183,7 @@ module.exports = __webpack_require__.p + "89a353e9c515885abd8e.png";
/******/
/******/ var uniqueName = "app";
/******/ var loadCssChunkData = (chunkId, link) => {
/******/ var data, token = "", token2, exports = {}, exportsWithId = [], i = 0, cc = 1;
/******/ var data, token = "", token2, exports = {}, exportsWithId = [], exportsWithDashes = [], i = 0, cc = 1;
/******/ try { if(!link) link = loadStylesheet(chunkId); data = link.sheet.cssRules; data = data[data.length - 1].style; } catch(e) { data = getComputedStyle(document.head); }
/******/ data = data.getPropertyValue("--webpack-" + uniqueName + "-" + chunkId);
/******/ if(!data) return;
@ -191,8 +191,8 @@ module.exports = __webpack_require__.p + "89a353e9c515885abd8e.png";
/******/ cc = data.charCodeAt(i);
/******/ if(cc == 40) { token2 = token; token = ""; }
/******/ else if(cc == 41) { exports[token2.replace(/^_/, "")] = token.replace(/^_/, ""); token = ""; }
/******/ else if(cc == 47) { token = token.replace(/^_/, ""); exports[token] = token; exportsWithId.push(token); token = ""; }
/******/ else if(!cc || cc == 44) { token = token.replace(/^_/, ""); exportsWithId.forEach((x) => (exports[x] = uniqueName + "-" + token + "-" + exports[x])); __webpack_require__.r(exports); __webpack_require__.m[token] = ((exports, module) => {
/******/ else if(cc == 47 || cc == 37) { token = token.replace(/^_/, ""); exports[token] = token; exportsWithId.push(token); if(cc == 37) exportsWithDashes.push(token); token = ""; }
/******/ else if(!cc || cc == 44) { token = token.replace(/^_/, ""); exportsWithId.forEach((x) => (exports[x] = uniqueName + "-" + token + "-" + exports[x])); exportsWithDashes.forEach((x) => (exports[x] = "--" + exports[x])); __webpack_require__.r(exports); __webpack_require__.m[token] = ((exports, module) => {
/******/ module.exports = exports;
/******/ }).bind(null, exports); token = ""; exports = {}; exportsWithId.length = 0; }
/******/ else if(cc == 92) { token += data[++i] }
@ -427,12 +427,48 @@ body {
background: red;
}
:root {
--app-6-large: 72px;
}
.app-6-main {
font-size: large;
font-size: var(--app-6-large);
color: darkblue;
}
head{--webpack-app-0:_4,_2,_1,_5,main/_6;}
head{--webpack-app-0:_4,_2,_1,_5,large%main/_6;}
```
## production
```javascript
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
.img {
width: 150px;
height: 150px;
background: url(89a353e9c515885abd8e.png);
}
body {
background: green;
font-family: "Open Sans";
}
body {
background: red;
}
:root {
--app-491-b: 72px;
}
.app-491-D {
font-size: var(--app-491-b);
color: darkblue;
}
head{--webpack-app-179:_548,_431,_258,_268,b%D/_491;}
```
# dist/1.output.css
@ -450,16 +486,16 @@ head{--webpack-app-1:_7;}
## Unoptimized
```
assets by chunk 16.6 KiB (name: main)
asset output.js 16.2 KiB [emitted] (name: main)
asset 0.output.css 333 bytes [emitted] (name: main)
assets by chunk 16.8 KiB (name: main)
asset output.js 16.4 KiB [emitted] (name: main)
asset 0.output.css 385 bytes [emitted] (name: main)
asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main)
asset 1.output.css 49 bytes [emitted]
Entrypoint main 16.6 KiB (14.6 KiB) = output.js 16.2 KiB 0.output.css 333 bytes 1 auxiliary asset
chunk (runtime: main) output.js, 0.output.css (main) 218 bytes (javascript) 301 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 9.8 KiB (runtime) [entry] [rendered]
Entrypoint main 16.8 KiB (14.6 KiB) = output.js 16.4 KiB 0.output.css 385 bytes 1 auxiliary asset
chunk (runtime: main) output.js, 0.output.css (main) 218 bytes (javascript) 335 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 9.94 KiB (runtime) [entry] [rendered]
> ./example.js main
runtime modules 9.8 KiB 9 modules
dependent modules 42 bytes (javascript) 14.6 KiB (asset) 301 bytes (css) 42 bytes (css-import) [dependent] 6 modules
runtime modules 9.94 KiB 9 modules
dependent modules 42 bytes (javascript) 14.6 KiB (asset) 335 bytes (css) 42 bytes (css-import) [dependent] 6 modules
./example.js 176 bytes [built] [code generated]
[no exports]
[used exports unknown]
@ -476,21 +512,21 @@ webpack 5.64.4 compiled successfully
## Production mode
```
assets by chunk 4.1 KiB (name: main)
asset output.js 3.77 KiB [emitted] [minimized] (name: main)
asset 179.output.css 341 bytes [emitted] (name: main)
assets by chunk 4.2 KiB (name: main)
asset output.js 3.82 KiB [emitted] [minimized] (name: main)
asset 179.output.css 385 bytes [emitted] (name: main)
asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: images/file.png] (auxiliary name: main)
asset 159.output.css 53 bytes [emitted]
Entrypoint main 4.1 KiB (14.6 KiB) = output.js 3.77 KiB 179.output.css 341 bytes 1 auxiliary asset
Entrypoint main 4.2 KiB (14.6 KiB) = output.js 3.82 KiB 179.output.css 385 bytes 1 auxiliary asset
chunk (runtime: main) 159.output.css 23 bytes
> ./lazy-style.css ./example.js 4:0-26
./lazy-style.css 23 bytes [built] [code generated]
[no exports]
import() ./lazy-style.css ./example.js 4:0-26
chunk (runtime: main) output.js, 179.output.css (main) 218 bytes (javascript) 301 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 9.8 KiB (runtime) [entry] [rendered]
chunk (runtime: main) output.js, 179.output.css (main) 218 bytes (javascript) 335 bytes (css) 14.6 KiB (asset) 42 bytes (css-import) 9.95 KiB (runtime) [entry] [rendered]
> ./example.js main
runtime modules 9.8 KiB 9 modules
dependent modules 42 bytes (javascript) 14.6 KiB (asset) 301 bytes (css) 42 bytes (css-import) [dependent] 6 modules
runtime modules 9.95 KiB 9 modules
dependent modules 42 bytes (javascript) 14.6 KiB (asset) 335 bytes (css) 42 bytes (css-import) [dependent] 6 modules
./example.js 176 bytes [built] [code generated]
[no exports]
[no exports used]

View File

@ -1,4 +1,8 @@
:root {
--large: 72px;
}
.main {
font-size: large;
font-size: var(--large);
color: darkblue;
}

View File

@ -22,6 +22,12 @@ _{{dist/output.js}}_
_{{dist/0.output.css}}_
```
## production
```javascript
_{{production:dist/179.output.css}}_
```
# dist/1.output.css
```javascript

View File

@ -139,7 +139,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
)};`
: "// data-webpack is not used as build has no uniqueName",
`var loadCssChunkData = ${runtimeTemplate.basicFunction("chunkId, link", [
'var data, token = "", token2, exports = {}, exportsWithId = [], i = 0, cc = 1;',
'var data, token = "", token2, exports = {}, exportsWithId = [], exportsWithDashes = [], i = 0, cc = 1;',
"try { if(!link) link = loadStylesheet(chunkId); data = link.sheet.cssRules; data = data[data.length - 1].style; } catch(e) { data = getComputedStyle(document.head); }",
`data = data.getPropertyValue(${
uniqueName
@ -159,9 +159,11 @@ class CssLoadingRuntimeModule extends RuntimeModule {
`else if(cc == ${cc(
")"
)}) { exports[token2.replace(/^_/, "")] = token.replace(/^_/, ""); token = ""; }`,
`else if(cc == ${cc(
"/"
)}) { token = token.replace(/^_/, ""); exports[token] = token; exportsWithId.push(token); token = ""; }`,
`else if(cc == ${cc("/")} || cc == ${cc(
"%"
)}) { token = token.replace(/^_/, ""); exports[token] = token; exportsWithId.push(token); if(cc == ${cc(
"%"
)}) exportsWithDashes.push(token); token = ""; }`,
`else if(!cc || cc == ${cc(
","
)}) { token = token.replace(/^_/, ""); exportsWithId.forEach(${runtimeTemplate.expressionFunction(
@ -179,6 +181,9 @@ class CssLoadingRuntimeModule extends RuntimeModule {
})
}`,
"x"
)}); exportsWithDashes.forEach(${runtimeTemplate.expressionFunction(
`exports[x] = "--" + exports[x]`,
"x"
)}); ${RuntimeGlobals.makeNamespaceObject}(exports); ${
RuntimeGlobals.moduleFactories
}[token] = (${runtimeTemplate.basicFunction(

View File

@ -8,9 +8,11 @@
const { ConcatSource } = require("webpack-sources");
const HotUpdateChunk = require("../HotUpdateChunk");
const RuntimeGlobals = require("../RuntimeGlobals");
const SelfModuleFactory = require("../SelfModuleFactory");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const {
@ -62,7 +64,7 @@ const escapeCss = (str, omitOptionalUnderscore) => {
/[^a-zA-Z0-9_\u0081-\uffff-]/g,
s => `\\${s}`
);
return !omitOptionalUnderscore && /^[0-9_-]/.test(escaped)
return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)
? `_${escaped}`
: escaped;
};
@ -79,6 +81,7 @@ class CssModulesPlugin {
compiler.hooks.compilation.tap(
plugin,
(compilation, { normalModuleFactory }) => {
const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
compilation.dependencyFactories.set(
CssUrlDependency,
normalModuleFactory
@ -91,6 +94,14 @@ class CssModulesPlugin {
CssLocalIdentifierDependency,
new CssLocalIdentifierDependency.Template()
);
compilation.dependencyFactories.set(
CssSelfLocalIdentifierDependency,
selfFactory
);
compilation.dependencyTemplates.set(
CssSelfLocalIdentifierDependency,
new CssSelfLocalIdentifierDependency.Template()
);
compilation.dependencyTemplates.set(
CssExportDependency,
new CssExportDependency.Template()
@ -262,11 +273,16 @@ class CssModulesPlugin {
metaData.push(
`${
exports
? Array.from(exports, ([n, v]) =>
v === `${uniqueName ? uniqueName + "-" : ""}${moduleId}-${n}`
? Array.from(exports, ([n, v]) => {
const shortcutValue = `${
uniqueName ? uniqueName + "-" : ""
}${moduleId}-${n}`;
return v === shortcutValue
? `${escapeCss(n)}/`
: `${escapeCss(n)}(${escapeCss(v)})`
).join("")
: v === "--" + shortcutValue
? `${escapeCss(n)}%`
: `${escapeCss(n)}(${escapeCss(v)})`;
}).join("")
: ""
}${escapeCss(moduleId)}`
);

View File

@ -10,6 +10,7 @@ const ConstDependency = require("../dependencies/ConstDependency");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const walkCssTokens = require("./walkCssTokens");
@ -70,18 +71,21 @@ class LocConverter {
const CSS_MODE_TOP_LEVEL = 0;
const CSS_MODE_IN_RULE = 1;
const CSS_MODE_AT_IMPORT_EXPECT_URL = 2;
const CSS_MODE_IN_LOCAL_RULE = 2;
const CSS_MODE_AT_IMPORT_EXPECT_URL = 3;
// TODO implement layer and supports for @import
const CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS = 3;
const CSS_MODE_AT_IMPORT_EXPECT_MEDIA = 4;
const CSS_MODE_AT_OTHER = 5;
const CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS = 4;
const CSS_MODE_AT_IMPORT_EXPECT_MEDIA = 5;
const CSS_MODE_AT_OTHER = 6;
const explainMode = mode => {
switch (mode) {
case CSS_MODE_TOP_LEVEL:
return "parsing top level css";
case CSS_MODE_IN_RULE:
return "parsing css rule content";
return "parsing css rule content (global)";
case CSS_MODE_IN_LOCAL_RULE:
return "parsing css rule content (local)";
case CSS_MODE_AT_IMPORT_EXPECT_URL:
return "parsing @import (expecting url)";
case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS:
@ -124,12 +128,19 @@ class CssParser extends Parser {
const module = state.module;
const declaredCssVariables = new Set();
const locConverter = new LocConverter(source);
let mode = CSS_MODE_TOP_LEVEL;
let modePos = 0;
let modeNestingLevel = 0;
let modeData = undefined;
let singleClassSelector = undefined;
let lastIdentifier = undefined;
const modeStack = [];
const isTopLevelLocal = () =>
modeData === "local" ||
(this.defaultMode === "local" && modeData === undefined);
const eatWhiteLine = (input, pos) => {
for (;;) {
const cc = input.charCodeAt(pos);
@ -240,7 +251,57 @@ class CssParser extends Parser {
pos = eatWhiteLine(input, pos);
return pos;
};
const eatPropertyName = eatUntil(":{};");
const processLocalDeclaration = (input, pos) => {
modeData = undefined;
const start = pos;
pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
const propertyNameStart = pos;
const [propertyNameEnd, propertyName] = eatText(
input,
pos,
eatPropertyName
);
if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return start;
pos = propertyNameEnd + 1;
if (propertyName.startsWith("--")) {
// CSS Variable
const { line: sl, column: sc } = locConverter.get(propertyNameStart);
const { line: el, column: ec } = locConverter.get(propertyNameEnd);
const name = propertyName.slice(2);
const dep = new CssLocalIdentifierDependency(
name,
[propertyNameStart, propertyNameEnd],
"--"
);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
declaredCssVariables.add(name);
} else if (
propertyName === "animation-name" ||
propertyName === "animation"
) {
modeData = "animation";
lastIdentifier = undefined;
}
return pos;
};
const processDeclarationValueDone = (input, pos) => {
if (modeData === "animation" && lastIdentifier) {
const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]);
const { line: el, column: ec } = locConverter.get(lastIdentifier[1]);
const name = input.slice(lastIdentifier[0], lastIdentifier[1]);
const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
}
};
const eatKeyframes = eatUntil("{};/");
const eatNameInVar = eatUntil(",)};/");
walkCssTokens(source, {
isSelector: () => {
return mode !== CSS_MODE_IN_RULE && mode !== CSS_MODE_IN_LOCAL_RULE;
},
url: (input, start, end, contentStart, contentEnd) => {
const value = cssUnescape(input.slice(contentStart, contentEnd));
switch (mode) {
@ -298,6 +359,27 @@ class CssParser extends Parser {
supports: undefined
};
}
if (name === "@keyframes") {
let pos = end;
pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
if (pos === input.length) return pos;
const [newPos, name] = eatText(input, pos, eatKeyframes);
const { line: sl, column: sc } = locConverter.get(pos);
const { line: el, column: ec } = locConverter.get(newPos);
const dep = new CssLocalIdentifierDependency(name, [pos, newPos]);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
pos = newPos;
if (pos === input.length) return pos;
if (input.charCodeAt(pos) !== CC_LEFT_CURLY) {
throw new Error(
`Unexpected ${input[pos]} at ${pos} during parsing of @keyframes (expected '{')`
);
}
mode = CSS_MODE_IN_LOCAL_RULE;
modeNestingLevel = 1;
return pos + 1;
}
return end;
},
semicolon: (input, start, end) => {
@ -320,18 +402,31 @@ class CssParser extends Parser {
module.addDependency(dep);
break;
}
case CSS_MODE_IN_LOCAL_RULE: {
processDeclarationValueDone(input, start);
return processLocalDeclaration(input, end);
}
case CSS_MODE_IN_RULE: {
return end;
}
}
mode = CSS_MODE_TOP_LEVEL;
modeData = undefined;
singleClassSelector = undefined;
return end;
},
leftCurlyBracket: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL:
mode = CSS_MODE_IN_RULE;
mode = isTopLevelLocal()
? CSS_MODE_IN_LOCAL_RULE
: CSS_MODE_IN_RULE;
modeNestingLevel = 1;
if (mode === CSS_MODE_IN_LOCAL_RULE)
return processLocalDeclaration(input, end);
break;
case CSS_MODE_IN_RULE:
case CSS_MODE_IN_LOCAL_RULE:
modeNestingLevel++;
break;
}
@ -339,9 +434,44 @@ class CssParser extends Parser {
},
rightCurlyBracket: (input, start, end) => {
switch (mode) {
case CSS_MODE_IN_LOCAL_RULE:
processDeclarationValueDone(input, start);
/* falls through */
case CSS_MODE_IN_RULE:
if (--modeNestingLevel === 0) {
mode = CSS_MODE_TOP_LEVEL;
modeData = undefined;
singleClassSelector = undefined;
}
break;
}
return end;
},
id: (input, start, end) => {
singleClassSelector = false;
switch (mode) {
case CSS_MODE_TOP_LEVEL:
if (isTopLevelLocal()) {
const name = input.slice(start + 1, end);
const dep = new CssLocalIdentifierDependency(name, [
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;
},
identifier: (input, start, end) => {
singleClassSelector = false;
switch (mode) {
case CSS_MODE_IN_LOCAL_RULE:
if (modeData === "animation") {
lastIdentifier = [start, end];
}
break;
}
@ -350,18 +480,19 @@ class CssParser extends Parser {
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]
);
if (isTopLevelLocal()) {
const name = input.slice(start + 1, end);
const dep = new CssLocalIdentifierDependency(name, [
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);
if (singleClassSelector === undefined) singleClassSelector = name;
} else {
singleClassSelector = false;
}
break;
}
@ -392,6 +523,7 @@ class CssParser extends Parser {
return end;
},
pseudoClass: (input, start, end) => {
singleClassSelector = false;
switch (mode) {
case CSS_MODE_TOP_LEVEL: {
const name = input.slice(start, end);
@ -436,12 +568,41 @@ class CssParser extends Parser {
}
return end;
},
function: (input, start, end) => {
switch (mode) {
case CSS_MODE_IN_LOCAL_RULE: {
const name = input.slice(start, end - 1);
if (name === "var") {
let pos = walkCssTokens.eatWhitespaceAndComments(input, end);
if (pos === input.length) return pos;
const [newPos, name] = eatText(input, pos, eatNameInVar);
if (!name.startsWith("--")) return end;
const { line: sl, column: sc } = locConverter.get(pos);
const { line: el, column: ec } = locConverter.get(newPos);
const dep = new CssSelfLocalIdentifierDependency(
name.slice(2),
[pos, newPos],
"--",
declaredCssVariables
);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
return newPos;
}
break;
}
}
return end;
},
comma: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL:
modeData = undefined;
modeStack.length = 0;
break;
case CSS_MODE_IN_LOCAL_RULE:
processDeclarationValueDone(input, start);
break;
}
return end;
}

View File

@ -7,11 +7,13 @@
/**
* @typedef {Object} CssTokenCallbacks
* @property {function(string, number): boolean} isSelector
* @property {function(string, number, number, number, number): number=} url
* @property {function(string, number, number): number=} string
* @property {function(string, number, number): number=} leftParenthesis
* @property {function(string, number, number): number=} rightParenthesis
* @property {function(string, number, number): number=} pseudoFunction
* @property {function(string, number, number): number=} function
* @property {function(string, number, number): number=} pseudoClass
* @property {function(string, number, number): number=} atKeyword
* @property {function(string, number, number): number=} class
@ -190,7 +192,7 @@ const consumeNumberSign = (input, pos, callbacks) => {
const start = pos;
pos++;
if (pos === input.length) return pos;
if (_startsIdentifier(input, pos)) {
if (callbacks.isSelector(input, pos) && _startsIdentifier(input, pos)) {
pos = _consumeIdentifier(input, pos);
if (callbacks.id !== undefined) {
return callbacks.id(input, start, pos);
@ -244,7 +246,8 @@ const consumeDot = (input, pos, callbacks) => {
if (pos === input.length) return pos;
const cc = input.charCodeAt(pos);
if (_isDigit(cc)) return consumeNumericToken(input, pos - 2, callbacks);
if (!_startsIdentifier(input, pos)) return pos;
if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
return pos;
pos = _consumeIdentifier(input, pos);
if (callbacks.class !== undefined) return callbacks.class(input, start, pos);
return pos;
@ -264,10 +267,19 @@ const consumeNumericToken = (input, pos, callbacks) => {
const consumeOtherIdentifier = (input, pos, callbacks) => {
const start = pos;
pos = _consumeIdentifier(input, pos);
// we could check for CC_LEFT_PARENTHESIS here,
// but we don't need that info
if (callbacks.identifier !== undefined) {
return callbacks.identifier(input, start, pos);
if (
pos !== input.length &&
!callbacks.isSelector(input, pos) &&
input.charCodeAt(pos) === CC_LEFT_PARENTHESIS
) {
pos++;
if (callbacks.function !== undefined) {
return callbacks.function(input, start, pos);
}
} else {
if (callbacks.identifier !== undefined) {
return callbacks.identifier(input, start, pos);
}
}
return pos;
};
@ -349,7 +361,8 @@ const consumePotentialUrl = (input, pos, callbacks) => {
const consumePotentialPseudo = (input, pos, callbacks) => {
const start = pos;
pos++;
if (!_startsIdentifier(input, pos)) return pos;
if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
return pos;
pos = _consumeIdentifier(input, pos);
let cc = input.charCodeAt(pos);
if (cc === CC_LEFT_PARENTHESIS) {

View File

@ -18,11 +18,13 @@ class CssLocalIdentifierDependency extends NullDependency {
/**
* @param {string} name name
* @param {[number, number]} range range
* @param {string=} prefix prefix
*/
constructor(name, range) {
constructor(name, range, prefix = "") {
super();
this.name = name;
this.range = range;
this.prefix = prefix;
}
get type() {
@ -51,6 +53,7 @@ class CssLocalIdentifierDependency extends NullDependency {
const { write } = context;
write(this.name);
write(this.range);
write(this.prefix);
super.serialize(context);
}
@ -58,17 +61,20 @@ class CssLocalIdentifierDependency extends NullDependency {
const { read } = context;
this.name = read();
this.range = read();
this.prefix = read();
super.deserialize(context);
}
}
const escapeCssIdentifier = str => {
const escapeCssIdentifier = (str, omitUnderscore) => {
const escaped = `${str}`.replace(
// cspell:word uffff
/[^a-zA-Z0-9_\u0081-\uffff-]/g,
s => `\\${s}`
);
return /^[0-9-]/.test(escaped) ? `_${escaped}` : escaped;
return !omitUnderscore && /^(?!--)[0-9-]/.test(escaped)
? `_${escaped}`
: escaped;
};
CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTemplate extends (
@ -91,16 +97,15 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla
.getUsedName(dep.name, runtime);
const moduleId = chunkGraph.getModuleId(module);
const identifier =
dep.prefix +
(runtimeTemplate.outputOptions.uniqueName
? runtimeTemplate.outputOptions.uniqueName + "-"
: "") +
moduleId +
"-" +
(used || "");
(used ? moduleId + "-" + used : "-");
source.replace(
dep.range[0],
dep.range[1] - 1,
escapeCssIdentifier(identifier)
escapeCssIdentifier(identifier, dep.prefix)
);
if (used) cssExports.set(used, identifier);
}

View File

@ -0,0 +1,101 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const Dependency = require("../Dependency");
const makeSerializable = require("../util/makeSerializable");
const CssLocalIdentifierDependency = require("./CssLocalIdentifierDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
class CssSelfLocalIdentifierDependency extends CssLocalIdentifierDependency {
/**
* @param {string} name name
* @param {[number, number]} range range
* @param {string=} prefix prefix
* @param {Set<string>=} declaredSet set of declared names (will only be active when in declared set)
*/
constructor(name, range, prefix = "", declaredSet = undefined) {
super(name, range, prefix);
this.declaredSet = declaredSet;
}
get type() {
return "css self local identifier";
}
get category() {
return "self";
}
/**
* @returns {string | null} an identifier to merge equal requests
*/
getResourceIdentifier() {
return `self`;
}
/**
* Returns the exported names
* @param {ModuleGraph} moduleGraph module graph
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
if (this.declaredSet && !this.declaredSet.has(this.name)) return;
return super.getExports(moduleGraph);
}
/**
* Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph
* @param {RuntimeSpec} runtime the runtime for which the module is analysed
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
getReferencedExports(moduleGraph, runtime) {
if (this.declaredSet && !this.declaredSet.has(this.name))
return Dependency.NO_EXPORTS_REFERENCED;
return [[this.name]];
}
serialize(context) {
const { write } = context;
write(this.declaredSet);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this.declaredSet = read();
super.deserialize(context);
}
}
CssSelfLocalIdentifierDependency.Template = class CssSelfLocalIdentifierDependencyTemplate extends (
CssLocalIdentifierDependency.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, templateContext) {
const dep = /** @type {CssSelfLocalIdentifierDependency} */ (dependency);
if (dep.declaredSet && !dep.declaredSet.has(dep.name)) return;
super.apply(dependency, source, templateContext);
}
};
makeSerializable(
CssSelfLocalIdentifierDependency,
"webpack/lib/dependencies/CssSelfLocalIdentifierDependency"
);
module.exports = CssSelfLocalIdentifierDependency;

View File

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

View File

@ -17,7 +17,13 @@ it("should allow to create css modules", done => {
: "./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"
: "./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);

View File

@ -20,24 +20,47 @@
color: pink;
}
/* @keyframes localkeyframes {
#ident {
color: purple;
}
@keyframes localkeyframes {
0% {
left: var(--pos1x);
top: var(--pos1y);
color: var(--theme-color1);
}
100% {
left: var(--pos2x);
top: var(--pos2y);
color: var(--theme-color2);
}
}
@keyframes localkeyframes2 {
0% {
left: 0;
}
100% {
left: 100px;
}
}
.animation {
animation-name: localkeyframes;
animation: 3s ease-in 1s 2 reverse both paused localkeyframes, localkeyframes;
} */
animation: 3s ease-in 1s 2 reverse both paused localkeyframes, localkeyframes2;
--pos1x: 0px;
--pos1y: 0px;
--pos2x: 10px;
--pos2y: 20px;
}
/* .composed {
composes: local1;
composes: local2;
} */
/* .vars {
.vars {
color: var(--local-color);
--local-color: red;
}
@ -45,4 +68,4 @@
.globalVars :global {
color: var(--global-color);
--global-color: red;
} */
}

View File

@ -1,10 +1,14 @@
import * as style from "./style.module.css";
import { local1, local2, local3, local4 } from "./style.module.css";
import { local1, local2, local3, local4, ident } from "./style.module.css";
export default {
global: style.global,
class: style.class,
local: `${local1} ${local2} ${local3} ${local4}`,
local2: `${style.local5} ${style.local6}`,
nested: `${style.nested1} ${style.nested2} ${style.nested3}`
nested: `${style.nested1} ${style.nested2} ${style.nested3}`,
ident,
keyframes: style.localkeyframes,
animation: style.animation,
vars: `${style["local-color"]} ${style.vars} ${style["global-color"]} ${style.globalVars}`
};

View File

@ -1,6 +1,8 @@
module.exports = [
[/export 'global' \(imported as 'style'\) was not found/],
[/export 'nested2' \(imported as 'style'\) was not found/],
[/export 'global-color' \(imported as 'style'\) was not found/],
[/export 'global' \(imported as 'style'\) was not found/],
[/export 'nested2' \(imported as 'style'\) was not found/]
[/export 'nested2' \(imported as 'style'\) was not found/],
[/export 'global-color' \(imported as 'style'\) was not found/]
];

View File

@ -174,6 +174,9 @@ class FakeSheet {
);
});
walkCssTokens(css, {
isSelector() {
return selector === undefined;
},
leftCurlyBracket(source, start, end) {
if (selector === undefined) {
selector = source.slice(last, start).trim();

View File

@ -5,6 +5,7 @@ describe("walkCssTokens", () => {
it(`should ${name}`, () => {
const results = [];
walkCssTokens(content, {
isSelector: () => true,
url: (input, s, e, cs, ce) => {
results.push(["url", input.slice(s, e), input.slice(cs, ce)]);
return e;