Last active
August 29, 2015 14:11
-
-
Save yisibl/a0ce8de274adef1a1704 to your computer and use it in GitHub Desktop.
CSSGrace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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
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