Created
June 29, 2017 19:21
-
-
Save baranok/3628b4ac14a5f10899cbd6f2483b5580 to your computer and use it in GitHub Desktop.
CSS Variable Polyfill
This file contains 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
/* | |
TODO: | |
- Option to wait to apply anything until all <link>s are parsed or inject what we have and update as each <link> returns | |
*/ | |
var cssVarPoly = { | |
init: function() { | |
if (window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)')) { | |
return; | |
} | |
cssVarPoly.ratifiedVars = {}; | |
cssVarPoly.varsByBlock = {}; | |
cssVarPoly.oldCSS = {}; | |
// start things off | |
cssVarPoly.findCSS(); | |
cssVarPoly.updateCSS(); | |
}, | |
// find all the css blocks, save off the content, and look for variables | |
findCSS: function() { | |
var styleBlocks = document.querySelectorAll('style:not([id*="inserted"]),link[type="text/css"],link[rel="stylesheet"]'); | |
// we need to track the order of the style/link elements when we save off the CSS, set a counter | |
var counter = 1; | |
// loop through all CSS blocks looking for CSS variables being set | |
[].forEach.call(styleBlocks, function(block) { | |
var theCSS; | |
if (block.nodeName === 'STYLE') { | |
theCSS = block.innerHTML; | |
cssVarPoly.getImports(block.innerHTML, function(theCSS){ | |
block.innerHTML = theCSS; | |
cssVarPoly.findSetters(theCSS, counter); | |
}); | |
} else if (block.nodeName === 'LINK') { | |
cssVarPoly.getLink(block.getAttribute('href'), counter, function(counter, request) { | |
cssVarPoly.getImports(request.responseText, function(respCSS){ | |
cssVarPoly.findSetters(respCSS, counter); | |
cssVarPoly.oldCSS[counter] = respCSS; | |
cssVarPoly.updateCSS(); | |
}); | |
}); | |
theCSS = ''; | |
} | |
// save off the CSS to parse through again later. the value may be empty for links that are waiting for their ajax return, but this will maintain the order | |
cssVarPoly.oldCSS[counter] = theCSS; | |
counter++; | |
}); | |
}, | |
// find all the "--variable: value" matches in a provided block of CSS and add them to the master list | |
findSetters: function(theCSS, counter) { | |
cssVarPoly.varsByBlock[counter] = theCSS.match(/([^var\/\*])--[a-zA-Z0-9\-]+:(\s?)(.+?);/gmi); | |
}, | |
// run through all the CSS blocks to update the variables and then inject on the page | |
updateCSS: function() { | |
// first lets loop through all the variables to make sure later vars trump earlier vars | |
cssVarPoly.ratifySetters(cssVarPoly.varsByBlock); | |
// loop through the css blocks (styles and links) | |
for (var curCSSID in cssVarPoly.oldCSS) { | |
var newCSS = cssVarPoly.replaceGetters(cssVarPoly.oldCSS[curCSSID], cssVarPoly.ratifiedVars); | |
// put it back into the page | |
// first check to see if this block exists already | |
if (document.querySelector('#inserted' + curCSSID)) { | |
document.querySelector('#inserted' + curCSSID).innerHTML = newCSS; | |
} else { | |
var style = document.createElement('style'); | |
style.innerHTML = newCSS; | |
style.id = 'inserted' + curCSSID; | |
document.getElementsByTagName('head')[0].appendChild(style); | |
} | |
}; | |
}, | |
// parse a provided block of CSS looking for a provided list of variables and replace the --var-name with the correct value | |
replaceGetters: function(curCSS, varList) { | |
for (var theVar in varList) { | |
// match the variable with the actual variable name | |
var getterRegex = new RegExp('var\\(\\s*?' + theVar.trim() + '(\\)|,([\\s\\,\\w]*\\)))', 'g'); | |
curCSS = curCSS.replace(getterRegex, varList[theVar]); | |
}; | |
// now check for any getters that are left that have fallbacks | |
var getterRegex2 = /var\(\s*?([^)^,\.]*)(?:[\s\,])*([^)\.]*)\)/g; | |
while (match = getterRegex2.exec(curCSS)) { | |
// find the fallback within the getter | |
curCSS = curCSS.replace(match[0], match[2]); | |
} | |
return curCSS; | |
}, | |
// determine the css variable name value pair and track the latest | |
ratifySetters: function(varList) { | |
// loop through each block in order, to maintain order specificity | |
for (var curBlock in varList) { | |
var curVars = varList[curBlock]; | |
// loop through each var in the block | |
curVars.forEach(function(theVar) { | |
// split on the name value pair separator | |
var matches = theVar.split(/:\s*/); | |
// put it in an object based on the varName. Each time we do this it will override a previous use and so will always have the last set be the winner | |
// 0 = the name, 1 = the value, strip off the ; if it is there | |
cssVarPoly.ratifiedVars[matches[0]] = matches[1].replace(/;/, ''); | |
}); | |
}; | |
}, | |
// get the CSS file | |
getLink: function(url, counter, success) { | |
var request = new XMLHttpRequest(); | |
request.open('GET', url, true); | |
request.overrideMimeType('text/css;'); | |
request.onload = function() { | |
if (request.status >= 200 && request.status < 400) { | |
if (typeof success === 'function') { | |
success(counter, request, url); | |
} | |
} else { | |
// We reached our target server, but it returned an error | |
console.warn('an error was returned from:', url); | |
} | |
}; | |
request.onerror = function() { | |
// There was a connection error of some sort | |
console.warn('we could not get anything from:', url); | |
}; | |
request.send(); | |
}, | |
getImports: function(css, callback){ | |
var reg = /^(?![\/\*])@import\s.*(?:url\(["'])([a-zA-Z0-9\.:\/_-]*)["']\)?([^;]*);/gim, | |
matches, | |
todo = 0, | |
done = 0, | |
replacements = {}; | |
if(css.search(reg) === -1){callback(css)} | |
while (matches = reg.exec(css)) { | |
replacements[matches[1]] = matches; | |
todo++; | |
cssVarPoly.getLink(matches[1], null, function(counter, request, url){ | |
css = css.replace(replacements[url][0], (replacements[url][2].trim() ? ('@media ' + replacements[url][2].trim() + ' {') : '') + request.responseText + (replacements[url][2].trim() ? '}' : '')); | |
done++; | |
if(done === todo){callback(css)} | |
}); | |
} | |
} | |
}; | |
cssVarPoly.init(); |
This file contains 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
var p={t:function(){window.CSS&&window.CSS.supports&&window.CSS.supports("(--foo: red)")||(p.v={},p.b={},p.o={},p.f(),p.u())},f:function(){var a=document.querySelectorAll('style:not([id*="inserted"]),link[type="text/css"],link[rel="stylesheet"]'),b=1;[].forEach.call(a,function(a){var c;"STYLE"===a.nodeName?(c=a.innerHTML,p.i(a.innerHTML,function(c){a.innerHTML=c,p.s(c,b)})):"LINK"===a.nodeName&&(p.g(a.getAttribute("href"),b,function(a,b){p.i(b.responseText,function(b){p.s(b,a),p.o[a]=b,p.u()})}),c=""),p.o[b]=c,b++})},s:function(a,b){p.b[b]=a.match(/([^var\/\*])--[a-zA-Z0-9\-]+:(\s?)(.+?);/gim)},u:function(){p.r(p.b);for(var a in p.o){var b=p.c(p.o[a],p.v);if(document.querySelector("#inserted"+a))document.querySelector("#inserted"+a).innerHTML=b;else{var c=document.createElement("style");c.innerHTML=b,c.id="inserted"+a,document.getElementsByTagName("head")[0].appendChild(c)}}},c:function(a,b){for(var c in b){var d=new RegExp("var\\(\\s*?"+c.trim()+"(\\)|,([\\s\\,\\w]*\\)))","g");a=a.replace(d,b[c])}for(var e=/var\(\s*?([^)^,\.]*)(?:[\s\,])*([^)\.]*)\)/g;match=e.exec(a);)a=a.replace(match[0],match[2]);return a},r:function(a){for(var b in a){var c=a[b];c.forEach(function(a){var b=a.split(/:\s*/);p.v[b[0]]=b[1].replace(/;/,"")})}},g:function(a,b,c){var d=new XMLHttpRequest;d.open("GET",a,!0),d.overrideMimeType("text/css;"),d.onload=function(){d.status>=200&&d.status<400?"function"==typeof c&&c(b,d,a):console.warn("an error was returned from:",a)},d.onerror=function(){console.warn("we could not get anything from:",a)},d.send()},i:function(a,b){var d,c=/^(?![\/\*])@import\s.*(?:url\(["'])([a-zA-Z0-9\.:\/_-]*)["']\)?([^;]*);/gim,e=0,f=0,g={};for(a.search(c)===-1&&b(a);d=c.exec(a);)g[d[1]]=d,e++,p.g(d[1],null,function(c,d,h){a=a.replace(g[h][0],(g[h][2].trim()?"@media "+g[h][2].trim()+" {":"")+d.responseText+(g[h][2].trim()?"}":"")),f++,f===e&&b(a)})}};p.t(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment