Skip to content

Commit 0c8a1c0

Browse files
author
Brijesh Bittu
committed
First working version of pigment-css/react@v1
1 parent 26c97dc commit 0c8a1c0

33 files changed

+1592
-155
lines changed

.codesandbox/ci.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"packages/pigment-css-core",
77
"packages/pigment-css-nextjs-plugin",
88
"packages/pigment-css-react",
9+
"packages/pigment-css-react-new",
910
"packages/pigment-css-theme",
1011
"packages/pigment-css-unplugin",
1112
"packages/pigment-css-utils",

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
/packages/pigment-css-react/tests/**/fixtures
1212
/packages/pigment-css-core/exports/
1313
/packages/pigment-css-core/tests/**/fixtures
14+
/packages/pigment-css-react-new/exports/
15+
/packages/pigment-css-react-new/tests/**/fixtures
1416
/packages/pigment-css-nextjs-plugin/loader.js
1517
# Ignore fixtures
1618
/packages-internal/scripts/typescript-to-proptypes/test/*/*

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ module.exports = {
8383
],
8484
'no-use-before-define': 'off',
8585

86+
'react/react-in-jsx-scope': 'off',
87+
8688
// disabled type-aware linting due to performance considerations
8789
'@typescript-eslint/dot-notation': 'off',
8890
'dot-notation': 'error',
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Pigment CSS
2+
3+
Pigment CSS is a zero-runtime CSS-in-JS library that extracts the colocated styles to their own CSS files at build time.
4+
5+
## Getting started
6+
7+
Pigment CSS supports Next.js and Vite with support for more bundlers in the future.
8+
9+
### Why choose Pigment CSS
10+
11+
Thanks to recent advancements in CSS (like CSS variables and `color-mix()`), "traditional" CSS-in-JS solutions that process styles at runtime are no longer required for unlocking features like color transformations and theme variables which are necessary for maintaining a sophisticated design system.
12+
13+
Pigment CSS addresses the needs of the modern React developer by providing a zero-runtime CSS-in-JS styling solution as a successor to tools like Emotion and styled-components.
14+
15+
Compared to its predecessors, Pigment CSS offers improved DX and runtime performance (though at the cost of increased build time) while also being compatible with React Server Components.
16+
Pigment CSS is built on top of [WyW-in-JS](https://wyw-in-js.dev/), enabling to provide the smoothest possible experience for Material UI users when migrating from Emotion in v5 to Pigment CSS in v6.
17+
18+
### Installation
19+
20+
<!-- #default-branch-switch -->
21+
22+
```bash
23+
npm install @pigment-css/core
24+
npm install --save-dev @pigment-css/nextjs-plugin
25+
```
26+
27+
<!-- Replace this with the documentation link once it is available. -->
28+
29+
For more information and getting started guide, check the [repository README.md](https://github.com/mui/pigment-css).
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Object.defineProperty(exports, '__esModule', {
2+
value: true,
3+
});
4+
5+
exports.default = require('../build/processors/css').StyledCssProcessor;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Object.defineProperty(exports, '__esModule', {
2+
value: true,
3+
});
4+
5+
exports.default = require('../build/processors/styled').StyledProcessor;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"name": "@pigment-css/react-new",
3+
"version": "0.0.27",
4+
"main": "build/index.js",
5+
"module": "build/index.mjs",
6+
"types": "build/index.d.ts",
7+
"author": "MUI Team",
8+
"description": "A zero-runtime CSS-in-JS library to be used with React.",
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/mui/pigment-css.git",
12+
"directory": "packages/pigment-css-react-new"
13+
},
14+
"license": "MIT",
15+
"bugs": {
16+
"url": "https://github.com/mui/pigment-css/issues"
17+
},
18+
"homepage": "https://github.com/mui/pigment-css/tree/master/README.md",
19+
"funding": {
20+
"type": "opencollective",
21+
"url": "https://opencollective.com/mui-org"
22+
},
23+
"scripts": {
24+
"clean": "rimraf build",
25+
"watch": "tsup --watch --clean false",
26+
"copy-license": "node ../../scripts/pigment-license.mjs",
27+
"build": "tsup",
28+
"test": "cd ../../ && cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=text mocha 'packages/pigment-css-react-new/**/*.test.{js,ts,tsx}'",
29+
"test:update": "cd ../../ && cross-env NODE_ENV=test UPDATE_FIXTURES=true mocha 'packages/pigment-css-react-new/**/*.test.{js,ts,tsx}'",
30+
"test:ci": "cd ../../ && cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=lcov --report-dir=./coverage/pigment-css-react-new mocha 'packages/pigment-css-react-new/**/*.test.{js,ts,tsx}'",
31+
"typescript": "tsc --noEmit -p ."
32+
},
33+
"dependencies": {
34+
"@babel/plugin-syntax-jsx": "^7.25.9",
35+
"@babel/types": "^7.25.8",
36+
"@pigment-css/core": "workspace:*",
37+
"@pigment-css/utils": "workspace:*",
38+
"@pigment-css/theme": "workspace:^",
39+
"@wyw-in-js/processor-utils": "^0.5.5",
40+
"@wyw-in-js/shared": "^0.5.5",
41+
"@wyw-in-js/transform": "^0.5.5",
42+
"csstype": "^3.1.3"
43+
},
44+
"peerDependencies": {
45+
"react": "^17 || ^18 || ^19 || ^19.0.0-rc"
46+
},
47+
"devDependencies": {
48+
"@types/react": "^19.0.2",
49+
"@types/chai": "^4.3.14",
50+
"chai": "^4.4.1",
51+
"prettier": "^3.3.3",
52+
"react": "^19.0.0"
53+
},
54+
"sideEffects": false,
55+
"publishConfig": {
56+
"access": "public"
57+
},
58+
"wyw-in-js": {
59+
"tags": {
60+
"styled": "./exports/styled.js",
61+
"sx": "./exports/sx.js",
62+
"keyframes": "@pigment-css/core/exports/keyframes",
63+
"css": "./exports/css.js"
64+
}
65+
},
66+
"files": [
67+
"src",
68+
"build",
69+
"exports",
70+
"package.json",
71+
"styles.css",
72+
"LICENSE"
73+
],
74+
"exports": {
75+
".": {
76+
"types": "./build/index.d.ts",
77+
"import": {
78+
"types": "./build/index.d.mts",
79+
"default": "./build/index.mjs"
80+
},
81+
"require": "./build/index.js",
82+
"default": "./build/index.js"
83+
},
84+
"./package.json": "./package.json",
85+
"./styles.css": "./styles.css",
86+
"./processors/css": {
87+
"import": "./build/processors/css.mjs",
88+
"require": "./build/processors/css.js",
89+
"default": "./build/processors/css.js"
90+
},
91+
"./processors/styled": {
92+
"import": "./build/processors/styled.mjs",
93+
"require": "./build/processors/styled.js",
94+
"default": "./build/processors/styled.js"
95+
},
96+
"./exports/*": {
97+
"default": "./exports/*.js"
98+
},
99+
"./runtime": {
100+
"import": "./build/runtime/index.mjs",
101+
"require": "./build/runtime/index.js",
102+
"default": "./build/runtime/index.js"
103+
}
104+
},
105+
"nx": {
106+
"targets": {
107+
"test": {
108+
"cache": false,
109+
"dependsOn": [
110+
"build"
111+
]
112+
},
113+
"test:update": {
114+
"cache": false,
115+
"dependsOn": [
116+
"build"
117+
]
118+
},
119+
"test:ci": {
120+
"cache": false,
121+
"dependsOn": [
122+
"build"
123+
]
124+
},
125+
"build": {
126+
"outputs": [
127+
"{projectRoot}/build"
128+
]
129+
}
130+
}
131+
}
132+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from '@pigment-css/core';
2+
export * from './styled';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { CssProcessor } from '@pigment-css/core/processors/css';
2+
3+
export class StyledCssProcessor extends CssProcessor {
4+
basePath = `${process.env.PACKAGE_NAME}/runtime`;
5+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { Identifier } from '@babel/types';
2+
import { evaluateClassNameArg } from '@pigment-css/utils';
3+
import {
4+
type CallParam,
5+
type Expression,
6+
type MemberParam,
7+
type Params,
8+
type TailProcessorParams,
9+
validateParams,
10+
} from '@wyw-in-js/processor-utils';
11+
import { ValueType } from '@wyw-in-js/shared';
12+
import { CssProcessor } from '@pigment-css/core/processors/css';
13+
import { BaseInterface } from '@pigment-css/core/css';
14+
15+
export type TemplateCallback = (params: Record<string, unknown> | undefined) => string | number;
16+
17+
type WrappedNode =
18+
| string
19+
| {
20+
node: Identifier;
21+
source: string;
22+
};
23+
24+
const REACT_COMPONENT = '$$reactComponent';
25+
26+
export class StyledProcessor extends CssProcessor {
27+
tagName: WrappedNode = '';
28+
29+
// eslint-disable-next-line class-methods-use-this
30+
get packageName() {
31+
return process.env.PACKAGE_NAME as string;
32+
}
33+
34+
basePath = `${this.packageName}/runtime`;
35+
36+
constructor(params: Params, ...args: TailProcessorParams) {
37+
const [callee, callOrMember, callOrTemplate] = params;
38+
super([callee, callOrTemplate], ...args);
39+
40+
if (params.length === 3) {
41+
validateParams(
42+
params,
43+
['callee', ['call', 'member'], ['call', 'template']],
44+
`Invalid use of ${this.tagSource.imported} function.`,
45+
);
46+
47+
this.setTagName(callOrMember as CallParam | MemberParam);
48+
} else {
49+
throw new Error(`${this.packageName} Invalid call to ${this.tagSource.imported} function.`);
50+
}
51+
}
52+
53+
private setTagName(param: CallParam | MemberParam) {
54+
if (param[0] === 'member') {
55+
this.tagName = param[1];
56+
} else {
57+
const [, element, callOpt] = param;
58+
switch (element.kind) {
59+
case ValueType.CONST: {
60+
if (typeof element.value === 'string') {
61+
this.tagName = element.value;
62+
}
63+
break;
64+
}
65+
case ValueType.LAZY: {
66+
this.tagName = {
67+
node: element.ex,
68+
source: element.source,
69+
};
70+
this.dependencies.push(element);
71+
break;
72+
}
73+
case ValueType.FUNCTION: {
74+
this.tagName = REACT_COMPONENT;
75+
break;
76+
}
77+
default:
78+
break;
79+
}
80+
81+
if (callOpt) {
82+
this.processor.staticClass = evaluateClassNameArg(callOpt.source) as BaseInterface;
83+
}
84+
}
85+
}
86+
87+
getBaseClass(): string {
88+
return this.className;
89+
}
90+
91+
get asSelector(): string {
92+
return this.processor.getBaseClass();
93+
}
94+
95+
get value(): Expression {
96+
return this.astService.stringLiteral(`.${this.processor.getBaseClass()}`);
97+
}
98+
99+
createReplacement() {
100+
const t = this.astService;
101+
const callId = t.addNamedImport('styled', this.getImportPath());
102+
const elementOrComponent = (() => {
103+
if (typeof this.tagName === 'string') {
104+
if (this.tagName === REACT_COMPONENT) {
105+
return t.arrowFunctionExpression([], t.blockStatement([]));
106+
}
107+
return t.stringLiteral(this.tagName);
108+
}
109+
if (this.tagName?.node) {
110+
return t.callExpression(t.identifier(this.tagName.node.name), []);
111+
}
112+
return t.nullLiteral();
113+
})();
114+
const firstCall = t.callExpression(callId, [elementOrComponent]);
115+
return t.callExpression(firstCall, [this.getStyleArgs()]);
116+
}
117+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from '@pigment-css/core/runtime';
2+
export * from './styled';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as React from 'react';
2+
3+
export function styled(component) {
4+
function classWrapper({ displayName }) {
5+
// eslint-disable-next-line react/prop-types
6+
const WrappedComponent = React.forwardRef(function WrappedComponent({ as: asProp, ...rest }) {
7+
const FinalComponent = asProp ?? component;
8+
return <FinalComponent {...rest} />;
9+
});
10+
WrappedComponent.displayName = `Pigment(${displayName ?? 'Styled'})`;
11+
}
12+
return classWrapper;
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare function styled(): void;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { styled } from './runtime/styled';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { CSSObjectNoCallback, ThemeArgs } from '@pigment-css/core';
2+
3+
type GetTheme<Argument> = Argument extends { theme: infer Theme } ? Theme : never;
4+
5+
export type SxProp =
6+
| CSSObjectNoCallback
7+
| ((theme: GetTheme<ThemeArgs>) => CSSObjectNoCallback)
8+
| ReadonlyArray<CSSObjectNoCallback | ((theme: GetTheme<ThemeArgs>) => CSSObjectNoCallback)>;
9+
10+
export default function sx(arg: SxProp, componentClass?: string): string;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/**
2+
* Placeholder css file where theme contents will be injected by the bundler
3+
*/

0 commit comments

Comments
 (0)