Skip to content

Commit

Permalink
[pigment-css][react] Implement sx transform for system components (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
brijeshb42 authored Apr 17, 2024
1 parent 48faf63 commit ed86fd6
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 154 deletions.
1 change: 1 addition & 0 deletions packages/pigment-css-react/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/processors/
/utils/
LICENSE
/private-runtime/
2 changes: 1 addition & 1 deletion packages/pigment-css-react/exports/sx-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../utils/pre-linaria-plugin').babelPlugin;
exports.default = require('../utils/sx-plugin').babelPlugin;
16 changes: 12 additions & 4 deletions packages/pigment-css-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
"@emotion/styled": "^11.11.5",
"@mui/system": "^6.0.0-alpha.1",
"@mui/utils": "^6.0.0-alpha.1",
"@wyw-in-js/processor-utils": "^0.5.0",
"@wyw-in-js/shared": "^0.5.0",
"@wyw-in-js/transform": "^0.5.0",
"@wyw-in-js/processor-utils": "^0.5.1",
"@wyw-in-js/shared": "^0.5.1",
"@wyw-in-js/transform": "^0.5.1",
"clsx": "^2.1.0",
"cssesc": "^3.0.0",
"csstype": "^3.1.3",
Expand Down Expand Up @@ -80,7 +80,6 @@
"wyw-in-js": {
"tags": {
"styled": "./exports/styled.js",
"default": "./exports/styled.js",
"sx": "./exports/sx.js",
"keyframes": "./exports/keyframes.js",
"generateAtomics": "./exports/generateAtomics.js",
Expand Down Expand Up @@ -166,6 +165,15 @@
},
"require": "./build/RtlProvider.js",
"default": "./build/RtlProvider.js"
},
"./private-runtime": {
"types": "./private-runtime/index.d.ts",
"import": {
"types": "./private-runtime/index.d.mts",
"default": "./private-runtime/index.mjs"
},
"require": "./private-runtime/index.js",
"default": "./private-runtime/index.js"
}
},
"nx": {
Expand Down
32 changes: 32 additions & 0 deletions packages/pigment-css-react/src/private-runtime/ForwardSx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import clsx from 'clsx';

function useSx(sx, className, style) {
const sxClass = typeof sx === 'string' ? sx : sx?.className;
const sxVars = sx && typeof sx !== 'string' ? sx.vars : undefined;
const varStyles = {};

if (sxVars) {
Object.entries(sxVars).forEach(([cssVariable, [value, isUnitLess]]) => {
if (typeof value === 'string' || isUnitLess) {
varStyles[`--${cssVariable}`] = value;
} else {
varStyles[`--${cssVariable}`] = `${value}px`;
}
});
}

return {
className: clsx(sxClass, className),
style: {
...varStyles,
...style,
},
};
}

/* eslint-disable-next-line react/prop-types */
export const ForwardSx = React.forwardRef(({ sx, sxComponent, className, style, ...rest }, ref) => {
const Component = sxComponent;
return <Component ref={ref} {...rest} {...useSx(sx, className, style)} />;
});
1 change: 1 addition & 0 deletions packages/pigment-css-react/src/private-runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ForwardSx';
82 changes: 0 additions & 82 deletions packages/pigment-css-react/src/utils/pre-linaria-plugin.ts

This file was deleted.

166 changes: 166 additions & 0 deletions packages/pigment-css-react/src/utils/sx-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { addNamed } from '@babel/helper-module-imports';
import { declare } from '@babel/helper-plugin-utils';
import { NodePath } from '@babel/core';
import * as Types from '@babel/types';

import { sxPropConverter } from './sxPropConverter';

function convertJsxMemberExpressionToMemberExpression(
t: typeof Types,
nodePath: NodePath<Types.JSXMemberExpression>,
): Types.MemberExpression {
const object = nodePath.get('object');
const property = nodePath.get('property');

if (object.isJSXMemberExpression()) {
return t.memberExpression(
convertJsxMemberExpressionToMemberExpression(t, object),
t.identifier(property.node.name),
);
}
return t.memberExpression(
t.identifier((object.node as Types.JSXIdentifier).name),
t.identifier(property.node.name),
);
}

function wrapWithSxComponent(
t: typeof Types,
tagNamePath: NodePath<Types.JSXIdentifier | Types.JSXMemberExpression | Types.JSXNamespacedName>,
sxComponentName: string,
) {
const sxComponent = addNamed(
tagNamePath,
sxComponentName,
`${process.env.PACKAGE_NAME}/private-runtime`,
);
const jsxElement = tagNamePath.findParent((p) => p.isJSXElement());
if (!jsxElement?.isJSXElement()) {
return;
}
const component = t.jsxIdentifier(sxComponent.name);

const newChildren = (jsxElement.get('children') ?? []).map((child) => child.node);
let sxComponentValue: Types.Identifier | Types.MemberExpression | null = null;

if (tagNamePath.isJSXIdentifier()) {
sxComponentValue = t.identifier(tagNamePath.node.name);
} else if (tagNamePath.isJSXMemberExpression()) {
sxComponentValue = convertJsxMemberExpressionToMemberExpression(t, tagNamePath);
}

const newElement = t.jsxElement(
t.jsxOpeningElement(
component,
[
t.jsxAttribute(
t.jsxIdentifier('sxComponent'),
t.jsxExpressionContainer(sxComponentValue ?? t.nullLiteral()),
),
...jsxElement
.get('openingElement')
.get('attributes')
.map((attr) => attr.node),
],
!newChildren.length,
),
newChildren.length ? t.jsxClosingElement(component) : null,
newChildren,
!newChildren.length,
);
jsxElement.replaceWith(newElement);
}

function replaceNodePath(
expressionPath: NodePath<Types.Expression>,
namePath: NodePath<Types.JSXIdentifier | Types.Identifier>,
importName: string,
t: typeof Types,
tagNamePath: NodePath<
Types.JSXIdentifier | Types.Identifier | Types.JSXMemberExpression | Types.MemberExpression
>,
sxComponentName: string,
) {
const sxIdentifier = addNamed(namePath, importName, process.env.PACKAGE_NAME as string);
let wasSxTransformed = false;

const wrapWithSxCall = (expPath: NodePath<Types.Expression>) => {
let tagNameArg: Types.Identifier | Types.MemberExpression | null = null;
if (tagNamePath.isJSXIdentifier()) {
tagNameArg = t.identifier(tagNamePath.node.name);
} else if (tagNamePath.isJSXMemberExpression()) {
tagNameArg = convertJsxMemberExpressionToMemberExpression(t, tagNamePath);
} else {
tagNameArg = tagNamePath.node as Types.Identifier | Types.MemberExpression;
}
expPath.replaceWith(t.callExpression(sxIdentifier, [expPath.node, tagNameArg]));
wasSxTransformed = true;
};

sxPropConverter(expressionPath, wrapWithSxCall);

if (wasSxTransformed) {
if (tagNamePath.isJSXIdentifier() || tagNamePath.isJSXMemberExpression()) {
wrapWithSxComponent(t, tagNamePath, sxComponentName);
}
}
}

export const babelPlugin = declare<{
propName?: string;
importName?: string;
sxComponentName?: string;
}>((api, { propName = 'sx', importName = 'sx', sxComponentName = 'ForwardSx' }) => {
api.assertVersion(7);
const { types: t } = api;
return {
name: '@pigmentcss/sx-plugin',
visitor: {
JSXAttribute(path) {
const namePath = path.get('name');
const openingElement = path.findParent((p) => p.isJSXOpeningElement());
if (
!openingElement ||
!openingElement.isJSXOpeningElement() ||
!namePath.isJSXIdentifier() ||
namePath.node.name !== propName
) {
return;
}
const tagName = openingElement.get('name');
const valuePath = path.get('value');
if (!valuePath.isJSXExpressionContainer()) {
return;
}
const expressionPath = valuePath.get('expression');
if (!expressionPath.isExpression()) {
return;
}
// @ts-ignore
replaceNodePath(expressionPath, namePath, importName, t, tagName, sxComponentName);
},
ObjectProperty(path) {
// @TODO - Maybe add support for React.createElement calls as well.
// Right now, it only checks for jsx(),jsxs(),jsxDEV() and jsxsDEV() calls.
const keyPath = path.get('key');
if (!keyPath.isIdentifier() || keyPath.node.name !== propName) {
return;
}
const valuePath = path.get('value');
if (!valuePath.isObjectExpression() && !valuePath.isArrowFunctionExpression()) {
return;
}
const parentJsxCall = path.findParent((p) => p.isCallExpression());
if (!parentJsxCall || !parentJsxCall.isCallExpression()) {
return;
}
const callee = parentJsxCall.get('callee');
if (!callee.isIdentifier() || !callee.node.name.includes('jsx')) {
return;
}
const jsxElement = parentJsxCall.get('arguments')[0] as NodePath<Types.Identifier>;
replaceNodePath(valuePath, keyPath, importName, t, jsxElement, sxComponentName);
},
},
};
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.bc1d15y {
._c1d15y {
margin: 0;
margin-block: 1rem;
padding: 0;
Expand Down
6 changes: 4 additions & 2 deletions packages/pigment-css-react/tests/Box/fixtures/box.output.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { sx as _sx2 } from '@pigment-css/react';
import { ForwardSx as _ForwardSx } from '@pigment-css/react/private-runtime';
import Box from '@pigment-css/react/Box';
export function App() {
return (
<Box as="ul" sx={'bc1d15y'}>
<_ForwardSx sxComponent={Box} as="ul" sx={'_c1d15y'}>
Hello Box
</Box>
</_ForwardSx>
);
}
14 changes: 7 additions & 7 deletions packages/pigment-css-react/tests/sx/fixtures/sxProps.output.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
.sjfloo5-1 {
font-size: 3rem;
}
.sjfloo5.s1o8xp19 {
.sjfloo5._1o8xp19 {
color: red;
}
.sjfloo5.s1xbsywq {
color: var(--s1xbsywq-0);
.sjfloo5._1xbsywq {
color: var(--_1xbsywq-0);
}
.sjfloo5.s1wnk6s5 {
.sjfloo5._1wnk6s5 {
background-color: blue;
color: white;
}
.sjfloo5.stzaibv {
color: var(--stzaibv-0);
.sjfloo5._tzaibv {
color: var(--_tzaibv-0);
}
.sjfloo5.sazg8ol {
.sjfloo5._azg8ol {
margin-bottom: 8px;
text-align: center;
}
Loading

0 comments on commit ed86fd6

Please sign in to comment.