Skip to content

Commit cb3febc

Browse files
committed
remove css prefixes #84
1 parent 4b256f8 commit cb3febc

File tree

26 files changed

+1229
-155
lines changed

26 files changed

+1229
-155
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
## v1.1.0
44

55
- [x] inline sourcemap
6-
- [x] validation using mdn-data
7-
- [ ] prefix removal
6+
- [x] CSS validation using mdn-data
7+
- [x] prefix removal now remove prefixes from all nodes. prefixed linear gradients are not supported
88

99
# v1.0.0
1010

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ $ deno add @tbela99/css-parser
3838
- inline css variables
3939
- remove duplicate properties
4040
- flatten @import rules
41+
- experimental CSS prefix removal
4142

4243
## Playground
4344

@@ -95,7 +96,7 @@ Javascript module from cdn
9596

9697
<script type="module">
9798
98-
import {transform} from 'https://esm.sh/@tbela99/css-parser@1.0.0/web';
99+
import {transform} from 'https://esm.sh/@tbela99/css-parser@1.1.0/web';
99100
100101
101102
const css = `
@@ -165,14 +166,18 @@ Include ParseOptions and RenderOptions
165166
in the :root {} or html {} rule.
166167
- removeEmpty: boolean, optional. remove empty rule lists from the ast.
167168

169+
> CSS Prefix Removal Options
170+
171+
- removePrefix: boolean, optional. remove CSS prefixes.
172+
168173
> Validation Options
169174
170175
- validation: ValidationLevel | boolean, optional. enable validation. permitted values are:
171-
- ValidationLevel.None: no validation
172-
- ValidationLevel.Default: validate selectors and at-rules (default)
173-
- ValidationLevel.All. validate everything
174-
- true: same as ValidationLevel.All.
175-
- false: same as ValidationLevel.None
176+
- ValidationLevel.None: no validation
177+
- ValidationLevel.Default: validate selectors and at-rules (default)
178+
- ValidationLevel.All. validate all nodes
179+
- true: same as ValidationLevel.All.
180+
- false: same as ValidationLevel.None
176181
- lenient: boolean, optional. preserve invalid tokens.
177182

178183
> Sourcemap Options

dist/index-umd-web.js

Lines changed: 229 additions & 29 deletions
Large diffs are not rendered by default.

dist/index.cjs

Lines changed: 229 additions & 29 deletions
Large diffs are not rendered by default.

dist/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ export declare interface WalkAttributesResult {
11281128
previousValue: Token | null;
11291129
nextValue: Token | null;
11301130
root?: AstNode;
1131-
parent: FunctionToken | ParensToken | BinaryExpressionToken | null;
1131+
parent: AstNode | Token | null;
11321132
list: Token[] | null;
11331133
}
11341134

dist/lib/ast/features/inlinecssvariables.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function inlineExpression(token) {
2424
function replace(node, variableScope) {
2525
for (const { value, parent: parentValue } of walkValues(node.val)) {
2626
if (value.typ == EnumToken.BinaryExpressionTokenType && parentValue != null && 'chi' in parentValue) {
27+
// @ts-ignore
2728
parentValue.chi.splice(parentValue.chi.indexOf(value), 1, ...inlineExpression(value));
2829
}
2930
}

dist/lib/ast/features/prefix.js

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,58 @@
1-
import { EnumToken } from '../types.js';
1+
import { EnumToken, SyntaxValidationResult } from '../types.js';
22
import { getSyntaxConfig } from '../../validation/config.js';
33
import '../../validation/parser/types.js';
44
import '../../validation/parser/parse.js';
5-
import { splitRule } from '../minify.js';
5+
import { splitRule, definedPropertySettings } from '../minify.js';
66
import { walkValues } from '../walk.js';
7-
import '../../parser/parse.js';
7+
import { parseAtRulePrelude, parseString } from '../../parser/parse.js';
88
import '../../parser/tokenize.js';
99
import '../../parser/utils/config.js';
10-
import { webkitPseudoAliasMap } from '../../syntax/syntax.js';
11-
import '../../renderer/color/utils/constants.js';
12-
import '../../renderer/sourcemap/lib/encode.js';
10+
import { pseudoAliasMap } from '../../syntax/syntax.js';
11+
import { renderToken } from '../../renderer/render.js';
12+
import { funcLike } from '../../renderer/color/utils/constants.js';
1313
import '../../validation/syntaxes/complex-selector.js';
14-
import '../../validation/syntax.js';
14+
import { evaluateSyntax } from '../../validation/syntax.js';
1515

1616
const config = getSyntaxConfig();
1717
function replacePseudo(tokens) {
1818
return tokens.map((raw) => raw.map(r => {
19-
if (r.startsWith(':-')) {
20-
const i = r.indexOf('(');
21-
let key = i != -1 ? r.slice(1, i) + '()' : r.slice(1);
22-
if (key in webkitPseudoAliasMap) {
23-
return ':' + webkitPseudoAliasMap[key] + (i == -1 ? '' : r.slice(i));
19+
if (r.includes('(')) {
20+
const index = r.indexOf('(');
21+
const name = r.slice(0, index) + '()';
22+
if (name in pseudoAliasMap) {
23+
return pseudoAliasMap[name] + r.slice(index);
2424
}
25+
return r;
2526
}
26-
return r;
27+
return r in pseudoAliasMap && pseudoAliasMap[r] in config["selectors" /* ValidationSyntaxGroupEnum.Selectors */] ? pseudoAliasMap[r] : r;
2728
}));
2829
}
29-
function replaceAstNodes(tokens) {
30-
for (const { value } of walkValues(tokens)) {
31-
if (value.typ == EnumToken.PseudoClassFuncTokenType || value.typ == EnumToken.PseudoClassTokenType) {
32-
if (value.val.startsWith(':-')) {
33-
let key = value.val.slice(1) + (value.typ == EnumToken.PseudoClassFuncTokenType ? '()' : '');
34-
if (key in webkitPseudoAliasMap) {
35-
value.val = ':' + webkitPseudoAliasMap[key];
30+
function replaceAstNodes(tokens, root) {
31+
let result = false;
32+
for (const { value, parent } of walkValues(tokens, root)) {
33+
if (value.typ == EnumToken.IdenTokenType || value.typ == EnumToken.PseudoClassFuncTokenType || value.typ == EnumToken.PseudoClassTokenType || value.typ == EnumToken.PseudoElementTokenType) {
34+
let key = value.val + (value.typ == EnumToken.PseudoClassFuncTokenType ? '()' : '');
35+
if (key in pseudoAliasMap) {
36+
const isPseudClass = pseudoAliasMap[key].startsWith('::');
37+
value.val = pseudoAliasMap[key];
38+
if (value.typ == EnumToken.IdenTokenType &&
39+
['min-resolution', 'max-resolution'].includes(value.val) &&
40+
parent?.typ == EnumToken.MediaQueryConditionTokenType &&
41+
parent.r?.[0]?.typ == EnumToken.NumberTokenType) {
42+
Object.assign(parent.r?.[0], {
43+
typ: EnumToken.ResolutionTokenType,
44+
unit: 'x',
45+
});
3646
}
47+
else if (isPseudClass && value.typ == EnumToken.PseudoElementTokenType) {
48+
// @ts-ignore
49+
value.typ = EnumToken.PseudoClassTokenType;
50+
}
51+
result = true;
3752
}
3853
}
3954
}
55+
return result;
4056
}
4157
class ComputePrefixFeature {
4258
get ordering() {
@@ -71,13 +87,63 @@ class ComputePrefixFeature {
7187
if (node.nam.charAt(0) == '-') {
7288
const match = node.nam.match(/^-([^-]+)-(.+)$/);
7389
if (match != null) {
74-
const nam = match[2];
75-
if (nam.toLowerCase() in config.declarations) {
90+
let nam = match[2];
91+
if (!(nam in config.declarations)) {
92+
if (node.nam in pseudoAliasMap) {
93+
nam = pseudoAliasMap[node.nam];
94+
}
95+
}
96+
if (nam in config.declarations) {
7697
node.nam = nam;
77-
replaceAstNodes(node.val);
7898
}
7999
}
80100
}
101+
let hasPrefix = false;
102+
for (const { value } of walkValues(node.val)) {
103+
if ((value.typ == EnumToken.IdenTokenType || funcLike.includes(value.typ)) && value.val.match(/^-([^-]+)-(.+)$/) != null) {
104+
if (value.val.endsWith('-gradient')) {
105+
// not supported yet
106+
break;
107+
}
108+
hasPrefix = true;
109+
break;
110+
}
111+
}
112+
if (hasPrefix) {
113+
const nodes = structuredClone(node.val);
114+
for (const { value } of walkValues(nodes)) {
115+
if ((value.typ == EnumToken.IdenTokenType || funcLike.includes(value.typ))) {
116+
const match = value.val.match(/^-([^-]+)-(.+)$/);
117+
if (match == null) {
118+
continue;
119+
}
120+
value.val = match[2];
121+
}
122+
}
123+
if (SyntaxValidationResult.Valid == evaluateSyntax({ ...node, val: nodes }, {}).valid) {
124+
node.val = nodes;
125+
}
126+
}
127+
}
128+
else if (node.typ == EnumToken.AtRuleNodeType || node.typ == EnumToken.KeyframeAtRuleNodeType) {
129+
if (node.nam.startsWith('-')) {
130+
const match = node.nam.match(/^-([^-]+)-(.+)$/);
131+
if (match != null && '@' + match[2] in config.atRules) {
132+
node.nam = match[2];
133+
}
134+
}
135+
if (node.typ == EnumToken.AtRuleNodeType && node.val !== '') {
136+
if (node.tokens == null) {
137+
Object.defineProperty(node, 'tokens', {
138+
// @ts-ignore
139+
...definedPropertySettings,
140+
value: parseAtRulePrelude(parseString(node.val), node),
141+
});
142+
}
143+
if (replaceAstNodes(node.tokens)) {
144+
node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr), '');
145+
}
146+
}
81147
}
82148
return node;
83149
}

dist/lib/ast/walk.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { EnumToken } from './types.js';
2-
31
var WalkerOptionEnum;
42
(function (WalkerOptionEnum) {
53
WalkerOptionEnum[WalkerOptionEnum["Ignore"] = 0] = "Ignore";
@@ -113,10 +111,38 @@ function* walkValues(values, root = null, filter, reverse) {
113111
stack.unshift(...sliced);
114112
}
115113
}
116-
else if (value.typ == EnumToken.BinaryExpressionTokenType) {
117-
map.set(value.l, value);
118-
map.set(value.r, value);
119-
stack.unshift(value.l, value.r);
114+
else {
115+
const values = [];
116+
if ('l' in value && value.l != null) {
117+
// @ts-ignore
118+
values.push(value.l);
119+
// @ts-ignore
120+
map.set(value.l, value);
121+
}
122+
if ('op' in value && typeof value.op == 'object') {
123+
values.push(value.op);
124+
// @ts-ignore
125+
map.set(value.op, value);
126+
}
127+
if ('r' in value && value.r != null) {
128+
if (Array.isArray(value.r)) {
129+
for (const r of value.r) {
130+
// @ts-ignore
131+
values.push(r);
132+
// @ts-ignore
133+
map.set(r, value);
134+
}
135+
}
136+
else {
137+
// @ts-ignore
138+
values.push(value.r);
139+
// @ts-ignore
140+
map.set(value.r, value);
141+
}
142+
}
143+
if (values.length > 0) {
144+
stack.unshift(...values);
145+
}
120146
}
121147
if (eventType == WalkerValueEvent.Leave && filter.fn != null) {
122148
const isValid = filter.type == null || value.typ == filter.type ||

dist/lib/parser/parse.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { isIdentStart, isIdent, isIdentColor, mathFuncs, isColor, parseColor, isHexColor, isPseudo, pseudoElements, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isHash, mediaTypes } from '../syntax/syntax.js';
1+
import { isColor, parseColor, isIdent, mediaTypes, isDimension, parseDimension, isPseudo, pseudoElements, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isHexColor, isHash, isIdentStart, isIdentColor, mathFuncs } from '../syntax/syntax.js';
22
import './utils/config.js';
33
import { EnumToken, ValidationLevel, SyntaxValidationResult } from '../ast/types.js';
44
import { minify, definedPropertySettings, combinators } from '../ast/minify.js';
55
import { walkValues, walk, WalkerOptionEnum } from '../ast/walk.js';
66
import { expand } from '../ast/expand.js';
77
import { parseDeclarationNode } from './utils/declaration.js';
88
import { renderToken } from '../renderer/render.js';
9-
import { funcLike, COLORS_NAMES, ColorKind, systemColors, deprecatedSystemColors, colorsFunc } from '../renderer/color/utils/constants.js';
9+
import { funcLike, ColorKind, COLORS_NAMES, systemColors, deprecatedSystemColors, colorsFunc } from '../renderer/color/utils/constants.js';
1010
import { buildExpression } from '../ast/math/expression.js';
1111
import { tokenize } from './tokenize.js';
1212
import '../validation/config.js';
@@ -916,6 +916,9 @@ function parseAtRulePrelude(tokens, atRule) {
916916
continue;
917917
}
918918
}
919+
if (value.typ == EnumToken.FunctionTokenType && value.val == 'selector') {
920+
parseSelector(value.chi);
921+
}
919922
if (value.typ == EnumToken.ParensTokenType || (value.typ == EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) {
920923
let i;
921924
let nameIndex = -1;
@@ -1645,4 +1648,4 @@ function parseTokens(tokens, options = {}) {
16451648
return tokens;
16461649
}
16471650

1648-
export { doParse, parseSelector, parseString, parseTokens, urlTokenMatcher };
1651+
export { doParse, parseAtRulePrelude, parseSelector, parseString, parseTokens, urlTokenMatcher };

dist/lib/renderer/render.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
624624
}
625625
return val + 's';
626626
}
627+
if (token.typ == EnumToken.ResolutionTokenType && unit == 'dppx') {
628+
unit = 'x';
629+
}
627630
return val.includes('/') ? val.replace('/', unit + '/') : val + unit;
628631
case EnumToken.FlexTokenType:
629632
case EnumToken.PercentageTokenType:

0 commit comments

Comments
 (0)