forked from parshap/css-eliminator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
104 lines (89 loc) · 2.54 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// jshint node:true
"use strict";
var htmlparser = require("htmlparser2"),
select = require("CSSselect").selectOne;
module.exports = function(html, options) {
// Create an eliminate function that will alter an AST rule node to
// eliminate dead code
var eliminate = eliminator(html, options);
return function(style) {
walkRules(style.stylesheet, eliminate);
return style;
};
};
// Create a function that will eliminate dead code from rules
function eliminator(html, options) {
var dom = parseDOM(html);
return function(rule) {
// Remove any selectors that don't appear in the document
rule.selectors = rule.selectors.filter(function(selector) {
return filter(selector) || isInDocument(selector);
});
// Remove declarations if there are no selectors
if (rule.selectors.length === 0) {
rule.declarations = [];
}
};
function filter(selector) {
return options && options.filter && options.filter(selector);
}
function isInDocument(selector) {
selector = stripPseudos(selector);
try {
// Return true if this selector matched
return select(selector, dom);
}
catch (e) {
// If the selector was not valid or there was another error
// assume the selector is not dead
}
}
}
// Sync htmlparser parser
function parseDOM(html) {
// Since we already have html string and are going to call parser.done()
// in a sync manner, we can just turn the parsing into a sync call.
var dom, err;
var parser = new htmlparser.Parser(
new htmlparser.DomHandler(function(err2, dom2) {
// This function will be called on
err = err2;
dom = dom2;
})
);
parser.write(html);
parser.done();
if (err) throw err;
return dom;
}
// Remove any pseudo classes or elements from the selector
// @TODO We may want to keep some pseudo classes (e.g., :nth-child())
var stripPseudos = (function() {
var parseCSS = require("slick").parse,
forEach = Array.prototype.forEach;
return function(selector) {
var expressions = parseCSS(selector);
forEach.call(expressions, function(expression) {
forEach.call(expression, function(part) {
part.pseudos = null;
});
});
return expressions.toString();
};
})();
// ## Walk Rules
// Walk all rule nodes in the given css ast, calling a function for each rule
// node
var walk = require("rework-walk");
function walkRules(node, fn) {
walk(node, function(node) {
if (isRealRule(node)) {
fn(node);
}
});
}
// Make sure the node is a rule and not a media query
function isRealRule(node) {
return node.type === "rule" &&
! (node.selectors.length === 1 && node.selectors[0][0] === "@");
}