Skip to content

Commit

Permalink
SX: Vendor prefixes via Autoprefixer
Browse files Browse the repository at this point in the history
Vendor prefixes are automatically added into the generated CSS.
  • Loading branch information
michalsanger committed Dec 6, 2020
1 parent e565bb0 commit 96daef5
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/example-relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@adeira/relay": "^2.2.0",
"@adeira/relay-runtime": "^0.18.0",
"@adeira/relay-utils": "0.11.0",
"@adeira/sx": "^0.21.0",
"@adeira/sx": "^0.22.0",
"@artsy/fresnel": "^1.3.0",
"apollo-server-micro": "^2.19.0",
"cors": "^2.8.5",
Expand Down
2 changes: 1 addition & 1 deletion src/sx-tailwind-website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dependencies": {
"@adeira/js": "^1.2.4",
"@adeira/monorepo-utils": "^0.9.0",
"@adeira/sx": "^0.21.0",
"@adeira/sx": "^0.22.0",
"@adeira/sx-tailwind": "^0.11.0",
"hast-util-to-html": "^7.1.2",
"next": "^10.0.3",
Expand Down
6 changes: 6 additions & 0 deletions src/sx-tailwind-website/src/TailwindCss/Gradients.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Link from '../components/Link';
import StartingColor, { code as codeStartingColor } from './gradients/StartingColor';
import EndingColor, { code as codeEndingColor } from './gradients/EndingColor';
import MiddleColor, { code as codeMiddleColor } from './gradients/MiddleColor';
import TextBackground, { code as codeTextBackground } from './gradients/TextBackground';
import Showcase from '../components/Showcase';

