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 })} />;
}
styles calls with variables and assigns an unique ID for each. Something like:js
styles(title, { __id: 'xgfs65d', '--title-color': color });
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.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:js
{ toString() { return 'xgsf64f'; }, templates: ['.xgsf64f { color: var(--title-color); }'] }
color: var(--title-color); color: blue should be considered as color: blue.names function should be updated to look for these objects and use toString when necessarystyles 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.preval-extract extract plugin and includes a plugin which just converts css to css.named calls.const title = css`
color: var(--title-color);
font-family: sans-serif;
&:hover {
color: var(--title-color-active);
}
@media (max-width: 600px) {
color: var(--title-color-small);
}
`;
is transpiled into something like:
const title = {
toString() { return 'title_456fgf' },
template: '.title_jhdsj5h { color: var(--title-color); }.title_jhdsj5h:hover { color: var(--title-color-active); }@media (max-width: 600px) { color: var(--title-color-small); }';
};
basically, the dynamic part is extracted from the static parts after pre-compiling and stored as a template. this makes it much more performant and super easy to do dynamic styling and also we don't have to deal with cors issues.
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,
[]
)
)
);
}
}
}
};
}
If anyone is interested in implementing or discussing, I updated the proposal with the steps needed to achieve this.
I was trying to do something similar with this plugin custom-properties
If your open to a runtime-only variant, you could create a queue of rules that use variables and process them at the end of a parse if the css spec allows you to use variables before you declare them.
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.
Do you have a test case i can use to see this.
If your open to a runtime-only variant
I think we would really prefer to have no runtime as that's the main idea behind the library. Curious to see your approach though.
Do you have a test case i can use to see this.
Not right now, but I'll prepare a test case.
We are not going to support this.
Most helpful comment
Crazy experiment
This is just an experiment and I don't want to add this to linaria.
Babel plugin which converts
styled-componentlike syntax to linaria syntax:becomes:
plugin implementation: