CSSGrace
/** | |
cssgrace.pack(css) | |
*/ | |
var path = require('path'); | |
var http = require('http'); | |
var url = require('url'); | |
var postcss = require('postcss'); | |
var sizeOf = require('image-size'); | |
var reVALUE = /([\.0-9]+)(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|dpi|dpcm|dppx|fr)/i; | |
var reRGBA = /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+\s*)/gi; | |
var reALL_PSEUDO = /::(before|after|first-line|first-letter)/gi; | |
var reBEFORE_AFTER = /::|:(before|after)/gi; | |
var reURL = /url\s*\(\s*(['"]?)([^\)'"]+)\1\s*\)\s+(\dx)/gi; | |
var reNO_SETURL = /url\(\s*(['"]?)([^\)'"]+)\1\s*\)/gi; | |
var reIMAGE_SIZE = /([-]?)((image-width)|(image-height))/i; | |
var reBLANK_LINE = /(\r\n|\n|\r)(\s*?\1)+/gm; | |
/** | |
* Remove display: block | |
* If float: left|right & position: absolute|fixed => remove display: block; | |
input: | |
.foo{position: absolute; display: block;} | |
output: | |
.foo{position: absolute;} | |
^ | |
*/ | |
var remove_display = function(decl) { | |
if ( | |
((decl.prop == 'position') && (decl.value == 'absolute' || decl.value == 'fixed')) || | |
(decl.prop == 'float' && decl.value != 'none') | |
) { | |
// Remove display | |
decl.parent.each(function(neighbor) { | |
if (neighbor.prop == 'display' && neighbor.value != 'none') { | |
//Remove | |
neighbor.removeSelf(); | |
} | |
}); | |
} | |
} | |
/** | |
* Remove float | |
* If position: absolute|fixed or display: flex, remove float | |
input: | |
.foo{position: absolute; float: right;} | |
output: | |
.foo{position: absolute;} | |
^ | |
*/ | |
var remove_float = function(decl) { | |
if ( | |
((decl.prop == 'position') && (decl.value == 'absolute' || decl.value == 'fixed')) || | |
((decl.prop == 'display') && (decl.value == 'flex')) | |
) { | |
decl.parent.each(function(neighbor) { | |
if ( | |
(neighbor.prop == 'float') | |
) { | |
neighbor.removeSelf(); | |
} | |
}); | |
} | |
} | |
//Remove the colons | |
/** | |
input: | |
.foo::after{display: block;} | |
output: | |
.foo:after{display: block;} | |
^ | |
*/ | |
var remove_colons = function(rule, i) { | |
if (rule.selector.match(reALL_PSEUDO)) { | |
rule.selector = rule.selector.replace(/::/g, ':'); | |
} | |
} | |
// Add content: "" | |
/** | |
input: | |
.foo:after{display: block;} | |
output: | |
.foo:after{content: ''; display: block;} | |
^ | |
*/ | |
var add_content = function(rule, i) { | |
if (rule.selector.match(reBEFORE_AFTER)) { | |
var hasContent = rule.some(function(i) { | |
return i.prop == 'content'; | |
}); | |
if (!hasContent) { | |
// Add content: "" | |
rule.prepend({ | |
prop: 'content', | |
value: "''" | |
}); | |
} | |
} | |
} | |
// position: center mixin | |
/** | |
input: | |
.foo{position: center; width: 200px; height: 100px;} | |
output: | |
.foo{position: absolute; width: 200px; height: 100px; left: 50%; right: 50%; margin-left: -100px; margin-top: -50px;} | |
^----> | |
*/ | |
function position_center_mixin(decl, i) { | |
var hasPosition = decl.parent.some(function(i) { | |
return i.prop == 'position' && i.value == 'center'; | |
}); | |
var hasWidth = decl.parent.some(function(i) { | |
return i.prop == 'width'; | |
}); | |
var hasHeight = decl.parent.some(function(i) { | |
return i.prop == 'height'; | |
}); | |
if (hasPosition && hasWidth && hasHeight) { | |
var widthValue, heightValue; | |
if (decl.prop == 'position') { | |
decl.value = 'absolute'; | |
decl.parent.eachDecl(function(decl) { | |
if (decl.prop == 'width') { | |
matchWidth = decl.value.match(reVALUE); | |
if (matchWidth && matchWidth != null) { | |
// console.log('matchW', matchWidth) | |
widthValue = (-matchWidth[1] / 2) + matchWidth[2]; | |
} | |
} | |
if (decl.prop == 'height') { | |
matchHeight = decl.value.match(reVALUE); | |
if (matchHeight != null) { | |
heightValue = (-matchHeight[1] / 2) + matchHeight[2]; | |
} | |
} | |
}); | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'margin-left', | |
value: widthValue | |
}); | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'margin-top', | |
value: heightValue | |
}); | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'top', | |
value: '50%' | |
}); | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'left', | |
value: '50%' | |
}); | |
} | |
} | |
} | |
/** | |
* ellipsis mixin | |
* | |
*/ | |
function ellipsis_mixin(decl, i) { | |
// var decl = decl.parent.childs[i]; | |
if (decl.prop == 'text-overflow' && decl.value == 'ellipsis') { | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
var count_overflow = 0, | |
count_whitespace = 0; | |
decl.parent.eachDecl(function(decl) { | |
// if overflow ≠ hidden, add white-space | |
if (decl.prop == 'overflow') { | |
decl.value = 'hidden'; | |
count_overflow++; | |
} | |
if (decl.prop == 'white-space') { | |
decl.value = 'nowrap'; | |
count_whitespace++; | |
} | |
}); | |
if (count_overflow == 0 && count_whitespace == 0) { | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'overflow', | |
value: 'hidden' | |
}); | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'white-space', | |
value: 'nowrap' | |
}); | |
} else if (count_overflow == 0) { | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'overflow', | |
value: 'hidden' | |
}); | |
} else if (count_whitespace == 0) { | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'white-space', | |
value: 'nowrap' | |
}); | |
} | |
} | |
} | |
/** | |
* resize mixin | |
* Add overflow auto | |
*/ | |
function resize_mixin(decl, i) { | |
if (decl.prop == 'resize' && decl.value !== 'none') { | |
var count = 0; | |
decl.parent.eachDecl(function(decl) { | |
if (decl.prop == "overflow") | |
count++; | |
}); | |
if (count === 0) { | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'overflow', | |
value: 'auto' | |
}); | |
} | |
} | |
} | |
/** | |
* clearfix mixin | |
* Use clear: fix | |
*/ | |
function clearfix_mixin(decl, i) { | |
if (decl.prop == 'clear' && decl.value == 'fix') { | |
decl.prop = '*zoom'; | |
decl.value = '1'; | |
var count = 0; | |
decl.parent.eachDecl(function(decl) { | |
if ( | |
(decl.prop == "overflow" && decl.value != 'visible') || | |
(decl.prop == "display" && decl.value == 'inline-block') || | |
(decl.prop == "position" && decl.value == 'absolute') || | |
(decl.prop == "position" && decl.value == 'fixed') | |
) { | |
count++; | |
} | |
}); | |
if (count === 0) { | |
var bothSelector = '\n' + decl.parent.selector + ':before' + ',\n' + decl.parent.selector + ':after'; | |
var afterSelector = '\n' + decl.parent.selector + ':after'; | |
var bothRule = postcss.rule({ | |
selector: bothSelector | |
}); | |
var afterRule = postcss.rule({ | |
selector: afterSelector | |
}); | |
decl.parent.parent.insertAfter(decl.parent, bothRule); | |
decl.parent.parent.insertAfter(decl.parent, afterRule); | |
bothRule.append({ | |
prop: 'content', | |
value: "''" | |
}).append({ | |
prop: 'display', | |
value: 'table' | |
}); | |
afterRule.append({ | |
prop: 'clear', | |
value: 'both' | |
}); | |
} else { | |
if (decl.parent.childs[i + 1] && decl.parent.childs[i + 1].type == "comment") { | |
decl.parent.childs[i + 1].removeSelf(); | |
} | |
decl.removeSelf(); | |
} | |
} | |
} | |
/** | |
* IE opacity hack | |
* Transform to IE filter | |
input: | |
.foo{opacity: .5;} | |
output: | |
.foo{opacity: .5; filter: alpha(opacity=50);} | |
*/ | |
function ie_opacity_hack(decl, i) { | |
var amount = Math.round(decl.value * 100); | |
if (decl.prop == 'opacity') { | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'filter', | |
value: 'alpha(opacity=' + amount + ')' | |
}); | |
} | |
} | |
/** | |
* IE rgba hack | |
* background rgba transform to IE ARGB | |
input: | |
.foo{ | |
background: rgba(8, 22, 123, .8); | |
} | |
output: | |
.foo{ | |
background: rgba(8, 22, 123, .8); | |
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cc08167b', endColorstr='#cc08167b'); | |
} | |
:root .foo { | |
filter: none\9; | |
} | |
*/ | |
} | |
function ie_rgba_hack(decl, i) { | |
function pad(str) { | |
return str.length == 1 ? '0' + str : '' + str; | |
} | |
if ((decl.prop == 'background' || decl.prop == 'background-color') && | |
decl.value.match(reRGBA)) { | |
// rgba transform to AARRGGBB | |
var colorR = pad(parseInt(RegExp.$1).toString(16)); | |
var colorG = pad(parseInt(RegExp.$2).toString(16)); | |
var colorB = pad(parseInt(RegExp.$3).toString(16)); | |
var colorA = pad(parseInt(RegExp.$4 * 255).toString(16)); | |
var ARGB = "'" + "#" + colorA + colorR + colorG + colorB + "'"; | |
// add filter | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: 'filter', | |
value: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=' + ARGB + ', endColorstr=' + ARGB + ')' | |
}); | |
// add :root hack | |
var newSelector = ':root ' + decl.parent.selector; | |
var ieHack = '\\9;' + decl.parent.after; | |
var nextrule = postcss.rule({ | |
selector: newSelector, | |
after: ieHack | |
}); | |
decl.parent.parent.insertAfter(decl.parent, nextrule); | |
nextrule.append({ | |
prop: 'filter', | |
value: 'none' | |
}); | |
} | |
} | |
// IE 6/7 inline-block | |
function ie_inline_block_hack(decl, i) { | |
if (decl.prop == 'display' && decl.value == 'inline-block') { | |
// var decl = decl.parent.childs[i]; | |
var reBefore = decl.before.replace(reBLANK_LINE, '$1') | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: '*zoom', | |
value: 1 | |
}); | |
insertDecl(decl, i, { | |
before: reBefore, | |
prop: '*display', | |
value: 'inline' | |
}); | |
} | |
} | |
/** | |
* 1x.jpg width= 200px height= 100px | |
input: | |
.foo{ | |
background: -webkit-image-set(url(./images/1x.jpg) 1x, url(./images/2x.jpg) 2x); | |
width: image-width; | |
height: image-height; | |
} | |
output: | |
.foo{ | |
background-image: url(./images/1x.png); | |
background: -webkit-image-set(url(./images/1x.jpg) 1x, url(./images/2x.jpg) 2x); | |
width: 200px; | |
height: 100px; | |
} | |
@media | |
only screen and (-o-min-device-pixel-ratio: 2/1), | |
only screen and (min--moz-device-pixel-ratio: 2), | |
only screen and (-moz-min-device-pixel-ratio: 2), | |
only screen and (-webkit-min-device-pixel-ratio: 2), | |
only screen and (min-resolution: 192dpi), | |
only screen and (min-resolution: 2dppx) { | |
.foo { | |
background-image: url(./images/2x.jpg); | |
background-size: 200px 100px; | |
} | |
} | |
*/ | |
function image_set_mixin(decl, i) { | |
/** | |
* Retina display background image | |
* 1x = 1dppx = 96dpi ≈0.39dpcm | |
* 1dpcm ≈ 2.54dpi | |
*/ | |
// var reURL = /url\s*\(\s*(['"]?)([^\)'"]+)\1\s*\)\s+[\d]+|(x|dpi|dppx|dpcm)/gi; | |
if (decl.prop == 'background' || decl.prop == 'background-image') { | |
if (decl.value.indexOf('image-set(') != -1) { | |
var paths = returnURL(decl.value, reURL); | |
var obj = {}; | |
for (var j = 0; j < paths.length; j++) { | |
obj.url = paths[j][2]; | |
obj.path = "url(" + obj.url + ")"; | |
obj.whichImg = paths[j][3]; | |
if (obj.whichImg == "1x" || obj.whichImg == "1x") { | |
var normalSizes = sizeOf(path.join(process.cwd(), obj.url)); | |
normalWidth = normalSizes.width + 'px'; | |
normalHeight = normalSizes.height + 'px'; | |
decl.parent.insertBefore(i, { | |
prop: 'background-image', | |
value: obj.path | |
}); | |
} else if (obj.whichImg == "2x" || obj.whichImg == "2x") { | |
var rSizes = sizeOf(path.join(process.cwd(), obj.url)); //2x image size | |
rWidth = rSizes.width / 2 + 'px'; | |
rHeight = rSizes.height / 2 + 'px'; | |
bgSize = rWidth + ' ' + rHeight; | |
// add hack | |
var atRuleObj = {}; | |
atRuleObj.name = "media"; | |
var new_params = [ | |
'\n only screen and (-o-min-device-pixel-ratio: 2/1)', | |
'\n only screen and (min--moz-device-pixel-ratio: 2)', | |
'\n only screen and (-moz-min-device-pixel-ratio: 2)', | |
'\n only screen and (-webkit-min-device-pixel-ratio: 2)', | |
'\n only screen and (min-resolution: 192dpi)', | |
'\n only screen and (min-resolution: 2dppx)' | |
]; | |
var atRule = postcss.atRule({ | |
name: 'media', | |
params: new_params | |
}); | |
var atBefore = decl.parent.before; | |
var nextrule = postcss.rule({ | |
selector: decl.parent.selector, | |
after: decl.parent.after, | |
// before: atBefore | |
}); | |
//add @ | |
nextrule.append({ | |
prop: 'background-image', | |
value: obj.path | |
}).append({ | |
prop: 'background-size', | |
value: bgSize | |
}); | |
atRule.append(nextrule); | |
decl.parent.parent.insertAfter(decl.parent, atRule); | |
} | |
} | |
} else if (decl.value.indexOf('url(') != -1) { | |
//if image-set == -1 | |
var paths2 = returnURL(decl.value, reNO_SETURL) //获取第一个url图片的路径 | |
var normalSizes2 = sizeOf(path.join(process.cwd(), paths2[0][2])); | |
normalWidth = normalSizes2.width + 'px'; | |
normalHeight = normalSizes2.height + 'px'; | |
} | |
} | |
/** | |
* Get images size | |
.foo{ | |
background: url(images/foo.png); | |
width: image-width; | |
height: image-height; | |
} | |
*/ | |
if (decl.prop != 'content' && (/image-width/gi).test(decl.value) || (/image-height/gi).test(decl.value)) { | |
decl.value = decl.value.replace(/image-width/gi, normalWidth).replace(/image-height/gi, normalHeight); | |
} | |
} | |
//Keep the comments on the current line | |
// Here we need better API | |
function insertDecl(decl, i, newDecl) { | |
var next = decl.parent.childs[i + 1], | |
decl_after; | |
if (next && next.type == 'comment' && next.before.indexOf('\n') == -1) { | |
decl_after = next; | |
} else { | |
decl_after = decl; | |
} | |
decl.parent.insertAfter(decl_after, newDecl) | |
} | |
var cssgraceRule = function(rule, i) { | |
var normalWidth = "", | |
normalHeight = ""; //1x images; | |
remove_colons(rule, i); | |
add_content(rule, i); | |
rule.eachDecl(function(decl, i) { | |
remove_display(decl, i); | |
ie_inline_block_hack(decl, i); | |
ie_opacity_hack(decl, i); | |
ie_rgba_hack(decl, i); | |
resize_mixin(decl, i); | |
ellipsis_mixin(decl, i); | |
clearfix_mixin(decl, i); | |
image_set_mixin(decl, i); | |
}); | |
rule.eachDecl(function(decl, i) { | |
position_center_mixin(decl, i); | |
remove_float(decl, i); | |
remove_display(decl, i); | |
}) | |
}; | |
function returnURL(val, reg) { | |
var result, paths = [] | |
while ((result = reg.exec(val)) != null) { | |
paths.push(result); | |
} | |
return paths; | |
} | |
// PostCSS Processor | |
// var cssgrace = function(css) { | |
// return postcss(function(css) { | |
// css.eachRule(cssgraceRule); | |
// }).process(css); | |
// }; | |
// module.exports = cssgrace; | |
var cssprocess = function(css) { | |
css.eachRule(cssgraceRule); | |
} | |
var pack = function(css, opts) { | |
return postcss().use(this.processor).process(css, opts); | |
} | |
exports.postcss = cssprocess | |
exports.cssgrace = pack | |
exports.pack = pack | |
exports.processor = cssprocess |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
About comments problems
Normal
input:
output:
Use ie_rgba_hack function
In the first brace
input:
output:
input:
output:
In the last brace
input:
output:
Expected output