Skip to content

Commit

Permalink
First working version of pigment-css/react@v1
Browse files Browse the repository at this point in the history
  • Loading branch information
Brijesh Bittu committed Jan 21, 2025
1 parent 26c97dc commit 0c8a1c0
Show file tree
Hide file tree
Showing 33 changed files with 1,592 additions and 155 deletions.
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"packages/pigment-css-core",
"packages/pigment-css-nextjs-plugin",
"packages/pigment-css-react",
"packages/pigment-css-react-new",
"packages/pigment-css-theme",
"packages/pigment-css-unplugin",
"packages/pigment-css-utils",
Expand Down
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/packages/pigment-css-react/tests/**/fixtures
/packages/pigment-css-core/exports/
/packages/pigment-css-core/tests/**/fixtures
/packages/pigment-css-react-new/exports/
/packages/pigment-css-react-new/tests/**/fixtures
/packages/pigment-css-nextjs-plugin/loader.js
# Ignore fixtures
/packages-internal/scripts/typescript-to-proptypes/test/*/*
Expand Down
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ module.exports = {
],
'no-use-before-define': 'off',

'react/react-in-jsx-scope': 'off',

// disabled type-aware linting due to performance considerations
'@typescript-eslint/dot-notation': 'off',
'dot-notation': 'error',
Expand Down
29 changes: 29 additions & 0 deletions packages/pigment-css-react-new/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Pigment CSS

Pigment CSS is a zero-runtime CSS-in-JS library that extracts the colocated styles to their own CSS files at build time.

## Getting started

Pigment CSS supports Next.js and Vite with support for more bundlers in the future.

### Why choose Pigment CSS

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.

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.

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.
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.

### Installation

<!-- #default-branch-switch -->

```bash
npm install @pigment-css/core
npm install --save-dev @pigment-css/nextjs-plugin
```

<!-- Replace this with the documentation link once it is available. -->

For more information and getting started guide, check the [repository README.md](https://github.com/mui/pigment-css).
5 changes: 5 additions & 0 deletions packages/pigment-css-react-new/exports/css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../build/processors/css').StyledCssProcessor;
5 changes: 5 additions & 0 deletions packages/pigment-css-react-new/exports/styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../build/processors/styled').StyledProcessor;
132 changes: 132 additions & 0 deletions packages/pigment-css-react-new/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"name": "@pigment-css/react-new",
"version": "0.0.27",
"main": "build/index.js",
"module": "build/index.mjs",
"types": "build/index.d.ts",
"author": "MUI Team",
"description": "A zero-runtime CSS-in-JS library to be used with React.",
"repository": {
"type": "git",
"url": "git+https://github.com/mui/pigment-css.git",
"directory": "packages/pigment-css-react-new"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/mui/pigment-css/issues"
},
"homepage": "https://github.com/mui/pigment-css/tree/master/README.md",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"scripts": {
"clean": "rimraf build",
"watch": "tsup --watch --clean false",
"copy-license": "node ../../scripts/pigment-license.mjs",
"build": "tsup",
"test": "cd ../../ && cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=text mocha 'packages/pigment-css-react-new/**/*.test.{js,ts,tsx}'",
"test:update": "cd ../../ && cross-env NODE_ENV=test UPDATE_FIXTURES=true mocha 'packages/pigment-css-react-new/**/*.test.{js,ts,tsx}'",
"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}'",
"typescript": "tsc --noEmit -p ."
},
"dependencies": {
"@babel/plugin-syntax-jsx": "^7.25.9",
"@babel/types": "^7.25.8",
"@pigment-css/core": "workspace:*",
"@pigment-css/utils": "workspace:*",
"@pigment-css/theme": "workspace:^",
"@wyw-in-js/processor-utils": "^0.5.5",
"@wyw-in-js/shared": "^0.5.5",
"@wyw-in-js/transform": "^0.5.5",
"csstype": "^3.1.3"
},
"peerDependencies": {
"react": "^17 || ^18 || ^19 || ^19.0.0-rc"
},
"devDependencies": {
"@types/react": "^19.0.2",
"@types/chai": "^4.3.14",
"chai": "^4.4.1",
"prettier": "^3.3.3",
"react": "^19.0.0"
},
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"wyw-in-js": {
"tags": {
"styled": "./exports/styled.js",
"sx": "./exports/sx.js",
"keyframes": "@pigment-css/core/exports/keyframes",
"css": "./exports/css.js"
}
},
"files": [
"src",
"build",
"exports",
"package.json",
"styles.css",
"LICENSE"
],
"exports": {
".": {
"types": "./build/index.d.ts",
"import": {
"types": "./build/index.d.mts",
"default": "./build/index.mjs"
},
"require": "./build/index.js",
"default": "./build/index.js"
},
"./package.json": "./package.json",
"./styles.css": "./styles.css",
"./processors/css": {
"import": "./build/processors/css.mjs",
"require": "./build/processors/css.js",
"default": "./build/processors/css.js"
},
"./processors/styled": {
"import": "./build/processors/styled.mjs",
"require": "./build/processors/styled.js",
"default": "./build/processors/styled.js"
},
"./exports/*": {
"default": "./exports/*.js"
},
"./runtime": {
"import": "./build/runtime/index.mjs",
"require": "./build/runtime/index.js",
"default": "./build/runtime/index.js"
}
},
"nx": {
"targets": {
"test": {
"cache": false,
"dependsOn": [
"build"
]
},
"test:update": {
"cache": false,
"dependsOn": [
"build"
]
},
"test:ci": {
"cache": false,
"dependsOn": [
"build"
]
},
"build": {
"outputs": [
"{projectRoot}/build"
]
}
}
}
}
2 changes: 2 additions & 0 deletions packages/pigment-css-react-new/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from '@pigment-css/core';
export * from './styled';
5 changes: 5 additions & 0 deletions packages/pigment-css-react-new/src/processors/css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CssProcessor } from '@pigment-css/core/processors/css';

