Skip to content

Commit

Permalink
Merge pull request #241 from airbnb/postcss-linaria-preprocessor
Browse files Browse the repository at this point in the history
Replace Linaria preprocessor with postcss to fix an error of postcss-transform-unit
  • Loading branch information
malash authored May 29, 2024
2 parents b9c777f + 1dbfdc2 commit 2e42c6d
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 13 deletions.
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
"postcss-nested": "^5.0.6",
"postcss-preset-env": "^7.0.2",
"postcss-reporter": "^7.0.4",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.19.0",
"stylis": "^3.5.4",
"terser-webpack-plugin": "^5.3.0",
"thread-loader": "^3.0.4",
"tslib": "^2.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`postcss-linaria-preprocessor :global() selector 1`] = `
"
.a {
color: red
}
page {
width: 50vw;
}
"
`;

exports[`postcss-linaria-preprocessor escape breaking control characters 1`] = `
"
.a {
content: '\\\\feff';
}
"
`;

exports[`postcss-linaria-preprocessor keyframes rename 1`] = `
"
.a {
animation: 1s ease 1 backwards normal zoomIn-a;
@keyframes zoomIn-a {
from {
opacity: 0;
transform: scale(0.94);
}
50% {
opacity: var(--opacity);
}
to {
transform: scale(var(--scale));
}
}
}
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`postcss-transform-unit px to rpx 1`] = `
"
.a {
width: 20rpx;
height: 20rpx;
font-size: 30px;
}
"
`;

