Skip to content

Commit b1e1805

Browse files
committed
select to apply minify features based on the node type #110
1 parent d3abeaa commit b1e1805

File tree

205 files changed

+10581
-9430
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

205 files changed

+10581
-9430
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v1.3.4
4+
5+
- [x] make streaming optional #109
6+
- [x] patch at-rule syntax for @font-feature-value #110
7+
38
## v1.3.3
49

510
- [x] relative color computation bug #105

dist/index-umd-web.js

Lines changed: 98 additions & 89 deletions
Large diffs are not rendered by default.

dist/index.cjs

Lines changed: 98 additions & 89 deletions
Large diffs are not rendered by default.

dist/index.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3327,6 +3327,11 @@ export declare interface MinifyFeatureOptions {
33273327
*/
33283328
export declare interface MinifyFeature {
33293329

3330+
/**
3331+
* accepted tokens
3332+
*/
3333+
accept?: Set<EnumToken$1>;
3334+
33303335
/**
33313336
* ordering
33323337
*/
@@ -3488,7 +3493,17 @@ export declare interface ParseResultStats {
34883493
/**
34893494
* imported files stats
34903495
*/
3491-
imports: ParseResultStats[]
3496+
imports: ParseResultStats[],
3497+
3498+
/**
3499+
* nodes count
3500+
*/
3501+
nodesCount: number;
3502+
3503+
/**
3504+
* tokens count
3505+
*/
3506+
tokensCount: number;
34923507
}
34933508

34943509
/**

dist/lib/ast/features/calc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../../parser/utils/config.js';
1111
import { FeatureWalkMode } from './type.js';
1212

1313
class ComputeCalcExpressionFeature {
14+
accept = new Set([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType]);
1415
get ordering() {
1516
return 1;
1617
}

dist/lib/ast/features/inlinecssvariables.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function replace(node, variableScope) {
5252
}
5353
}
5454
class InlineCssVariablesFeature {
55+
accept = new Set([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType]);
5556
get ordering() {
5657
return 0;
5758
}

dist/lib/ast/features/shorthand.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../renderer/sourcemap/lib/encode.js';
1010
import { FeatureWalkMode } from './type.js';
1111

1212
class ComputeShorthandFeature {
13+
accept = new Set([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType, EnumToken.KeyFramesRuleNodeType]);
1314
get ordering() {
1415
return 3;
1516
}

dist/lib/ast/features/transform.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { eqMatrix } from '../transform/minify.js';
1313
import { FeatureWalkMode } from './type.js';
1414

1515
class TransformCssFeature {
16+
accept = new Set([EnumToken.RuleNodeType, EnumToken.AtRuleNodeType]);
1617
get ordering() {
1718
return 4;
1819
}

dist/lib/ast/minify.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co
6161
}
6262
replacement = parent;
6363
for (const feature of options.features) {
64-
if ((feature.processMode & FeatureWalkMode.Pre) === 0) {
64+
if ((feature.processMode & FeatureWalkMode.Pre) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) {
6565
continue;
6666
}
6767
const result = feature.run(replacement, options, parent.parent ?? ast, context, FeatureWalkMode.Pre);
@@ -100,7 +100,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co
100100
replacement = parent;
101101
if (postprocess) {
102102
for (const feature of options.features) {
103-
if ((feature.processMode & FeatureWalkMode.Post) === 0) {
103+
if ((feature.processMode & FeatureWalkMode.Post) === 0 || (feature.accept != null && !feature.accept.has(parent.typ))) {
104104
continue;
105105
}
106106
const result = feature.run(replacement, options, parent.parent ?? ast, context, FeatureWalkMode.Post);

dist/lib/parser/parse.js

Lines changed: 92 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,6 @@ const enumTokenHints = new Set([
3939
function reject(reason) {
4040
throw new Error(reason ?? 'Parsing aborted');
4141
}
42-
function removeNode(node, parent) {
43-
// @ts-ignore
44-
const index = parent.chi.indexOf(node);
45-
if (index != -1) {
46-
parent.chi.splice(index, 1);
47-
node.parent = null;
48-
}
49-
}
5042
function normalizeVisitorKeyName(keyName) {
5143
return keyName.replace(/-([a-z])/g, (all, one) => one.toUpperCase());
5244
}
@@ -126,6 +118,8 @@ async function doParse(iter, options = {}) {
126118
const stats = {
127119
src: options.src ?? '',
128120
bytesIn: 0,
121+
nodesCount: 0,
122+
tokensCount: 0,
129123
importedBytesIn: 0,
130124
parse: `0ms`,
131125
minify: `0ms`,
@@ -154,14 +148,75 @@ async function doParse(iter, options = {}) {
154148
src: ''
155149
};
156150
}
157-
let item;
158-
let node;
151+
const valuesHandlers = new Map;
152+
const preValuesHandlers = new Map;
153+
const postValuesHandlers = new Map;
154+
const preVisitorsHandlersMap = new Map;
155+
const visitorsHandlersMap = new Map;
156+
const postVisitorsHandlersMap = new Map;
157+
const allValuesHandlers = [];
159158
const rawTokens = [];
160159
const imports = [];
160+
let item;
161+
let node;
161162
// @ts-ignore ignore error
162163
let isAsync = typeof iter[Symbol.asyncIterator] === 'function';
164+
if (options.visitor != null) {
165+
for (const [key, value] of Object.entries(options.visitor)) {
166+
if (key in EnumToken) {
167+
if (typeof value == 'function') {
168+
valuesHandlers.set(EnumToken[key], value);
169+
}
170+
else if (typeof value == 'object' && 'type' in value && 'handler' in value && value.type in WalkerValueEvent) {
171+
if (WalkerValueEvent[value.type] == WalkerValueEvent.Enter) {
172+
preValuesHandlers.set(EnumToken[key], value.handler);
173+
}
174+
else if (WalkerValueEvent[value.type] == WalkerValueEvent.Leave) {
175+
postValuesHandlers.set(EnumToken[key], value.handler);
176+
}
177+
}
178+
else {
179+
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
180+
}
181+
}
182+
else if (['Declaration', 'Rule', 'AtRule', 'KeyframesRule', 'KeyframesAtRule'].includes(key)) {
183+
if (typeof value == 'function') {
184+
visitorsHandlersMap.set(key, value);
185+
}
186+
else if (typeof value == 'object') {
187+
if ('type' in value && 'handler' in value && value.type in WalkerValueEvent) {
188+
if (WalkerValueEvent[value.type] == WalkerValueEvent.Enter) {
189+
preVisitorsHandlersMap.set(key, value.handler);
190+
}
191+
else if (WalkerValueEvent[value.type] == WalkerValueEvent.Leave) {
192+
postVisitorsHandlersMap.set(key, value.handler);
193+
}
194+
}
195+
else {
196+
visitorsHandlersMap.set(key, value);
197+
}
198+
}
199+
else {
200+
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
201+
}
202+
}
203+
else {
204+
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
205+
}
206+
}
207+
if (preValuesHandlers.size > 0) {
208+
allValuesHandlers.push(preValuesHandlers);
209+
}
210+
if (valuesHandlers.size > 0) {
211+
allValuesHandlers.push(valuesHandlers);
212+
}
213+
if (postValuesHandlers.size > 0) {
214+
allValuesHandlers.push(postValuesHandlers);
215+
}
216+
}
163217
while (item = isAsync ? (await iter.next()).value : iter.next().value) {
164218
stats.bytesIn = item.bytesIn;
219+
stats.tokensCount++;
165220
rawTokens.push(item);
166221
if (item.hint != null && BadTokensTypes.includes(item.hint)) {
167222
const node = getTokenType(item.token, item.hint);
@@ -189,7 +244,7 @@ async function doParse(iter, options = {}) {
189244
ast.loc.end = item.end;
190245
}
191246
if (item.token == ';' || item.token == '{') {
192-
node = parseNode(tokens, context, options, errors, src, map, rawTokens);
247+
node = parseNode(tokens, context, options, errors, src, map, rawTokens, stats);
193248
rawTokens.length = 0;
194249
if (node != null) {
195250
if ('chi' in node) {
@@ -233,7 +288,7 @@ async function doParse(iter, options = {}) {
233288
map = new Map;
234289
}
235290
else if (item.token == '}') {
236-
parseNode(tokens, context, options, errors, src, map, rawTokens);
291+
parseNode(tokens, context, options, errors, src, map, rawTokens, stats);
237292
rawTokens.length = 0;
238293
if (context.loc != null) {
239294
context.loc.end = item.end;
@@ -254,7 +309,7 @@ async function doParse(iter, options = {}) {
254309
}
255310
}
256311
if (tokens.length > 0) {
257-
node = parseNode(tokens, context, options, errors, src, map, rawTokens);
312+
node = parseNode(tokens, context, options, errors, src, map, rawTokens, stats);
258313
rawTokens.length = 0;
259314
if (node != null) {
260315
if (node.typ == EnumToken.AtRuleNodeType && node.nam == 'import') {
@@ -317,78 +372,7 @@ async function doParse(iter, options = {}) {
317372
if (options.expandNestingRules) {
318373
ast = expand(ast);
319374
}
320-
const valuesHandlers = new Map;
321-
const preValuesHandlers = new Map;
322-
const postValuesHandlers = new Map;
323-
const preVisitorsHandlersMap = new Map;
324-
const visitorsHandlersMap = new Map;
325-
const postVisitorsHandlersMap = new Map;
326-
const allValuesHandlers = [];
327-
if (options.visitor != null) {
328-
for (const [key, value] of Object.entries(options.visitor)) {
329-
if (key in EnumToken) {
330-
if (typeof value == 'function') {
331-
valuesHandlers.set(EnumToken[key], value);
332-
}
333-
else if (typeof value == 'object' && 'type' in value && 'handler' in value && value.type in WalkerValueEvent) {
334-
if (WalkerValueEvent[value.type] == WalkerValueEvent.Enter) {
335-
preValuesHandlers.set(EnumToken[key], value.handler);
336-
}
337-
else if (WalkerValueEvent[value.type] == WalkerValueEvent.Leave) {
338-
postValuesHandlers.set(EnumToken[key], value.handler);
339-
}
340-
}
341-
else {
342-
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
343-
}
344-
}
345-
else if (['Declaration', 'Rule', 'AtRule', 'KeyframesRule', 'KeyframesAtRule'].includes(key)) {
346-
if (typeof value == 'function') {
347-
visitorsHandlersMap.set(key, value);
348-
}
349-
else if (typeof value == 'object') {
350-
if ('type' in value && 'handler' in value && value.type in WalkerValueEvent) {
351-
if (WalkerValueEvent[value.type] == WalkerValueEvent.Enter) {
352-
preVisitorsHandlersMap.set(key, value.handler);
353-
}
354-
else if (WalkerValueEvent[value.type] == WalkerValueEvent.Leave) {
355-
postVisitorsHandlersMap.set(key, value.handler);
356-
}
357-
}
358-
else {
359-
visitorsHandlersMap.set(key, value);
360-
}
361-
}
362-
else {
363-
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
364-
}
365-
}
366-
else {
367-
errors.push({ action: 'ignore', message: `doParse: visitor.${key} is not a valid key name` });
368-
}
369-
}
370-
if (preValuesHandlers.size > 0) {
371-
allValuesHandlers.push(preValuesHandlers);
372-
}
373-
if (valuesHandlers.size > 0) {
374-
allValuesHandlers.push(valuesHandlers);
375-
}
376-
if (postValuesHandlers.size > 0) {
377-
allValuesHandlers.push(postValuesHandlers);
378-
}
379-
}
380375
for (const result of walk(ast)) {
381-
if (result.parent != null && !isNodeAllowedInContext(result.node, result.parent)) {
382-
errors.push({
383-
action: 'drop',
384-
message: `${EnumToken[result.parent.typ]}: child ${EnumToken[result.node.typ]}${result.node.typ == EnumToken.DeclarationNodeType ? ` '${result.node.nam}'` : result.node.typ == EnumToken.AtRuleNodeType || result.node.typ == EnumToken.KeyframesAtRuleNodeType ? ` '@${result.node.nam}'` : ''} not allowed in context${result.parent.typ == EnumToken.AtRuleNodeType ? ` '@${result.parent.nam}'` : result.parent.typ == EnumToken.StyleSheetNodeType ? ` 'stylesheet'` : ''}`,
385-
// @ts-ignore
386-
location: result.node.loc ?? map.get(result.node) ?? null
387-
});
388-
// @ts-ignore
389-
removeNode(result.node, result.parent);
390-
continue;
391-
}
392376
if (allValuesHandlers.length > 0 || preVisitorsHandlersMap.size > 0 || visitorsHandlersMap.size > 0 || postVisitorsHandlersMap.size > 0) {
393377
if ((result.node.typ == EnumToken.DeclarationNodeType &&
394378
(preVisitorsHandlersMap.has('Declaration') || visitorsHandlersMap.has('Declaration') || postVisitorsHandlersMap.has('Declaration'))) ||
@@ -573,7 +557,7 @@ function getLastNode(context) {
573557
}
574558
return null;
575559
}
576-
function parseNode(results, context, options, errors, src, map, rawTokens) {
560+
function parseNode(results, context, options, errors, src, map, rawTokens, stats) {
577561
let tokens = [];
578562
for (const t of results) {
579563
const node = getTokenType(t.token, t.hint);
@@ -596,6 +580,7 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
596580
}
597581
loc = location;
598582
context.chi.push(tokens[i]);
583+
stats.nodesCount++;
599584
if (options.sourcemap) {
600585
tokens[i].loc = loc;
601586
}
@@ -774,13 +759,18 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
774759
isValid = false;
775760
}
776761
}
762+
const isAllowed = isNodeAllowedInContext(node, context);
777763
// @ts-ignore
778764
const valid = options.validation == ValidationLevel.None ? {
779765
valid: SyntaxValidationResult.Valid,
780766
error: '',
781767
node,
782768
syntax: '@' + node.nam
783-
} : isValid ? (node.typ == EnumToken.KeyframesAtRuleNodeType ? validateAtRuleKeyframes(node) : validateAtRule(node, options, context)) : {
769+
} : !isAllowed ? {
770+
valid: SyntaxValidationResult.Drop,
771+
node,
772+
syntax: '@' + node.nam,
773+
error: `${EnumToken[context.typ]}: child ${EnumToken[node.typ]} not allowed in context${context.typ == EnumToken.AtRuleNodeType ? ` '@${context.nam}'` : context.typ == EnumToken.StyleSheetNodeType ? ` 'stylesheet'` : ''}`} : isValid ? (node.typ == EnumToken.KeyframesAtRuleNodeType ? validateAtRuleKeyframes(node) : validateAtRule(node, options, context)) : {
784774
valid: SyntaxValidationResult.Drop,
785775
node,
786776
syntax: '@' + node.nam,
@@ -804,6 +794,7 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
804794
}), '');
805795
}
806796
context.chi.push(node);
797+
stats.nodesCount++;
807798
Object.defineProperties(node, {
808799
parent: { ...definedPropertySettings, value: context },
809800
validSyntax: { ...definedPropertySettings, value: valid.valid == SyntaxValidationResult.Valid }
@@ -907,10 +898,14 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
907898
// @ts-ignore
908899
context.chi.push(node);
909900
Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
901+
const isAllowed = isNodeAllowedInContext(node, context);
910902
// @ts-ignore
911903
const valid = options.validation == ValidationLevel.None ? {
912904
valid: SyntaxValidationResult.Valid,
913905
error: null
906+
} : !isAllowed ? {
907+
valid: SyntaxValidationResult.Drop,
908+
error: `${EnumToken[context.typ]}: child ${EnumToken[node.typ]} not allowed in context${context.typ == EnumToken.AtRuleNodeType ? ` '@${context.nam}'` : context.typ == EnumToken.StyleSheetNodeType ? ` 'stylesheet'` : ''}`
914909
} : ruleType == EnumToken.KeyFramesRuleNodeType ? validateKeyframeSelector(tokens) : validateSelector(tokens, options, context);
915910
if (valid.valid != SyntaxValidationResult.Valid) {
916911
// @ts-ignore
@@ -1025,6 +1020,7 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
10251020
node.loc.end = { ...map.get(delim).end };
10261021
}
10271022
context.chi.push(node);
1023+
stats.nodesCount++;
10281024
}
10291025
return null;
10301026
}
@@ -1056,13 +1052,21 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
10561052
if (context.typ == EnumToken.StyleSheetNodeType && options.lenient) {
10571053
Object.assign(node, { typ: EnumToken.InvalidDeclarationNodeType });
10581054
context.chi.push(node);
1055+
stats.nodesCount++;
10591056
return null;
10601057
}
10611058
const result = parseDeclarationNode(node, errors, location);
10621059
Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context });
10631060
if (result != null) {
10641061
if (options.validation == ValidationLevel.All) {
1065-
const valid = evaluateSyntax(result, context, options);
1062+
const isAllowed = isNodeAllowedInContext(node, context);
1063+
// @ts-ignore
1064+
const valid = !isAllowed ? {
1065+
valid: SyntaxValidationResult.Drop,
1066+
error: `${EnumToken[node.typ]} not allowed in context${context.typ == EnumToken.AtRuleNodeType ? ` '@${context.nam}'` : context.typ == EnumToken.StyleSheetNodeType ? ` 'stylesheet'` : ''}`,
1067+
node,
1068+
syntax: null
1069+
} : evaluateSyntax(result, context, options);
10661070
Object.defineProperty(result, 'validSyntax', {
10671071
...definedPropertySettings,
10681072
value: valid.valid == SyntaxValidationResult.Valid
@@ -1082,6 +1086,7 @@ function parseNode(results, context, options, errors, src, map, rawTokens) {
10821086
}
10831087
}
10841088
context.chi.push(result);
1089+
stats.nodesCount++;
10851090
}
10861091
return null;
10871092
}

0 commit comments

Comments
 (0)