Skip to content

Commit

Permalink
feat(transformer): add JS runtime transformer for @pandabox/unplugin (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jonambas authored Apr 9, 2024
1 parent bcc9947 commit 3bb81a4
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/node_modules
/dist
/app
/apps
/coverage
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ Which will produce:

---

### Supported Syntax
### Supported syntax

This plugin supports aliasing to Panda's object syntax via a `value` key, just as you would define semantic tokens in Panda's theme.
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.

```ts
export default defineConfig({
Expand All @@ -84,7 +84,7 @@ export default defineConfig({
},
},
text: {
value: 'gray.100',
value: '#111',
},
},
}),
Expand All @@ -102,14 +102,45 @@ export default defineConfig({
Produces:

```html
<div class="bg_red.500 lg:bg_blue.500 text_gray.100" />
<div class="bg_red.500 lg:bg_blue.500 text_#111" />
```

---

### Alternatives
### Further optimization

There are alternatives to achieve the same result.
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.

- Use Panda's `importMap` in config to reference your own alias to token mapping.
- Use `@pandabox/unplugin` to strip out and remove your own alias mapping at build time.
Example Next.js config:

```js
import unplugin from '@pandabox/unplugin';
import { transform } from 'panda-plugin-ct';

// Your token object
// This should be the same as the object you supplied to the Panda plugin
const tokens = {};

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
config.plugins.push(
unplugin.webpack({
transform: transform(tokens),
optimizeJs: true, // Optional, this will replace other Panda runtime functions (css, cva, etc)
}),
);
return config;
},
};

export default nextConfig;
```

---

### Acknowledgement

- [Jimmy](https://github.com/jimmymorris) – for the idea and motivation behind the plugin
- [Alex](https://github.com/astahmer) – for providing feedback with the plugin's internals and functionality
8 changes: 4 additions & 4 deletions src/__tests__/codegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ describe('codegen', () => {
"code": "
const pluginCtMap = new Map([["foo.100","#fff"],["foo.200",{"base":"#000","lg":"#111"}],["bar.100","red"],["bar.200","blue"]]);
export const ct = (path) => {
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
return pluginCtMap.get(path);
};",
export const ct = (path) => {
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
return pluginCtMap.get(path);
};",
"file": "ct.mjs",
},
{
Expand Down
4 changes: 3 additions & 1 deletion src/__tests__/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const makeParser = (content: string) => {
describe('parser', () => {
it('parses', () => {
const res = makeParser(`
import foo from 'bar';
import { css, ct, cva } from '@/styled-system/css';
const styles = cva({
Expand All @@ -36,7 +37,8 @@ describe('parser', () => {

expect(res).toMatchInlineSnapshot(
`
"import { css, ct, cva } from '@/styled-system/css';
"import foo from 'bar';
import { css, ct, cva } from '@/styled-system/css';
const styles = cva({
base: {
Expand Down
81 changes: 81 additions & 0 deletions src/__tests__/transformer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { transform } from '../transform';
import { tokens } from './fixtures';

describe('transform', () => {
it('returns a function', () => {
expect(transform(tokens)).toBeTypeOf('function');
});

it('replaces ct', () => {
expect(
transform(tokens)({
configure: () => {},
filePath: 'test.tsx',
content: `
import { css, ct, cva } from '@/styled-system/css';
const styles = cva({
base: {
// background: ct('foo.200'),
color: ct('bar.200'),
},
});
export const Component = () => {
return (<div
className={
css({
bg: ct('foo.200'),
color: ct('bar.100')
})}
/>);
`,
}),
).toMatchInlineSnapshot(`
"import { css, ct, cva } from '@/styled-system/css';
const styles = cva({
base: {
// background: ct('foo.200'),
color: 'blue',
},
});
export const Component = () => {
return (<div
className={
css({
bg: {"base":"#000","lg":"#111"},
color: 'red'
})}
/>);
"
`);
});

it('skips without imports, expressions, content', () => {
expect(
transform(tokens)({
configure: () => {},
filePath: 'test.tsx',
content: `<div className={css({ bg: ct("foo.200") })/>`,
}),
).toBeUndefined();

expect(
transform(tokens)({
configure: () => {},
filePath: 'test.tsx',
content: `import { ct } from '@/styled-system/css`,
}),
).toBeUndefined();

expect(
transform(tokens)({
configure: () => {},
filePath: 'test.tsx',
content: ``,
}),
).toBeUndefined();
});
});
8 changes: 4 additions & 4 deletions src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export const codegen = (
const ctFile: ArtifactContent = {
file: `ct.${ext}`,
code: `${mapTemplate(map)}
export const ct = (path) => {
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
return pluginCtMap.get(path);
};`,
export const ct = (path) => {
if (!pluginCtMap.has(path)) return 'panda-plugin-ct_alias-not-found';
return pluginCtMap.get(path);
};`,
};

const ctDtsFile: ArtifactContent = {
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { codegen } from './codegen';
import { createContext } from './context';
import type { ComponentTokens } from './types';
import { makeMap } from './map';
import { transform } from './transform';

/**
* 🐼 A Panda CSS plugin for design token aliases
*
* @see https://github.com/jonambas/panda-plugin-ct
*/
const pluginComponentTokens = (tokens: ComponentTokens): PandaPlugin => {
Expand All @@ -27,4 +30,4 @@ const pluginComponentTokens = (tokens: ComponentTokens): PandaPlugin => {
};
};

export { pluginComponentTokens, ComponentTokens };
export { pluginComponentTokens, transform, type ComponentTokens };
21 changes: 21 additions & 0 deletions src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ParserResultBeforeHookArgs } from '@pandacss/types';
import { createContext } from './context';
import type { ComponentTokens } from './types';
import { parser } from './parser';

/**
* Transformer for @pandabox/unplugin.
* Replaces JS runtime calls to `ct` with their resulting class names.
*
* @see https://github.com/jonambas/panda-plugin-ct
* @see https://github.com/astahmer/pandabox/tree/main/packages/unplugin
*/
export const transform = (tokens: ComponentTokens) => {
const context = createContext(tokens);
return (args: ParserResultBeforeHookArgs) => {
// This doesn't have `args.configure`

if (!args.content) return;
return parser(args, context);
};
};

0 comments on commit 3bb81a4

Please sign in to comment.