export class StyledCssProcessor extends CssProcessor {
basePath = `${process.env.PACKAGE_NAME}/runtime`;
}
117 changes: 117 additions & 0 deletions packages/pigment-css-react-new/src/processors/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Identifier } from '@babel/types';
import { evaluateClassNameArg } from '@pigment-css/utils';
import {
type CallParam,
type Expression,
type MemberParam,
type Params,
type TailProcessorParams,
validateParams,
} from '@wyw-in-js/processor-utils';
import { ValueType } from '@wyw-in-js/shared';
import { CssProcessor } from '@pigment-css/core/processors/css';
import { BaseInterface } from '@pigment-css/core/css';

export type TemplateCallback = (params: Record<string, unknown> | undefined) => string | number;

type WrappedNode =
| string
| {
node: Identifier;
source: string;
};

const REACT_COMPONENT = '$$reactComponent';

export class StyledProcessor extends CssProcessor {
tagName: WrappedNode = '';

// eslint-disable-next-line class-methods-use-this
get packageName() {
return process.env.PACKAGE_NAME as string;
}

basePath = `${this.packageName}/runtime`;

constructor(params: Params, ...args: TailProcessorParams) {
const [callee, callOrMember, callOrTemplate] = params;
super([callee, callOrTemplate], ...args);

if (params.length === 3) {
validateParams(
params,
['callee', ['call', 'member'], ['call', 'template']],
`Invalid use of ${this.tagSource.imported} function.`,
);

this.setTagName(callOrMember as CallParam | MemberParam);
} else {
throw new Error(`${this.packageName} Invalid call to ${this.tagSource.imported} function.`);
}
}

private setTagName(param: CallParam | MemberParam) {
if (param[0] === 'member') {
this.tagName = param[1];
} else {
const [, element, callOpt] = param;
switch (element.kind) {
case ValueType.CONST: {
if (typeof element.value === 'string') {
this.tagName = element.value;
}
break;
}
case ValueType.LAZY: {
this.tagName = {
node: element.ex,
source: element.source,
};
this.dependencies.push(element);
break;
}
case ValueType.FUNCTION: {
this.tagName = REACT_COMPONENT;
break;
}
default:
break;
}

if (callOpt) {
this.processor.staticClass = evaluateClassNameArg(callOpt.source) as BaseInterface;
}
}
}

getBaseClass(): string {
return this.className;
}

get asSelector(): string {
return this.processor.getBaseClass();
}

get value(): Expression {
return this.astService.stringLiteral(`.${this.processor.getBaseClass()}`);
}

createReplacement() {
const t = this.astService;
const callId = t.addNamedImport('styled', this.getImportPath());
const elementOrComponent = (() => {
if (typeof this.tagName === 'string') {
if (this.tagName === REACT_COMPONENT) {
return t.arrowFunctionExpression([], t.blockStatement([]));
}
return t.stringLiteral(this.tagName);
}
if (this.tagName?.node) {
return t.callExpression(t.identifier(this.tagName.node.name), []);
}
return t.nullLiteral();
})();
const firstCall = t.callExpression(callId, [elementOrComponent]);
return t.callExpression(firstCall, [this.getStyleArgs()]);
}
}
2 changes: 2 additions & 0 deletions packages/pigment-css-react-new/src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from '@pigment-css/core/runtime';
export * from './styled';
13 changes: 13 additions & 0 deletions packages/pigment-css-react-new/src/runtime/styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

export function styled(component) {
function classWrapper({ displayName }) {
// eslint-disable-next-line react/prop-types
const WrappedComponent = React.forwardRef(function WrappedComponent({ as: asProp, ...rest }) {
const FinalComponent = asProp ?? component;
return <FinalComponent {...rest} />;
});
WrappedComponent.displayName = `Pigment(${displayName ?? 'Styled'})`;
}
return classWrapper;
}
1 change: 1 addition & 0 deletions packages/pigment-css-react-new/src/styled.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare function styled(): void;
1 change: 1 addition & 0 deletions packages/pigment-css-react-new/src/styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { styled } from './runtime/styled';
10 changes: 10 additions & 0 deletions packages/pigment-css-react-new/src/sx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { CSSObjectNoCallback, ThemeArgs } from '@pigment-css/core';

type GetTheme<Argument> = Argument extends { theme: infer Theme } ? Theme : never;

export type SxProp =
| CSSObjectNoCallback
| ((theme: GetTheme<ThemeArgs>) => CSSObjectNoCallback)
| ReadonlyArray<CSSObjectNoCallback | ((theme: GetTheme<ThemeArgs>) => CSSObjectNoCallback)>;

export default function sx(arg: SxProp, componentClass?: string): string;
3 changes: 3 additions & 0 deletions packages/pigment-css-react-new/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* Placeholder css file where theme contents will be injected by the bundler
*/
Loading

0 comments on commit 0c8a1c0

Please sign in to comment.