export default function Gradients(): Node {
Expand Down Expand Up @@ -36,6 +37,11 @@ export default function Gradients(): Node {
<Showcase code={codeMiddleColor}>
<MiddleColor />
</Showcase>

<H2>Text background</H2>
<Showcase code={codeTextBackground}>
<TextBackground />
</Showcase>
</Layout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @flow

import type { Node } from 'react';
import { tailwind } from '@adeira/sx-tailwind';

export default function TextBackground(): Node {
return (
<h1 className={tailwind('text-6xl font-bold')}>
<span
className={tailwind(
'bg-clip-text text-transparent bg-gradient-to-r from-teal-400 to-blue-500',
)}
>
Greetings from SX Tailwind
</span>
</h1>
);
}

export const code = `<h1 className={tailwind('text-6xl font-bold')}>
<span className={tailwind('bg-clip-text text-transparent bg-gradient-to-r from-teal-400 to-blue-500')}>
Greetings from SX Tailwind
</span>
</h1>`;
2 changes: 1 addition & 1 deletion src/sx-tailwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"dependencies": {
"@adeira/js": "^1.3.0",
"@adeira/signed-source": "^1.0.0",
"@adeira/sx": "^0.21.0",
"@adeira/sx": "^0.22.0",
"@babel/runtime": "^7.12.5",
"change-case": "^4.1.2",
"fast-levenshtein": "^3.0.0",
Expand Down
24 changes: 24 additions & 0 deletions src/sx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ In conventional applications, CSS rules are duplicated throughout the stylesheet
- [Keyframes](#keyframes)
- [Composability and customizability](#composability-and-customizability)
- [Precise Flow types](#precise-flow-types)
- [Autoprefixer support](#autoprefixer-support)
- [Production usage considerations](#production-usage-considerations)
- [Server-side rendering](#server-side-rendering)
- [Architecture](#architecture)
Expand Down Expand Up @@ -320,6 +321,29 @@ const styles = sx.create({

Sometimes it's hard or even impossible to have sound types for some CSS properties/values though. In such case, we choose the unsound strategy. Typical example of such unsoundness is when it comes to complex media queries or CSS variables - they cannot be statically analyzed. These situations are usually complemented with runtime checks and eventually even Eslint rules.
### Autoprefixer support
Vendor prefixes are automatically added whenever needed.
```js
const styles = sx.create({
title: {
backgroundClip: 'text',
},
});
```
The example above will generate CSS like this:
```css
._2D4soO {
-webkit-background-clip: text;
background-clip: text;
}
```
This feature is provided by [Autoprefixer](https://github.com/postcss/autoprefixer) so [you can specify](https://github.com/postcss/autoprefixer#browsers) the browsers you want to support.
## Production usage considerations
1. SX does not include any CSS reset or CSS normalization. It's because we couldn't decide which strategy would be the best. We concluded that each user should choose their own strategy (either reset, normalizer or nothing) alongside SX.
Expand Down
4 changes: 3 additions & 1 deletion src/sx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"homepage": "https://github.com/adeira/universe/tree/master/src/sx",
"license": "MIT",
"private": false,
"version": "0.21.0",
"version": "0.22.0",
"main": "index",
"sideEffects": false,
"module": false,
Expand All @@ -14,11 +14,13 @@
"@adeira/murmur-hash": "^1.0.0",
"@adeira/signed-source": "^1.0.0",
"@babel/runtime": "^7.12.5",
"autoprefixer": "^10.0.4",
"change-case": "^4.1.2",
"css-tree": "^1.1.2",
"fast-levenshtein": "^3.0.0",
"json-stable-stringify": "^1.0.1",
"mdn-data": "^2.0.14",
"postcss": "^8.1.14",
"prettier": "^2.2.1"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/sx/src/__tests__/SxDomTests.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ it('works with keyframes', () => {
<style
data-adeira-sx="true"
>
.P4y5l{animation-name:_2rMlJa}.HDQox{animation-duration:2s}@keyframes _2rMlJa {from {opacity:0;}to {opacity:1;}}
.P4y5l{-webkit-animation-name:_2rMlJa;animation-name:_2rMlJa}.HDQox{-webkit-animation-duration:2s;animation-duration:2s}@-webkit-keyframes _2rMlJa {from {opacity:0;}to {opacity:1;}}@keyframes _2rMlJa {from {opacity:0;}to {opacity:1;}}
</style>
`);
});
45 changes: 43 additions & 2 deletions src/sx/src/__tests__/__snapshots__/fixtures.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ exports[`matches expected output: @supports.js 1`] = `
color: #f00;
}
}
@supports (transform-style: preserve) or (-moz-transform-style: preserve) or
(-o-transform-style: preserve) or (-webkit-transform-style: preserve) {
@supports (transform-style: preserve) or (-webkit-transform-style: preserve) {
._2gYAIc._2gYAIc {
color: #f00;
}
Expand Down Expand Up @@ -111,6 +110,27 @@ exports[`matches expected output: @unknown.js 1`] = `
Invariant Violation: Unsupported rule "@unknown"
`;

exports[`matches expected output: autoprefix.js 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
{
"title": {
"backgroundClip": "text"
}
}
~~~~~~~~~~ OUTPUT ~~~~~~~~~~
._2D4soO {
-webkit-background-clip: text;
background-clip: text;
}
~~~~~~~~~~ USAGE ~~~~~~~~~~
className={styles('title')}
↓ ↓ ↓
class="_2D4soO"
`;

exports[`matches expected output: keyframes-from-to.js 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
{
Expand All @@ -120,8 +140,17 @@ exports[`matches expected output: keyframes-from-to.js 1`] = `
}
~~~~~~~~~~ OUTPUT ~~~~~~~~~~
.P4y5l {
-webkit-animation-name: _2rMlJa;
animation-name: _2rMlJa;
}
@-webkit-keyframes _2rMlJa {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes _2rMlJa {
from {
opacity: 0;
Expand All @@ -148,8 +177,20 @@ exports[`matches expected output: keyframes-percentage.js 1`] = `
}
~~~~~~~~~~ OUTPUT ~~~~~~~~~~
._4BANPA {
-webkit-animation-name: gg1lu;
animation-name: gg1lu;
}
@-webkit-keyframes gg1lu {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
25% {
opacity: 0.7;
}
}
@keyframes gg1lu {
0% {
opacity: 0;
Expand Down
49 changes: 49 additions & 0 deletions src/sx/src/__tests__/autoprefixRuntimeStyles.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// @flow

/* global document */

import { render } from '@testing-library/react';

import sx from '../../index';

it('autoprefix runtime styles', () => {
render(<style data-adeira-sx />);

const styleTag = document.querySelector('style');
expect(styleTag?.sheet?.cssRules).toHaveLength(0);

sx.create({
test: {
backgroundClip: 'text',
},
});

expect(styleTag?.sheet?.cssRules).toHaveLength(1);
expect(styleTag?.sheet?.cssRules).toMatchInlineSnapshot(`
Array [
CSSStyleRule {
"__ends": 59,
"__starts": 0,
"parentRule": null,
"parentStyleSheet": CSSStyleSheet {
"cssRules": [Circular],
"parentStyleSheet": null,
},
"selectorText": "._2D4soO",
"style": CSSStyleDeclaration {
"-webkit-background-clip": "text",
"0": "-webkit-background-clip",
"1": "background-clip",
"__starts": 8,
"_importants": Object {
"-webkit-background-clip": "",
"background-clip": "",
},
"background-clip": "text",
"length": 2,
"parentRule": [Circular],
},
},
]
`);
});
9 changes: 9 additions & 0 deletions src/sx/src/__tests__/fixtures/autoprefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @flow

import type { SheetDefinitions } from '../../create';

export default ({
title: {
backgroundClip: 'text',
},
}: SheetDefinitions);
12 changes: 9 additions & 3 deletions src/sx/src/injectRuntimeStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
/* global document */

import { invariant, warning } from '@adeira/js';
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

import StyleCollectorAtNode from './StyleCollectorAtNode';
import StyleCollectorNode from './StyleCollectorNode';
Expand Down Expand Up @@ -65,6 +67,7 @@ export function injectRuntimeKeyframes(css: string, name: string) {
// https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model
export default function injectRuntimeStyles(styleBuffer: StyleBufferType) {
const styleSheet = getStyleTag();
const prefixer = postcss([autoprefixer]);

const matchFunction = (node) => (cssRule) => {
if (cssRule.type === CSSRule.STYLE_RULE) {
Expand All @@ -82,16 +85,19 @@ export default function injectRuntimeStyles(styleBuffer: StyleBufferType) {
if (node instanceof StyleCollectorNode) {
if (hasStyleRule(matchFunction(node)) === false) {
// apply missing styles
styleSheet.insertRule(node.printNodes().join(''), insertIndex);
const rule = prefixer.process(node.printNodes().join('')).css;
styleSheet.insertRule(rule, insertIndex);
}
} else if (node instanceof StyleCollectorAtNode) {
// TODO: make sure we are not adding already added styles (?)
styleSheet.insertRule(node.printNodes().join(''), insertIndex);
const rule = prefixer.process(node.printNodes().join('')).css;
styleSheet.insertRule(rule, insertIndex);
} else if (node instanceof StyleCollectorPseudoNode) {
const nodes = node.printNodes();
for (const nodeElement of nodes) {
// TODO: make sure we are not adding already added styles (?)
styleSheet.insertRule(nodeElement, insertIndex);
const rule = prefixer.process(nodeElement).css;
styleSheet.insertRule(rule, insertIndex);
}
} else {
warning(false, 'Node not supported in runtime styles: %j', node);
Expand Down
6 changes: 5 additions & 1 deletion src/sx/src/renderPageWithSX.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @flow

import type { Node } from 'react';
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

import StyleCollector from './StyleCollector';

Expand All @@ -14,14 +16,16 @@ type RenderPageResult = {|
export default function renderPageWithSX(renderPage: () => any): RenderPageResult {
const html = renderPage();

const cssStyles = postcss([autoprefixer]).process(StyleCollector.print()).css;

return {
...html,
styles: [
// We need to render this as html, else things like `content: "ok"` will be rendered as `content: &quot;ok&quot;`, and it is not valid css
<style
key="adeira-sx"
data-adeira-sx={true}
dangerouslySetInnerHTML={{ __html: StyleCollector.print() }}
dangerouslySetInnerHTML={{ __html: cssStyles }}
/>,
],
};
Expand Down
Loading

0 comments on commit 96daef5

Please sign in to comment.