exports[`postcss-transform-unit rpx to px 1`] = `
"
.a {
width: 10px;
height: 10px;
font-size: 30rpx;
}
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import postcss from 'postcss';

const transform = async (css: string) => {
// eslint-disable-next-line global-require
const result = await postcss([require('../postcssLinariaPreprocessor')()]).process(css, {
from: '/path/to/file.css',
});
return result.css;
};

describe('postcss-linaria-preprocessor', () => {
test(':global() selector', () => {
const css = `
.a {
color: red;
:global() {
page {
width: 50vw;
}
}
}
`;
expect(transform(css)).resolves.toMatchSnapshot();
});

test('keyframes rename', () => {
const css = `
.a {
animation: 1s ease 1 backwards normal zoomIn;
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.94);
}
50% {
opacity: var(--opacity);
}
to {
transform: scale(var(--scale));
}
}
}
`;
expect(transform(css)).resolves.toMatchSnapshot();
});

test('escape breaking control characters', () => {
const css = `
.a {
content: '\feff';
}
`;
expect(transform(css)).resolves.toMatchSnapshot();
});
});
49 changes: 49 additions & 0 deletions packages/cli/src/config/__tests__/postcssTransformUnit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import postcss from 'postcss';

describe('postcss-transform-unit', () => {
test('px to rpx', async () => {
const css = `
.a {
width: 10px;
height: 20rpx;
font-size: 30px; /* no */
}
`;

const result = await postcss([
// eslint-disable-next-line global-require
require('../postcssTransformUnit')({
divisor: 1,
multiple: 2,
sourceUnit: 'px',
targetUnit: 'rpx',
}),
]).process(css, {
from: '/path/to/file.css',
});
expect(result.css).toMatchSnapshot();
});

test('rpx to px', async () => {
const css = `
.a {
width: 10px;
height: 20rpx;
font-size: 30rpx; /* no */
}
`;

const result = await postcss([
// eslint-disable-next-line global-require
require('../postcssTransformUnit')({
divisor: 2,
multiple: 1,
sourceUnit: 'rpx',
targetUnit: 'px',
}),
]).process(css, {
from: '/path/to/file.css',
});
expect(result.css).toMatchSnapshot();
});
});
3 changes: 2 additions & 1 deletion packages/cli/src/config/postcssConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ const getTransformUnitsPlugin = (unit: TransformUnit) => {
// to improve PostCSS performance, we should always use `require(name)(options)` rather than `[name, options]`
// also we should set `postcssOptions.config` to `false` to avoid loading any `postcss.config.js`
// TODO: hope PostCSS could fix this issue https://github.com/csstools/postcss-preset-env/issues/232#issuecomment-992263741
export default ({ unit }: { unit: TransformUnit }) => {
export default ({ unit, linaria }: { unit: TransformUnit; linaria: boolean }) => {
const transformUnitPlugin = getTransformUnitsPlugin(unit);

return {
plugins: [
require('postcss-each')(),
...(linaria ? [require('./postcssLinariaPreprocessor')()] : []),
// TODO: `postcss-nesting` from `postcss-preset-env` output `:is` pseudo-class that Mini Programs don't support.
// We have to use `postcss-nested` manually before them to prevent `:is` being created.
require('postcss-nested')(),
Expand Down
91 changes: 91 additions & 0 deletions packages/cli/src/config/postcssLinariaPreprocessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable import/no-import-module-exports */
import type { PluginCreator } from 'postcss';
import valueParser from 'postcss-value-parser';

const reserved = [
'none',
'inherited',
'initial',
'unset',
/* single-timing-function */
'linear',
'ease',
'ease-in',
'ease-in-out',
'ease-out',
'step-start',
'step-end',
'start',
'end',
/* single-animation-iteration-count */
'infinite',
/* single-animation-direction */
'normal',
'reverse',
'alternate',
'alternate-reverse',
/* single-animation-fill-mode */
'forwards',
'backwards',
'both',
/* single-animation-play-state */
'running',
'paused',
];

interface Options {}

const postcssLinariaPreprocessor: PluginCreator<Options> = () => ({
postcssPlugin: 'postcss-linaria-preprocessor',
Once(root, postcss) {
const nodes = [...root.nodes];
for (const node of nodes) {
if (node.type === 'rule' && node.selector.startsWith('.')) {
// use unique keyframe name to avoid conflict
// inspired from https://github.com/css-modules/postcss-icss-keyframes/blob/5f890e4068820daa80025d88a4f750a3a085dcc8/src/index.js
const keyframeNameMapping = new Map<string, string>();
node.walkAtRules(/keyframes$/, atRule => {
const name = atRule.params;
if (reserved.includes(name)) {
postcss.result.warn(`Unable to use reserve '${name}' animation name`, {
node: atRule,
});

return;
}
const newName = `${name}-${node.selector.replace(/^\./, '')}`;
keyframeNameMapping.set(name, newName);
atRule.params = newName;
});
node.walkDecls(/animation$|animation-name$/, decl => {
const parsed = valueParser(decl.value);
for (const item of parsed.nodes) {
if (item.type === 'word' && keyframeNameMapping.has(item.value)) {
item.value = keyframeNameMapping.get(item.value)!;
}
}
decl.value = parsed.toString();
});

// extract the global rule to the top of the root
node.walkRules(/^:global\(\)$/, globalRule => {
globalRule.remove();
for (const globalNode of globalRule.nodes) {
root.insertAfter(node, globalNode);
}
});
}
}
},
Declaration(decl) {
// escape breaking control characters
// from: https://github.com/thysultan/stylis/blob/v3.5.4/tests/spec.js#L113C3-L116
if (decl.value.match(/[\0\r\f]/)) {
decl.value = decl.value.replace(/\0/g, '\\0').replace(/\r/g, '\\r').replace(/\f/g, '\\f');
}
},
});

postcssLinariaPreprocessor.postcss = true;

module.exports = postcssLinariaPreprocessor;
5 changes: 3 additions & 2 deletions packages/cli/src/config/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const getWebpackConfig = ({
configFile: require.resolve('./linaria.config'),
sourceMap: true,
cacheProvider: require.resolve('./linariaFileCache'),
preprocessor: 'none',
babelOptions: {
// always use internal babel.config.js file
configFile: require.resolve('./babel.config'),
Expand Down Expand Up @@ -232,7 +233,7 @@ export const getWebpackConfig = ({
implementation: require.resolve('postcss'),
postcssOptions: {
config: false,
...postcssConfig({ unit: cssUnit }),
...postcssConfig({ unit: cssUnit, linaria: false }),
},
},
},
Expand Down Expand Up @@ -269,7 +270,7 @@ export const getWebpackConfig = ({
implementation: require.resolve('postcss'),
postcssOptions: {
config: false,
...postcssConfig({ unit: cssUnit }),
...postcssConfig({ unit: cssUnit, linaria: true }),
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const main = css`
const todosHeader = css`
padding: 10px 0;
width: 100%;
font-size: 100px;
font-size: 203rpx;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
Expand Down
10 changes: 1 addition & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2857,7 +2857,7 @@
"@docusaurus/theme-search-algolia" "2.4.3"
"@docusaurus/types" "2.4.3"

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

"react-loadable@npm:@docusaurus/[email protected]":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
dependencies:
"@types/react" "*"
prop-types "^15.6.2"

react-reconciler@^0.26.2:
version "0.26.2"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91"
Expand Down

0 comments on commit 2e42c6d

Please sign in to comment.