question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Polyfill CSS Variables Support

See original GitHub issue

API

The API for writing styles remains the same. For example:

const title = css`
  color: var(--title-color);
`;

When using this class name in a component:

function MyComponent({ color }) {
  return <h1 {...styles(title, { '--title-color': color })} />;
}

How to implement

  1. Write a Babel plugin which looks for styles calls with variables and assigns an unique ID for each. Something like:
    styles(title, { __id: 'xgfs65d', '--title-color': color });
    
    This ID is necessary so that we can keep track of dynamic styles and avoid inserting same styles multiple times. The ID should be unique, but shouldn’t change between builds. The easiest way is to hash the current file path and the sequential number in the file.
  2. We will need to generate an AST from the CSS string. We can probably use the ast plugin for stylis to generate the ast, but I had a broken ast when I used nested media queries. We will probably need to fix the plugin. We will also need to write a simple serializer which takes the AST and serializes it to plain CSS string.
  3. The css tagged template literal should parse the CSS string to an AST to determine if there are CSS variables in the string. It should generate 2 different CSS strings, one with properties using variables, and one without. The one without is inserted normally to the sheet API, but the ones with variables is returned as a templates. It’s an array because there can be multiple rules. The returned object from css can look like following:
    { toString() { return 'xgsf64f'; }, templates: ['.xgsf64f { color: var(--title-color); }'] }
    
    Something to keep in mind is that if a property is redeclared after a variable use, we should discard the one with variable, i.e. color: var(--title-color); color: blue should be considered as color: blue.
  4. The names function should be updated to look for these objects and use toString when necessary
  5. The styles function should look for templates and do a search replace in them with the value of the variables, then insert it to the CSSOM with the insertRule API. If CSS custom properties are supported in the browser, it should avoid the search replace and use inline styles to set the variable values.
  6. The babel presets should be separated to client and server presets, where the server preset excludes the preval-extract extract plugin and includes a plugin which just converts css to css.named calls.
  7. Server rendering APIs and instructions should be updated to read the stylesheet from the string in memory instead of from a file.
  8. This should be configurable in the Babel preset options. If the polyfill behaviour is disabled, templates shouldn’t be generated and inline styles should be used to set the variables instead.

Advantages

  1. A consistent prop-based way to use dynamic styles with components
  2. Variables don’t cascade anymore, and results are more predictable
  3. No overhead for browsers which already support CSS variables

Disadvantages

  1. Some overhead for browsers which don’t support CSS variables

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:3
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

4reactions
satya164commented, Sep 13, 2017

Crazy experiment

This is just an experiment and I don’t want to add this to linaria.

Babel plugin which converts styled-component like syntax to linaria syntax:

var Title = styled('div')`
  color: ${props => props.color};
  background: ${props => props.background};
  text-align: center;
  font-family: ${font.family};
`;

becomes:

var Title = props => <div
  {...props}
  {...styles(_title, props.className, {
    "--prop-0": (props => props.color)(props),
    "--prop-1": (props => props.background)(props)
  })} />;

var _title = css`
  color: ${"var(--prop-0)"};
  background: ${"var(--prop-1)"};
  text-align: center;
  font-family: ${font.family};
`;

plugin implementation:

export default function(babel) {
  const { types: t } = babel;

  return {
    visitor: {
      TaggedTemplateExpression(path, state) {
        if (t.isCallExpression(path.node.tag) && path.node.tag.callee.name === "styled") {
          const tagName = path.node.tag.arguments[0].value;
          const variableName = path.parentPath.node.id.name;
          const interpolations = {};
          const node = path.node;

          node.tag = t.identifier("css");
          node.quasi.expressions = node.quasi.expressions.map((ex, i) => {
            if (t.isArrowFunctionExpression(ex)) {
              const name = `--prop-${i}`;
              interpolations[name] = ex;
              return t.stringLiteral(`var(${name})`);
            }
            return ex;
          });

          let parent = path.parentPath;

          while (t.isVariableDeclarator(parent) || t.isExportNamedDeclaration(parent.parentPath)) {
            parent = parent.parentPath;
          }
          
          const className = parent.scope.generateUidIdentifier(variableName.toLowerCase()).name;

          parent.insertAfter(t.variableDeclaration("var", [t.variableDeclarator(t.identifier(className), node)]));
          path.replaceWith(
            t.arrowFunctionExpression(
              [t.identifier("props")],
              t.jSXElement(
                t.jSXOpeningElement(
                  t.jSXIdentifier("div"),
                  [
                    t.jSXSpreadAttribute(t.identifier("props")),
                    t.jSXSpreadAttribute(
                      t.callExpression(t.identifier("styles"), [
                        t.identifier(className),
                        t.identifier("props.className"),
                        t.objectExpression(
                          Object.keys(interpolations).map(it =>
                            t.objectProperty(
                              t.stringLiteral(it),
                              t.callExpression(interpolations[it], [t.identifier("props")])
                            )
                          )
                        )
                      ])
                    )
                  ],
                  true
                ),
                null,
                []
              )
            )
          );
        }
      }
    }
  };
}
0reactions
zamotanycommented, Sep 20, 2018

We are not going to support this.

Read more comments on GitHub >

github_iconTop Results From Across the Web

IE11 - does a polyfill / script exist for CSS variables?
I'm developing a webpage in a mixed web browser environment (Chrome/IE11). IE11 doesn't support CSS variables, is there a polyfill or script ...
Read more >
A basic polyfill for CSS Variables/custom-properties - GitHub
This is an attempt at a very basic CSS variables (custom properties) polyfil. In reality this is more of a partial polyfill as...
Read more >
css-vars-ponyfill - npm
Client-side support for CSS custom properties (aka "CSS variables") in legacy and modern browsers. Latest version: 2.4.8, last published: 5 ...
Read more >
CSS Variables Polyfill - CodePen
According to caniuse.com, of current browsers only IE, Edge and Opera Mini do not support CSS variables. This polyfil appears to work on...
Read more >
css-vars-ponyfill - GitHub Pages
css-vars-ponyfill - Client-side support for CSS custom properties (aka "CSS variables") in legacy ... This library is offered as a ponyfill, not a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found