Skip to content

Commit

Permalink
[pigment-css][react] Add a How Pigment works guide
Browse files Browse the repository at this point in the history
  • Loading branch information
brijeshb42 committed Mar 27, 2024
1 parent 2827bac commit e5caf4a
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 0 deletions.
152 changes: 152 additions & 0 deletions packages/pigment-css-react/HOW_PIGMENT_CSS_WORKS.md
@@ -0,0 +1,152 @@
# How Pigment CSS works

As already [stated](README.md), Pigment CSS is a zero-runtime CSS-in-JS library. This means it does not have access to the browser runtime of the end-user to generate and insert the authored CSS at runtime. So it has to do all its processing during the build time to pregenerate the CSS which are then made part of the output bundle. This is the reason it cannot be consumed on its own. You also have to install the specific Pigment CSS plugin and configure your bundler with it (currently, it supports Next.js and Vite with plans for more bundlers in future).

## Internals

Pigment CSS uses [WyW-in-JS](https://wyw-in-js.dev/) library that also powers [Linaria](https://linaria.dev/). It has a concept of `processors` which enables custom logic around what to do when users use an import from the library. For ex, in this particular use of `css` function from Pigment CSS:

```js
// app.js
import { css } from '@pigment-css/react';

const testClass = css(({ theme }) => ({
lineHeight: '1.4375em',
padding: 0,
position: 'relative',
color: (theme.vars || theme).palette.text.secondary,
...theme.typography.body1,
}));
```

This uses the `css` function to generate and assign a string className to `testClass` variable. The minimal bundler configuration for this to work with the `theme` might look like:

```js
// config.js
const customTheme = {
palette: {
text: {
secondary: '#00e676',
},
},
typography: {
body1: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontWeight: 400,
fontSize: 16,
lineHeight: 1.5,
// ...others
},
},
};

const pigmentConfig = {
theme: customTheme,
};
```

When the source file above (`app.js`) goes through the bundler transform, the bundler plugin of Pigment CSS looks for the call site of `css` imported from `@pigment-css/react` and prepares an intermediate source file to get the actual values of the arguments of the `css` call. In this case, that intermediate code might look like:

```js
const _exp1 = ({ theme }) => ({
lineHeight: '1.4375em',
padding: 0,
position: 'relative',
color: (theme.vars || theme).palette.text.secondary,
...theme.typography.body1,
});

module.exports = {
_exp1,
};
```

The above code is then evaluated through the use of `node:module` [module](https://nodejs.org/docs/v20.11.1/api/modules.html#module) which returns the evaluated values of the parameters.
Now that it has access to the actual values of the styles, Pigment CSS does source code transformation in-place to remove the `css` call from the source and replaces it with a static class name string:

```js
// app.js
import { css } from '@pigment-css/react';

const testClass = 'c1aiqtje';
```

At the same time, it generates the CSS string for the above styles using `emotion` at build time and through the bundler specific plugin, makes it part of the output CSS:

```js
// app.js
import { css } from '@pigment-css/react';
import './app.pigment.css';

const testClass = 'c1aiqtje';
```

Above, you can assume the `./app.pigment.css` to be a virtual module (this file doesn't actually exist on the file system) with the contents generated in-memory. This varies across various bundler implementations but the general idea is the same. Here, the contents of the `app.pigment.css` file would be:

```css
.c1aiqtje {
padding: 0;
position: relative;
color: #00e676;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-weight: 400;
font-size: 16;
line-height: 1.5;
}
```

If the same was done using the `styled` call instead of `css` for the source:

```js
// app.js
import { styled } from '@pigment-css/react';

const Paragraph = styled.p(({ theme }) => ({
lineHeight: '1.4375em',
padding: 0,
position: 'relative',
color: (theme.vars || theme).palette.text.secondary,
...theme.typography.body1,
}));
```

The transformed output would look like:

```js
// app.js
import { styled } from '@pigment-css/react';

const Paragraph = styled('p')({
baseClasses: ['c1aiqtje'],
});
```

You can notice above that now the runtime code, that finally ends up in the output bundle, doesn't involve any kind of CSS generation and it only needs to deal with primitive strings to apply it to the `p` that it'd render in the end.

## Debugging

Since Pigment CSS does all it's heavy lifting at build-time, seeing and debugging for errors works a little differently for it. When using `emotion` with a code like below:

Check warning on line 128 in packages/pigment-css-react/HOW_PIGMENT_CSS_WORKS.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.OxfordComma] Use the Oxford comma in 'Since Pigment CSS does all it's heavy lifting at build-time, seeing and'. Raw Output: {"message": "[Google.OxfordComma] Use the Oxford comma in 'Since Pigment CSS does all it's heavy lifting at build-time, seeing and'.", "location": {"path": "packages/pigment-css-react/HOW_PIGMENT_CSS_WORKS.md", "range": {"start": {"line": 128, "column": 1}}}, "severity": "WARNING"}

```js
// app.js
import { styled } from '@emotion/styled';

const Paragraph = styled.p(({ theme }) => {
console.log(theme);
return {
lineHeight: '1.4375em',
padding: 0,
position: 'relative',
color: (theme.vars || theme).palette.text.secondary,
...theme.typography.body1,
};
});
```

Running this code, either in browser or on server, you'd see the actual value of the theme being logged at runtime, either in browser console or your server logs.

But when this same code uses `@pigment-css/react` instead of `@emotion/styled` (with the bundler configured), you'll see the theme being logged in your terminal at build-time or during development. This can be either when you run `next dev`/`next build` when using Next.js or when you run `vite dev`/`vite build` when using Vite. The actual output bundle won't have the code that logs the theme since that has already been replaced as shown in [line 120](#L120). So the notion of `theme` doesn't exist after bundling and it's just reduced down to a collection of CSS variables.

So to debug why something is not working, you'll have to look into your terminal to see issues specific to Pigment CSS. They'll either start with `@pigment-css/` or `wyw-in-js` and have a small summary of why the issue is happening. It would mostly be related to the scope of variables being used to define the styles in most cases. Feel free to open a [new issue](https://github.com/mui/material-ui/issues/new/choose) when it seems like it is probably related to the internals of Pigment CSS.

<!-- @TODO: Add more about specific issues -->
5 changes: 5 additions & 0 deletions packages/pigment-css-react/README.md
Expand Up @@ -22,6 +22,7 @@ Pigment CSS is a zero-runtime CSS-in-JS library that extracts the colocated sty
- [Right-to-left support](#right-to-left-support)
- [How-to guides](#how-to-guides)
- [Coming from Emotion or styled-components](#coming-from-emotion-or-styled-components)
- [How Pigment CSS works](#how-pigment-css-works)

## Getting started

Expand Down Expand Up @@ -854,3 +855,7 @@ function App() {
)
}
```

## How Pigment CSS works

See a detailed [guide here](HOW_PIGMENT_CSS_WORKS.md).

0 comments on commit e5caf4a

Please sign in to comment.