Skip to content

Commit 2e42c6d

Browse files
authored
Merge pull request #241 from airbnb/postcss-linaria-preprocessor
Replace Linaria preprocessor with postcss to fix an error of postcss-transform-unit
2 parents b9c777f + 1dbfdc2 commit 2e42c6d

File tree

10 files changed

+269
-13
lines changed

10 files changed

+269
-13
lines changed

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
"postcss-nested": "^5.0.6",
4242
"postcss-preset-env": "^7.0.2",
4343
"postcss-reporter": "^7.0.4",
44+
"postcss-value-parser": "^4.2.0",
4445
"resolve": "^1.19.0",
46+
"stylis": "^3.5.4",
4547
"terser-webpack-plugin": "^5.3.0",
4648
"thread-loader": "^3.0.4",
4749
"tslib": "^2.3.0",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`postcss-linaria-preprocessor :global() selector 1`] = `
4+
"
5+
.a {
6+
color: red
7+
}
8+
page {
9+
width: 50vw;
10+
}
11+
"
12+
`;
13+
14+
exports[`postcss-linaria-preprocessor escape breaking control characters 1`] = `
15+
"
16+
.a {
17+
content: '\\\\feff';
18+
}
19+
"
20+
`;
21+
22+
exports[`postcss-linaria-preprocessor keyframes rename 1`] = `
23+
"
24+
.a {
25+
animation: 1s ease 1 backwards normal zoomIn-a;
26+
@keyframes zoomIn-a {
27+
from {
28+
opacity: 0;
29+
transform: scale(0.94);
30+
}
31+
32+
50% {
33+
opacity: var(--opacity);
34+
}
35+
36+
to {
37+
transform: scale(var(--scale));
38+
}
39+
}
40+
}
41+
"
42+
`;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`postcss-transform-unit px to rpx 1`] = `
4+
"
5+
.a {
6+
width: 20rpx;
7+
height: 20rpx;
8+
font-size: 30px;
9+
}
10+
"
11+
`;
12+
13+
exports[`postcss-transform-unit rpx to px 1`] = `
14+
"
15+
.a {
16+
width: 10px;
17+
height: 10px;
18+
font-size: 30rpx;
19+
}
20+
"
21+
`;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import postcss from 'postcss';
2+
3+
const transform = async (css: string) => {
4+
// eslint-disable-next-line global-require
5+
const result = await postcss([require('../postcssLinariaPreprocessor')()]).process(css, {
6+
from: '/path/to/file.css',
7+
});
8+
return result.css;
9+
};
10+
11+
describe('postcss-linaria-preprocessor', () => {
12+
test(':global() selector', () => {
13+
const css = `
14+
.a {
15+
color: red;
16+
:global() {
17+
page {
18+
width: 50vw;
19+
}
20+
}
21+
}
22+
`;
23+
expect(transform(css)).resolves.toMatchSnapshot();
24+
});
25+
26+
test('keyframes rename', () => {
27+
const css = `
28+
.a {
29+
animation: 1s ease 1 backwards normal zoomIn;
30+
@keyframes zoomIn {
31+
from {
32+
opacity: 0;
33+
transform: scale(0.94);
34+
}
35+
36+
50% {
37+
opacity: var(--opacity);
38+
}
39+
40+
to {
41+
transform: scale(var(--scale));
42+
}
43+
}
44+
}
45+
`;
46+
expect(transform(css)).resolves.toMatchSnapshot();
47+
});
48+
49+
test('escape breaking control characters', () => {
50+
const css = `
51+
.a {
52+
content: '\feff';
53+
}
54+
`;
55+
expect(transform(css)).resolves.toMatchSnapshot();
56+
});
57+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import postcss from 'postcss';
2+
3+
describe('postcss-transform-unit', () => {
4+
test('px to rpx', async () => {
5+
const css = `
6+
.a {
7+
width: 10px;
8+
height: 20rpx;
9+
font-size: 30px; /* no */
10+
}
11+
`;
12+
13+
const result = await postcss([
14+
// eslint-disable-next-line global-require
15+
require('../postcssTransformUnit')({
16+
divisor: 1,
17+
multiple: 2,
18+
sourceUnit: 'px',
19+
targetUnit: 'rpx',
20+
}),
21+
]).process(css, {
22+
from: '/path/to/file.css',
23+
});
24+
expect(result.css).toMatchSnapshot();
25+
});
26+
27+
test('rpx to px', async () => {
28+
const css = `
29+
.a {
30+
width: 10px;
31+
height: 20rpx;
32+
font-size: 30rpx; /* no */
33+
}
34+
`;
35+
36+
const result = await postcss([
37+
// eslint-disable-next-line global-require
38+
require('../postcssTransformUnit')({
39+
divisor: 2,
40+
multiple: 1,
41+
sourceUnit: 'rpx',
42+
targetUnit: 'px',
43+
}),
44+
]).process(css, {
45+
from: '/path/to/file.css',
46+
});
47+
expect(result.css).toMatchSnapshot();
48+
});
49+
});

packages/cli/src/config/postcssConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ const getTransformUnitsPlugin = (unit: TransformUnit) => {
3333
// to improve PostCSS performance, we should always use `require(name)(options)` rather than `[name, options]`
3434
// also we should set `postcssOptions.config` to `false` to avoid loading any `postcss.config.js`
3535
// TODO: hope PostCSS could fix this issue https://github.com/csstools/postcss-preset-env/issues/232#issuecomment-992263741
36-
export default ({ unit }: { unit: TransformUnit }) => {
36+
export default ({ unit, linaria }: { unit: TransformUnit; linaria: boolean }) => {
3737
const transformUnitPlugin = getTransformUnitsPlugin(unit);
3838

3939
return {
4040
plugins: [
4141
require('postcss-each')(),
42+
...(linaria ? [require('./postcssLinariaPreprocessor')()] : []),
4243
// TODO: `postcss-nesting` from `postcss-preset-env` output `:is` pseudo-class that Mini Programs don't support.
4344
// We have to use `postcss-nested` manually before them to prevent `:is` being created.
4445
require('postcss-nested')(),
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/* eslint-disable import/no-import-module-exports */
2+
import type { PluginCreator } from 'postcss';
3+
import valueParser from 'postcss-value-parser';
4+
5+
const reserved = [
6+
'none',
7+
'inherited',
8+
'initial',
9+
'unset',
10+
/* single-timing-function */
11+
'linear',
12+
'ease',
13+
'ease-in',
14+
'ease-in-out',
15+
'ease-out',
16+
'step-start',
17+
'step-end',
18+
'start',
19+
'end',
20+
/* single-animation-iteration-count */
21+
'infinite',
22+
/* single-animation-direction */
23+
'normal',
24+
'reverse',
25+
'alternate',
26+
'alternate-reverse',
27+
/* single-animation-fill-mode */
28+
'forwards',
29+
'backwards',
30+
'both',
31+
/* single-animation-play-state */
32+
'running',
33+
'paused',
34+
];
35+
36+
interface Options {}
37+
38+
const postcssLinariaPreprocessor: PluginCreator<Options> = () => ({
39+
postcssPlugin: 'postcss-linaria-preprocessor',
40+
Once(root, postcss) {
41+
const nodes = [...root.nodes];
42+
for (const node of nodes) {
43+
if (node.type === 'rule' && node.selector.startsWith('.')) {
44+
// use unique keyframe name to avoid conflict
45+
// inspired from https://github.com/css-modules/postcss-icss-keyframes/blob/5f890e4068820daa80025d88a4f750a3a085dcc8/src/index.js
46+
const keyframeNameMapping = new Map<string, string>();
47+
node.walkAtRules(/keyframes$/, atRule => {
48+
const name = atRule.params;
49+
if (reserved.includes(name)) {
50+
postcss.result.warn(`Unable to use reserve '${name}' animation name`, {
51+
node: atRule,
52+
});
53+
54+
return;
55+
}
56+
const newName = `${name}-${node.selector.replace(/^\./, '')}`;
57+
keyframeNameMapping.set(name, newName);
58+
atRule.params = newName;
59+
});
60+
node.walkDecls(/animation$|animation-name$/, decl => {
61+
const parsed = valueParser(decl.value);
62+
for (const item of parsed.nodes) {
63+
if (item.type === 'word' && keyframeNameMapping.has(item.value)) {
64+
item.value = keyframeNameMapping.get(item.value)!;
65+
}
66+
}
67+
decl.value = parsed.toString();
68+
});
69+
70+
// extract the global rule to the top of the root
71+
node.walkRules(/^:global\(\)$/, globalRule => {
72+
globalRule.remove();
73+
for (const globalNode of globalRule.nodes) {
74+
root.insertAfter(node, globalNode);
75+
}
76+
});
77+
}
78+
}
79+
},
80+
Declaration(decl) {
81+
// escape breaking control characters
82+
// from: https://github.com/thysultan/stylis/blob/v3.5.4/tests/spec.js#L113C3-L116
83+
if (decl.value.match(/[\0\r\f]/)) {
84+
decl.value = decl.value.replace(/\0/g, '\\0').replace(/\r/g, '\\r').replace(/\f/g, '\\f');
85+
}
86+
},
87+
});
88+
89+
postcssLinariaPreprocessor.postcss = true;
90+
91+
module.exports = postcssLinariaPreprocessor;

packages/cli/src/config/webpack.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export const getWebpackConfig = ({
193193
configFile: require.resolve('./linaria.config'),
194194
sourceMap: true,
195195
cacheProvider: require.resolve('./linariaFileCache'),
196+
preprocessor: 'none',
196197
babelOptions: {
197198
// always use internal babel.config.js file
198199
configFile: require.resolve('./babel.config'),
@@ -232,7 +233,7 @@ export const getWebpackConfig = ({
232233
implementation: require.resolve('postcss'),
233234
postcssOptions: {
234235
config: false,
235-
...postcssConfig({ unit: cssUnit }),
236+
...postcssConfig({ unit: cssUnit, linaria: false }),
236237
},
237238
},
238239
},
@@ -269,7 +270,7 @@ export const getWebpackConfig = ({
269270
implementation: require.resolve('postcss'),
270271
postcssOptions: {
271272
config: false,
272-
...postcssConfig({ unit: cssUnit }),
273+
...postcssConfig({ unit: cssUnit, linaria: true }),
273274
},
274275
},
275276
},

packages/demo-todomvc-linaria/src/components/MainSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const main = css`
1616
const todosHeader = css`
1717
padding: 10px 0;
1818
width: 100%;
19-
font-size: 100px;
19+
font-size: 203rpx;
2020
font-weight: 100;
2121
text-align: center;
2222
color: rgba(175, 47, 47, 0.15);

yarn.lock

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,7 +2857,7 @@
28572857
"@docusaurus/theme-search-algolia" "2.4.3"
28582858
"@docusaurus/types" "2.4.3"
28592859

2860-
"@docusaurus/[email protected]":
2860+
"@docusaurus/[email protected]", "react-loadable@npm:@docusaurus/[email protected]":
28612861
version "5.5.2"
28622862
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
28632863
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
@@ -14224,14 +14224,6 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
1422414224
dependencies:
1422514225
"@babel/runtime" "^7.10.3"
1422614226

14227-
"react-loadable@npm:@docusaurus/[email protected]":
14228-
version "5.5.2"
14229-
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
14230-
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
14231-
dependencies:
14232-
"@types/react" "*"
14233-
prop-types "^15.6.2"
14234-
1423514227
react-reconciler@^0.26.2:
1423614228
version "0.26.2"
1423714229
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91"

0 commit comments

Comments
 (0)