[6a3a178] | 1 | var resolveProperty = require('css-tree').property;
|
---|
| 2 | var resolveKeyword = require('css-tree').keyword;
|
---|
| 3 | var walk = require('css-tree').walk;
|
---|
| 4 | var generate = require('css-tree').generate;
|
---|
| 5 | var fingerprintId = 1;
|
---|
| 6 | var dontRestructure = {
|
---|
| 7 | 'src': 1 // https://github.com/afelix/csso/issues/50
|
---|
| 8 | };
|
---|
| 9 |
|
---|
| 10 | var DONT_MIX_VALUE = {
|
---|
| 11 | // https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility
|
---|
| 12 | 'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i,
|
---|
| 13 | // https://developer.mozilla.org/en/docs/Web/CSS/text-align
|
---|
| 14 | 'text-align': /^(start|end|match-parent|justify-all)$/i
|
---|
| 15 | };
|
---|
| 16 |
|
---|
| 17 | var SAFE_VALUES = {
|
---|
| 18 | cursor: [
|
---|
| 19 | 'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help',
|
---|
| 20 | 'n-resize', 'e-resize', 's-resize', 'w-resize',
|
---|
| 21 | 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize',
|
---|
| 22 | 'pointer', 'progress', 'not-allowed', 'no-drop', 'vertical-text', 'all-scroll',
|
---|
| 23 | 'col-resize', 'row-resize'
|
---|
| 24 | ],
|
---|
| 25 | overflow: [
|
---|
| 26 | 'hidden', 'visible', 'scroll', 'auto'
|
---|
| 27 | ],
|
---|
| 28 | position: [
|
---|
| 29 | 'static', 'relative', 'absolute', 'fixed'
|
---|
| 30 | ]
|
---|
| 31 | };
|
---|
| 32 |
|
---|
| 33 | var NEEDLESS_TABLE = {
|
---|
| 34 | 'border-width': ['border'],
|
---|
| 35 | 'border-style': ['border'],
|
---|
| 36 | 'border-color': ['border'],
|
---|
| 37 | 'border-top': ['border'],
|
---|
| 38 | 'border-right': ['border'],
|
---|
| 39 | 'border-bottom': ['border'],
|
---|
| 40 | 'border-left': ['border'],
|
---|
| 41 | 'border-top-width': ['border-top', 'border-width', 'border'],
|
---|
| 42 | 'border-right-width': ['border-right', 'border-width', 'border'],
|
---|
| 43 | 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
|
---|
| 44 | 'border-left-width': ['border-left', 'border-width', 'border'],
|
---|
| 45 | 'border-top-style': ['border-top', 'border-style', 'border'],
|
---|
| 46 | 'border-right-style': ['border-right', 'border-style', 'border'],
|
---|
| 47 | 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
|
---|
| 48 | 'border-left-style': ['border-left', 'border-style', 'border'],
|
---|
| 49 | 'border-top-color': ['border-top', 'border-color', 'border'],
|
---|
| 50 | 'border-right-color': ['border-right', 'border-color', 'border'],
|
---|
| 51 | 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
|
---|
| 52 | 'border-left-color': ['border-left', 'border-color', 'border'],
|
---|
| 53 | 'margin-top': ['margin'],
|
---|
| 54 | 'margin-right': ['margin'],
|
---|
| 55 | 'margin-bottom': ['margin'],
|
---|
| 56 | 'margin-left': ['margin'],
|
---|
| 57 | 'padding-top': ['padding'],
|
---|
| 58 | 'padding-right': ['padding'],
|
---|
| 59 | 'padding-bottom': ['padding'],
|
---|
| 60 | 'padding-left': ['padding'],
|
---|
| 61 | 'font-style': ['font'],
|
---|
| 62 | 'font-variant': ['font'],
|
---|
| 63 | 'font-weight': ['font'],
|
---|
| 64 | 'font-size': ['font'],
|
---|
| 65 | 'font-family': ['font'],
|
---|
| 66 | 'list-style-type': ['list-style'],
|
---|
| 67 | 'list-style-position': ['list-style'],
|
---|
| 68 | 'list-style-image': ['list-style']
|
---|
| 69 | };
|
---|
| 70 |
|
---|
| 71 | function getPropertyFingerprint(propertyName, declaration, fingerprints) {
|
---|
| 72 | var realName = resolveProperty(propertyName).basename;
|
---|
| 73 |
|
---|
| 74 | if (realName === 'background') {
|
---|
| 75 | return propertyName + ':' + generate(declaration.value);
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | var declarationId = declaration.id;
|
---|
| 79 | var fingerprint = fingerprints[declarationId];
|
---|
| 80 |
|
---|
| 81 | if (!fingerprint) {
|
---|
| 82 | switch (declaration.value.type) {
|
---|
| 83 | case 'Value':
|
---|
| 84 | var vendorId = '';
|
---|
| 85 | var iehack = '';
|
---|
| 86 | var special = {};
|
---|
| 87 | var raw = false;
|
---|
| 88 |
|
---|
| 89 | declaration.value.children.each(function walk(node) {
|
---|
| 90 | switch (node.type) {
|
---|
| 91 | case 'Value':
|
---|
| 92 | case 'Brackets':
|
---|
| 93 | case 'Parentheses':
|
---|
| 94 | node.children.each(walk);
|
---|
| 95 | break;
|
---|
| 96 |
|
---|
| 97 | case 'Raw':
|
---|
| 98 | raw = true;
|
---|
| 99 | break;
|
---|
| 100 |
|
---|
| 101 | case 'Identifier':
|
---|
| 102 | var name = node.name;
|
---|
| 103 |
|
---|
| 104 | if (!vendorId) {
|
---|
| 105 | vendorId = resolveKeyword(name).vendor;
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | if (/\\[09]/.test(name)) {
|
---|
| 109 | iehack = RegExp.lastMatch;
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | if (SAFE_VALUES.hasOwnProperty(realName)) {
|
---|
| 113 | if (SAFE_VALUES[realName].indexOf(name) === -1) {
|
---|
| 114 | special[name] = true;
|
---|
| 115 | }
|
---|
| 116 | } else if (DONT_MIX_VALUE.hasOwnProperty(realName)) {
|
---|
| 117 | if (DONT_MIX_VALUE[realName].test(name)) {
|
---|
| 118 | special[name] = true;
|
---|
| 119 | }
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | break;
|
---|
| 123 |
|
---|
| 124 | case 'Function':
|
---|
| 125 | var name = node.name;
|
---|
| 126 |
|
---|
| 127 | if (!vendorId) {
|
---|
| 128 | vendorId = resolveKeyword(name).vendor;
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | if (name === 'rect') {
|
---|
| 132 | // there are 2 forms of rect:
|
---|
| 133 | // rect(<top>, <right>, <bottom>, <left>) - standart
|
---|
| 134 | // rect(<top> <right> <bottom> <left>) – backwards compatible syntax
|
---|
| 135 | // only the same form values can be merged
|
---|
| 136 | var hasComma = node.children.some(function(node) {
|
---|
| 137 | return node.type === 'Operator' && node.value === ',';
|
---|
| 138 | });
|
---|
| 139 | if (!hasComma) {
|
---|
| 140 | name = 'rect-backward';
|
---|
| 141 | }
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | special[name + '()'] = true;
|
---|
| 145 |
|
---|
| 146 | // check nested tokens too
|
---|
| 147 | node.children.each(walk);
|
---|
| 148 |
|
---|
| 149 | break;
|
---|
| 150 |
|
---|
| 151 | case 'Dimension':
|
---|
| 152 | var unit = node.unit;
|
---|
| 153 |
|
---|
| 154 | if (/\\[09]/.test(unit)) {
|
---|
| 155 | iehack = RegExp.lastMatch;
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | switch (unit) {
|
---|
| 159 | // is not supported until IE11
|
---|
| 160 | case 'rem':
|
---|
| 161 |
|
---|
| 162 | // v* units is too buggy across browsers and better
|
---|
| 163 | // don't merge values with those units
|
---|
| 164 | case 'vw':
|
---|
| 165 | case 'vh':
|
---|
| 166 | case 'vmin':
|
---|
| 167 | case 'vmax':
|
---|
| 168 | case 'vm': // IE9 supporting "vm" instead of "vmin".
|
---|
| 169 | special[unit] = true;
|
---|
| 170 | break;
|
---|
| 171 | }
|
---|
| 172 | break;
|
---|
| 173 | }
|
---|
| 174 | });
|
---|
| 175 |
|
---|
| 176 | fingerprint = raw
|
---|
| 177 | ? '!' + fingerprintId++
|
---|
| 178 | : '!' + Object.keys(special).sort() + '|' + iehack + vendorId;
|
---|
| 179 | break;
|
---|
| 180 |
|
---|
| 181 | case 'Raw':
|
---|
| 182 | fingerprint = '!' + declaration.value.value;
|
---|
| 183 | break;
|
---|
| 184 |
|
---|
| 185 | default:
|
---|
| 186 | fingerprint = generate(declaration.value);
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | fingerprints[declarationId] = fingerprint;
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | return propertyName + fingerprint;
|
---|
| 193 | }
|
---|
| 194 |
|
---|
| 195 | function needless(props, declaration, fingerprints) {
|
---|
| 196 | var property = resolveProperty(declaration.property);
|
---|
| 197 |
|
---|
| 198 | if (NEEDLESS_TABLE.hasOwnProperty(property.basename)) {
|
---|
| 199 | var table = NEEDLESS_TABLE[property.basename];
|
---|
| 200 |
|
---|
| 201 | for (var i = 0; i < table.length; i++) {
|
---|
| 202 | var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints);
|
---|
| 203 | var prev = props.hasOwnProperty(ppre) ? props[ppre] : null;
|
---|
| 204 |
|
---|
| 205 | if (prev && (!declaration.important || prev.item.data.important)) {
|
---|
| 206 | return prev;
|
---|
| 207 | }
|
---|
| 208 | }
|
---|
| 209 | }
|
---|
| 210 | }
|
---|
| 211 |
|
---|
| 212 | function processRule(rule, item, list, props, fingerprints) {
|
---|
| 213 | var declarations = rule.block.children;
|
---|
| 214 |
|
---|
| 215 | declarations.eachRight(function(declaration, declarationItem) {
|
---|
| 216 | var property = declaration.property;
|
---|
| 217 | var fingerprint = getPropertyFingerprint(property, declaration, fingerprints);
|
---|
| 218 | var prev = props[fingerprint];
|
---|
| 219 |
|
---|
| 220 | if (prev && !dontRestructure.hasOwnProperty(property)) {
|
---|
| 221 | if (declaration.important && !prev.item.data.important) {
|
---|
| 222 | props[fingerprint] = {
|
---|
| 223 | block: declarations,
|
---|
| 224 | item: declarationItem
|
---|
| 225 | };
|
---|
| 226 |
|
---|
| 227 | prev.block.remove(prev.item);
|
---|
| 228 |
|
---|
| 229 | // TODO: use it when we can refer to several points in source
|
---|
| 230 | // declaration.loc = {
|
---|
| 231 | // primary: declaration.loc,
|
---|
| 232 | // merged: prev.item.data.loc
|
---|
| 233 | // };
|
---|
| 234 | } else {
|
---|
| 235 | declarations.remove(declarationItem);
|
---|
| 236 |
|
---|
| 237 | // TODO: use it when we can refer to several points in source
|
---|
| 238 | // prev.item.data.loc = {
|
---|
| 239 | // primary: prev.item.data.loc,
|
---|
| 240 | // merged: declaration.loc
|
---|
| 241 | // };
|
---|
| 242 | }
|
---|
| 243 | } else {
|
---|
| 244 | var prev = needless(props, declaration, fingerprints);
|
---|
| 245 |
|
---|
| 246 | if (prev) {
|
---|
| 247 | declarations.remove(declarationItem);
|
---|
| 248 |
|
---|
| 249 | // TODO: use it when we can refer to several points in source
|
---|
| 250 | // prev.item.data.loc = {
|
---|
| 251 | // primary: prev.item.data.loc,
|
---|
| 252 | // merged: declaration.loc
|
---|
| 253 | // };
|
---|
| 254 | } else {
|
---|
| 255 | declaration.fingerprint = fingerprint;
|
---|
| 256 |
|
---|
| 257 | props[fingerprint] = {
|
---|
| 258 | block: declarations,
|
---|
| 259 | item: declarationItem
|
---|
| 260 | };
|
---|
| 261 | }
|
---|
| 262 | }
|
---|
| 263 | });
|
---|
| 264 |
|
---|
| 265 | if (declarations.isEmpty()) {
|
---|
| 266 | list.remove(item);
|
---|
| 267 | }
|
---|
| 268 | }
|
---|
| 269 |
|
---|
| 270 | module.exports = function restructBlock(ast) {
|
---|
| 271 | var stylesheetMap = {};
|
---|
| 272 | var fingerprints = Object.create(null);
|
---|
| 273 |
|
---|
| 274 | walk(ast, {
|
---|
| 275 | visit: 'Rule',
|
---|
| 276 | reverse: true,
|
---|
| 277 | enter: function(node, item, list) {
|
---|
| 278 | var stylesheet = this.block || this.stylesheet;
|
---|
| 279 | var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id;
|
---|
| 280 | var ruleMap;
|
---|
| 281 | var props;
|
---|
| 282 |
|
---|
| 283 | if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
|
---|
| 284 | ruleMap = {};
|
---|
| 285 | stylesheetMap[stylesheet.id] = ruleMap;
|
---|
| 286 | } else {
|
---|
| 287 | ruleMap = stylesheetMap[stylesheet.id];
|
---|
| 288 | }
|
---|
| 289 |
|
---|
| 290 | if (ruleMap.hasOwnProperty(ruleId)) {
|
---|
| 291 | props = ruleMap[ruleId];
|
---|
| 292 | } else {
|
---|
| 293 | props = {};
|
---|
| 294 | ruleMap[ruleId] = props;
|
---|
| 295 | }
|
---|
| 296 |
|
---|
| 297 | processRule.call(this, node, item, list, props, fingerprints);
|
---|
| 298 | }
|
---|
| 299 | });
|
---|
| 300 | };
|
---|