Skip to content

Commit 3bb81a4

Browse files
authored
feat(transformer): add JS runtime transformer for @pandabox/unplugin (#21)
1 parent bcc9947 commit 3bb81a4

File tree

8 files changed

+157
-19
lines changed

8 files changed

+157
-19
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/node_modules
22
/dist
3-
/app
3+
/apps
44
/coverage

README.md

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ Which will produce:
6868

6969
---
7070

71-
### Supported Syntax
71+
### Supported syntax
7272

73-
This plugin supports aliasing to Panda's object syntax via a `value` key, just as you would define semantic tokens in Panda's theme.
73+
This plugin supports aliasing to Panda's object syntax via a `value` key, just as you would define semantic tokens in Panda's theme. Anything Panda supports will work, including raw values.
7474

7575
```ts
7676
export default defineConfig({
@@ -84,7 +84,7 @@ export default defineConfig({
8484
},
8585
},
8686
text: {
87-
value: 'gray.100',
87+
value: '#111',
8888
},
8989
},
9090
}),
@@ -102,14 +102,45 @@ export default defineConfig({
102102
Produces:
103103

104104
```html
105-
<div class="bg_red.500 lg:bg_blue.500 text_gray.100" />
105+
<div class="bg_red.500 lg:bg_blue.500 text_#111" />
106106
```
107107

108108
---
109109

110-
### Alternatives
110+
### Further optimization
111111

112-
There are alternatives to achieve the same result.
112+
This plugin generates a performant JS runtime to map paths to their respective class names. This runtime can be completely removed using [@pandabox/unplugin](https://github.com/astahmer/pandabox/tree/main/packages/unplugin), with a transformer exported from this package. Your bundler's config will need to be modified to achieve this.
113113

114-
- Use Panda's `importMap` in config to reference your own alias to token mapping.
115-
- Use `@pandabox/unplugin` to strip out and remove your own alias mapping at build time.
114+
Example Next.js config:
115+
116+
```js
117+
import unplugin from '@pandabox/unplugin';
118+
import { transform } from 'panda-plugin-ct';
119+
120+
// Your token object
121+
// This should be the same as the object you supplied to the Panda plugin
122+
const tokens = {};
123+
124+
/** @type {import('next').NextConfig} */
125+
const nextConfig = {
126+
reactStrictMode: true,
127+
webpack: (config) => {
128+
config.plugins.push(
129+
unplugin.webpack({
130+
transform: transform(tokens),
131+
optimizeJs: true, // Optional, this will replace other Panda runtime functions (css, cva, etc)
132+
}),
133+
);
134+
return config;
135+
},
136+
};
137+
138+
export default nextConfig;
139+
```
140+
141+
---
142+
143+
### Acknowledgement
144+
145+
- [Jimmy](https://github.com/jimmymorris) – for the idea and motivation behind the plugin
146+
- [Alex](https://github.com/astahmer) – for providing feedback with the plugin's internals and functionality

src/__tests__/codegen.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ describe('codegen', () => {
3030
"code": "
3131
const pluginCtMap = new Map([["foo.100","#fff"],["foo.200",{"base":"#000","lg":"#111"}],["bar.100","red"],["bar.200","blue"]]);
3232
33-
export const ct = (path) => {
34-
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
35-
return pluginCtMap.get(path);
36-
};",
33+
export const ct = (path) => {
34+
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
35+
return pluginCtMap.get(path);
36+
};",
3737
"file": "ct.mjs",
3838
},
3939
{

src/__tests__/parser.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const makeParser = (content: string) => {
1515
describe('parser', () => {
1616
it('parses', () => {
1717
const res = makeParser(`
18+
import foo from 'bar';
1819
import { css, ct, cva } from '@/styled-system/css';
1920
2021
const styles = cva({
@@ -36,7 +37,8 @@ describe('parser', () => {
3637

3738
expect(res).toMatchInlineSnapshot(
3839
`
39-
"import { css, ct, cva } from '@/styled-system/css';
40+
"import foo from 'bar';
41+
import { css, ct, cva } from '@/styled-system/css';
4042
4143
const styles = cva({
4244
base: {

src/__tests__/transformer.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { transform } from '../transform';
2+
import { tokens } from './fixtures';
3+
4+
describe('transform', () => {
5+
it('returns a function', () => {
6+
expect(transform(tokens)).toBeTypeOf('function');
7+
});
8+
9+
it('replaces ct', () => {
10+
expect(
11+
transform(tokens)({
12+
configure: () => {},
13+
filePath: 'test.tsx',
14+
content: `
15+
import { css, ct, cva } from '@/styled-system/css';
16+
17+
const styles = cva({
18+
base: {
19+
// background: ct('foo.200'),
20+
color: ct('bar.200'),
21+
},
22+
});
23+
24+
export const Component = () => {
25+
return (<div
26+
className={
27+
css({
28+
bg: ct('foo.200'),
29+
color: ct('bar.100')
30+
})}
31+
/>);
32+
`,
33+
}),
34+
).toMatchInlineSnapshot(`
35+
"import { css, ct, cva } from '@/styled-system/css';
36+
37+
const styles = cva({
38+
base: {
39+
// background: ct('foo.200'),
40+
color: 'blue',
41+
},
42+
});
43+
44+
export const Component = () => {
45+
return (<div
46+
className={
47+
css({
48+
bg: {"base":"#000","lg":"#111"},
49+
color: 'red'
50+
})}
51+
/>);
52+
"
53+
`);
54+
});
55+
56+
it('skips without imports, expressions, content', () => {
57+
expect(
58+
transform(tokens)({
59+
configure: () => {},
60+
filePath: 'test.tsx',
61+
content: `<div className={css({ bg: ct("foo.200") })/>`,
62+
}),
63+
).toBeUndefined();
64+
65+
expect(
66+
transform(tokens)({
67+
configure: () => {},
68+
filePath: 'test.tsx',
69+
content: `import { ct } from '@/styled-system/css`,
70+
}),
71+
).toBeUndefined();
72+
73+
expect(
74+
transform(tokens)({
75+
configure: () => {},
76+
filePath: 'test.tsx',
77+
content: ``,
78+
}),
79+
).toBeUndefined();
80+
});
81+
});

src/codegen.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export const codegen = (
2626
const ctFile: ArtifactContent = {
2727
file: `ct.${ext}`,
2828
code: `${mapTemplate(map)}
29-
export const ct = (path) => {
30-
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
31-
return pluginCtMap.get(path);
32-
};`,
29+
export const ct = (path) => {
30+
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
31+
return pluginCtMap.get(path);
32+
};`,
3333
};
3434

3535
const ctDtsFile: ArtifactContent = {

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { codegen } from './codegen';
44
import { createContext } from './context';
55
import type { ComponentTokens } from './types';
66
import { makeMap } from './map';
7+
import { transform } from './transform';
78

89
/**
10+
* 🐼 A Panda CSS plugin for design token aliases
11+
*
912
* @see https://github.com/jonambas/panda-plugin-ct
1013
*/
1114
const pluginComponentTokens = (tokens: ComponentTokens): PandaPlugin => {
@@ -27,4 +30,4 @@ const pluginComponentTokens = (tokens: ComponentTokens): PandaPlugin => {
2730
};
2831
};
2932

30-
export { pluginComponentTokens, ComponentTokens };
33+
export { pluginComponentTokens, transform, type ComponentTokens };

src/transform.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ParserResultBeforeHookArgs } from '@pandacss/types';
2+
import { createContext } from './context';
3+
import type { ComponentTokens } from './types';
4+
import { parser } from './parser';
5+
6+
/**
7+
* Transformer for @pandabox/unplugin.
8+
* Replaces JS runtime calls to `ct` with their resulting class names.
9+
*
10+
* @see https://github.com/jonambas/panda-plugin-ct
11+
* @see https://github.com/astahmer/pandabox/tree/main/packages/unplugin
12+
*/
13+
export const transform = (tokens: ComponentTokens) => {
14+
const context = createContext(tokens);
15+
return (args: ParserResultBeforeHookArgs) => {
16+
// This doesn't have `args.configure`
17+
18+
if (!args.content) return;
19+
return parser(args, context);
20+
};
21+
};

0 commit comments

Comments
 (0)