Comments (7)
Hi @vladbicu.
This is a relatively easy fix, but it comes with caveats.
Currently, CSS data is collected from a stylesheet's Node.textContent
value. As you've mentioned, this won't be reliable if a stylesheet has been modified using the deleteRule()
or insertRule()
methods because these modifications aren't reflected in the Node.textContent
value.
The fix requires iterating over the CSSRuleList
of each stylesheet and concatenating all CSSRule.cssText
values into a single string. This works, but there are two things to consider:
-
Iterating over potentially thousands of CSS rules is significantly slower than simply reading a
textContent
value. This is especially true with legacy browsers. For example, a Windows 10 VM on a 2009 Mac Pro @ 2.66GHz is able to generate CSS from the CSSRuleList of bootstrap.min.css in ~10ms running Chrome 69 and ~30ms with IE11. I intentionally chose a large block of CSS to illustrate the point: the fix is significantly slower than the ~0ms it takes to read thetextContent
property.The way to mitigate this performance hit is to limit processing to only the CSS that needs to be processed. This can be done using either the
include
orexclude
options. It looks like emotion.js adds adata-emotion
attribute to the inserted<style>
element, which makes this pretty easy (see below). -
The fix will (potentially) produce different CSS output for each browser. This is because browsers can generate different runtime rules from the same CSS input based on how they handle unrecognized and vendor-prefixed properties and values. This is not a big deal functionally speaking, but it is different than how the ponyfill works today and could be an issue if you are (for some reason) trying to use ponyfill-generated CSS created in one browser in a different browser.
Example CSS:
body { -moz-appearance: none; -webkit-appearance: none; }
Chrome / Safari CSSRule.cssText:
body { -webkit-appearance: none; }
Firefox CSSRule.cssText:
body { -moz-appearance: none; }
Edge / IE CSSRule.cssText:
body { -moz-appearance: none; -webkit-appearance: none; }
With those caveats understood, I think it makes sense to add an option for this scenario. I'm not familiar enough with the various CSS-in-JS libraries to know if this is a common issue, but it seems anyone modifying CSS using the deleteRule()
or insertRule()
methods would benefit.
The ponyfill will continue to work as it does today by default (reading CSS data from the textContent
value), but the new option will allow data to be generated from runtime values. Your final implementation would look something like this:
cssVars({
// Only process emotion.js <style> nodes
include: 'style[data-emotion]' //
// Get <style> CSS from runtime values
parseRuntime: true
}) ;
I'll put something together on a separate branch later on tonight. You can give it a test, see how it works, and if all is well I'll publish the changes to npm.
from css-vars-ponyfill.
I've created a runtime branch with the fix described above. To install from this branch on GitHub using NPM:
npm i -D jhildenbiddle/css-vars-ponyfill#runtime
You can view the options.parseRuntime
docs in the same branch. Let me know if this addresses your issue and if you have any feedback.
from css-vars-ponyfill.
Well, that didn't go as planned.
Turns out the fix doesn't work in legacy browsers. I was testing in the latest version of Chrome which worked fine. Once I ran all automated tests in legacy browers I noticed that the new options.parseRuntime
test was failing.
The reason for the failure is due to the browser behavior I described above:
[...] browsers can generate different runtime rules from the same CSS input based on how they handle unrecognized and vendor-prefixed properties and values.
Legacy browsers don't understand CSS custom properties. When a rule is inserted that contains either a custom property declaration or var()
function, that declaration or function is dropped from the CSSRule.cssText
value. For example:
// Insert two rules into an empty stylesheet (like emotions.js)
styleElm.sheet.insertRule(':root { --color: red; }', 0); // 1
styleElm.sheet.insertRule('p { color: var(--color); }', 0); // 2
// Modern browser CSSRuleList
// 1: ':root { --color: red; }'
// 2: 'p { color: var(--color); }'
// Legacy browser CSSRuleList (e.g. IE)
// 1: ':root { }'
// 2: 'p { }'
Given this, I don't think it is possible to have the ponyfill work with stylesheets that use insertRule()
to add rules that contain CSS custom properties. Bummer.
One last thought...
You mentioned that everything was working fine until you did a production build. I assume this means emotion.js behaves differently in development mode vs production mode by modifying the <style>
elements text content for development but insertRule()
for production (it seems that similar libraries also take this approach for performance reasons). Perhaps there is a way to force emotion.js to update CSS using the same process used in development mode even while in production. Ideally, this would be something that you could enable/disable conditionally on the client, which would allow you to enable this behavior only for legacy browsers. If this option isn't available, consider creating an GitHub issue for emotion. It may be a trivial option to add, and it could enable emotion.js to work with other libs that collect CSS data by reading a stylesheet's textContent
value.
Hope this helps!
from css-vars-ponyfill.
Hi @jhildenbiddle,
Thanks for the effort! Maybe will help some people that use insertRule/deleteRule API.
Emotion library use the .insertRule API in production because it's fast and i don't think they will change that. At this point i have two options:
- Drop IE11 support
- Replace emotion CSS with something else
from css-vars-ponyfill.
Sorry we couldn't find a solution, @vladbicu. Best of luck with your project and finding a solution for IE11.
from css-vars-ponyfill.
Can proxy insetRule method?
https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet
// Object.keys(CSSStyleSheet.prototype)
// (9) ['ownerRule', 'cssRules', 'rules', 'addRule', 'deleteRule', 'insertRule', 'removeRule', 'replace', 'replaceSync']
// Some methods are supported only in higher versions and can be done without a proxy
var insertRuleFn = CSSStyleSheet.prototype.insertRule
CSSStyleSheet.prototype.insertRule =function(ruleStr,index) {
// console.log(ruleStr);
var legacyRuleStr = window.cssVarsSync ? window.cssVarsSync(ruleStr) : ruleStr;
// console.log(legacyRuleStr);
insertRuleFn.call(this, legacyRuleStr, index) }
from css-vars-ponyfill.
@chenyulun --
The challenge with an insertRule
proxy (or any CSSOM proxy, for that matter) is that there is no indication of how "ready" the CSS is for processing. You either have to treat each rule as if it is the last and process it using what you know, or find some indicator to serve as a "ready" event like waiting some amount of time since the last CSSOM method call (addRule
, deleteRule
, insertRule
, etc).
If rules are inserted in a very specific order, this is not an issue:
styleElm.sheet.insertRule(':root { --color: red; }', 0);
styleElm.sheet.insertRule('p { color: var(--color); }', 0);
p { color: red; }
But if rules are inserted out of order...
styleElm.sheet.insertRule('p { color: var(--color); }', 0);
styleElm.sheet.insertRule(':root { --color: red; }', 0);
... or override previous values...
styleElm.sheet.insertRule(':root { --color: red; }', 0);
styleElm.sheet.insertRule('p { color: var(--color); }', 0);
styleElm.sheet.insertRule(':root { --color: blue; }', 0);
... or invalidate previous values ...
styleElm.sheet.insertRule(':root { --color: red; }', 0);
styleElm.sheet.insertRule(':root { --color: blue; }', 0);
styleElm.sheet.insertRule('p { color: var(--color); }', 0);
styleElm.sheet.deleteRule(1);
... things get complicated very quickly.
The ponyfill doesn't suffer from these problems because it doesn't begin its work until the DOMContentLoaded
event fires. This is a "good enough" signal that all CSS is ready for processing:
- Get all CSS data from
<link>
and<style>
elements - Parse CSS data
- Store all custom property values
- Replace all
var()
functions with custom property values - Inject
<style>
elements with transformed CSS after each<link>
and<style>
element processed
Could CSSOM method proxies work? Possibly, but it would be a significant amount of work and has the likely potential to perform poorly if/when previously processed rules need to be re-processed (see examples above). If someone were motivated, they could attempt it using the individual libraries in the src
directory of the repo.
from css-vars-ponyfill.
Related Issues (20)
- works in IE dev mode, but not prod mode HOT 2
- CSS XHR error HOT 2
- Not working IE11 in Prod Mode HOT 3
- How to switch themes without browser reload using css variables? HOT 1
- v2.4.4 no longer transpiles to pure ECMAScript 5 HOT 1
- `CSS.supports()` doesn't support in IE11, but css-vars-ponyfill insert it into build artifact. HOT 1
- Option to skip formatting CSS HOT 3
- Does this library works for dynamically added CSS? HOT 1
- Am i missing something for use in Angular 10+? HOT 1
- the page's efficiency HOT 1
- Doesn't work with styled-components createGlobalStyle HOT 3
- Can I use it in Nuxtjs? HOT 2
- run onComplete callback too slow HOT 8
- [bug] link set setAttribute disabled not work HOT 1
- HasVarChange has a calculation problem HOT 1
- `fixKeyframes` throws error when using shadow DOM + legacy browsers HOT 1
- IE11 js runs too slowly. Can you use this when packaging webpacks? HOT 1
- map file HOT 1
- cssVars():: CSS parse error: missing '}' HOT 1
- ie 9 10 Use without effect HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from css-vars-ponyfill.