-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
171 lines (145 loc) · 4.16 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
const parseStyle = require('postcss-safe-parser'),
entities = require('entities');
const property = p => o => o[p];
const isWhitespace = s => /^\s*$/.test(s);
// Rules that are safe to strip from nodes with no child text or only
// whitespace
const contentProps = new Set([
'color'
, 'font-family'
, 'text-align'
, 'font-weight'
, 'vertical-align'
, 'word-wrap'
, '-webkit-hyphens'
, '-moz-hyphens'
, 'hyphens'
]);
const noContentProps = new Set([
'font-size', 'line-height'
]);
// Rules that are safe to strip from nodes where these rules are
// overriden before they would be applied to any non-whitespace text.
const contentPropsSafe = new Set([
'color'
, 'font-family'
, 'font-weight'
, 'vertical-align'
, 'word-wrap'
, '-webkit-hyphens'
, '-moz-hyphens'
, 'hyphens'
]);
// Like contentPropsSafe except will only be removed if never gets
// applied to any text, whitespace or not
const contentPropsNoTextSafe = new Set([
'text-decoration',
'font-size'
]);
function getText(tree) {
return (tree.content || []).map(function (child) {
if (typeof child === 'string') {
return child;
} else {
return getText(child);
}
}).join('');
}
function isPropNeverAppliedToText_(targetProp, tree, allowWhitespace) {
if (typeof tree === 'string') {
return allowWhitespace ? isWhitespace(tree) : false;
}
if (tree.tag === 'img') {
if (tree.attrs && tree.attrs.style) {
const styles = parseStyle(tree.attrs.style).nodes;
if (styles.some(function (s) { return s.prop === targetProp; })) {
return true;
}
}
return false;
}
if (!tree.content) {
return true;
}
if (tree.attrs && tree.attrs.style) {
const styles = parseStyle(tree.attrs.style).nodes;
if (styles.some(function (s) { return s.prop === targetProp; })) {
return true;
}
}
return tree.content.every(function (child) {
return isPropNeverAppliedToText(targetProp, child);
});
}
// Same as the _ helper version, except don't check the first level
// for the targetProp style
function isPropNeverAppliedToText(targetProp, tree, allowWhitespace) {
if (typeof allowWhitespace === 'undefined') {
allowWhitespace = true;
}
if (typeof tree === 'string') {
return false;
}
if (tree.tag === 'img') {
return false;
}
if (!tree.content) {
return true;
}
return tree.content.every(function (child) {
return isPropNeverAppliedToText_(targetProp, child, allowWhitespace);
});
}
function postHtmlMinifyInlineCss(tree) {
tree.match({}, function (node) {
// Ignore text nodes
if (!node.tag) {
return node;
}
const text = node.content ? entities.decodeHTML(getText(node)) : '';
if (isWhitespace(text) && node.attrs && node.attrs.style) {
if (node.tag.toLowerCase() !== 'img') {
const styles = parseStyle(node.attrs.style);
styles.nodes = styles.nodes.filter(function (o) {
return !contentProps.has(o.prop);
});
if (!node.content) {
styles.nodes = styles.nodes.filter(function (o) {
return !noContentProps.has(o.prop);
});
}
node.attrs.style = styles.toString();
}
}
if (node.attrs && node.attrs.style) {
const styles = parseStyle(node.attrs.style),
props = new Set(styles.nodes.map(property('prop')));
contentPropsSafe.forEach(function (cp) {
if (props.has(cp) && isPropNeverAppliedToText(cp, node)) {
styles.nodes = styles.nodes.filter(function (o) {
return o.prop !== cp;
});
}
});
contentPropsNoTextSafe.forEach(function (cp) {
if (node.tag === 'a' && cp === 'text-decoration') {
return;
}
if (props.has(cp) && isPropNeverAppliedToText(cp, node, false)) {
styles.nodes = styles.nodes.filter(function (o) {
return o.prop !== cp;
});
}
});
node.attrs.style = styles.toString();
}
if (node.attrs && node.attrs.style === '') {
delete node.attrs.style;
}
return node;
});
}
module.exports = function (options) {
// Options not used; but may be later.
return postHtmlMinifyInlineCss;
};