diff --git a/.changeset/short-trains-train.md b/.changeset/short-trains-train.md
new file mode 100644
index 000000000..b542fcc2e
--- /dev/null
+++ b/.changeset/short-trains-train.md
@@ -0,0 +1,14 @@
+---
+'@pandabox/unplugin-panda-macro': patch
+---
+
+Allow only inlining macro imports
+
+```ts
+import { css } from '../styled-system/css' with { type: 'macro' }
+// ^^^^^^^^^^^^^^^^^^^^
+// without this, the plugin will not transform the `css` usage
+
+const className = css({ display: 'flex', flexDirection: 'column', color: 'red.300' })
+// -> `const className = 'd_flex flex_column text_red.300'`
+```
diff --git a/packages/unplugin-panda-macro/README.md b/packages/unplugin-panda-macro/README.md
index 586bed390..361282783 100644
--- a/packages/unplugin-panda-macro/README.md
+++ b/packages/unplugin-panda-macro/README.md
@@ -16,7 +16,12 @@ Make your `styled-system` disappear at build-time by inlining the results as cla
- [x] cva `const xxx = cva({ ... })`
- [x] recipes `button({ ... })`
- [x] JSX styled factory `styled.div({ ... })` / `styled('div', { ... })`
-- [x] any JSX pattern like ``, `` etc
+- [x] any function or JSX pattern like `box()` / ``, `stack()` / `` etc
+
+> ⚠️ Avoid [anything dynamic](https://panda-css.com/docs/guides/dynamic-styling) as usual, if not more, with Panda CSS
+> due to static analysis limitations.
+
+> ❌ [Runtime conditions](https://panda-css.com/docs/guides/dynamic-styling#runtime-conditions) will NOT be transformed
You can even choose to inline as `atomic` or `grouped` class names.
@@ -146,6 +151,18 @@ type PluginOptions = {
* Do not transform Panda recipes to `atomic` or `grouped` and instead keep their defaults BEM-like classes
*/
keepRecipeClassNames?: boolean
+ /**
+ * Only transform macro imports
+ * @example
+ * ```ts
+ * import { css } from '../styled-system/css' with { type: "macro" }
+ *
+ * const className = css({ display: "flex", flexDirection: "column", color: "red.300" })
+ * // -> `const className = 'd_flex flex_column text_red.300'`
+ * ```
+ *
+ */
+ onlyMacroImports?: boolean
}
````
diff --git a/packages/unplugin-panda-macro/__tests__/transform.test.ts b/packages/unplugin-panda-macro/__tests__/transform.test.ts
index cb92bb849..d93381cc4 100644
--- a/packages/unplugin-panda-macro/__tests__/transform.test.ts
+++ b/packages/unplugin-panda-macro/__tests__/transform.test.ts
@@ -54,6 +54,88 @@ describe('atomic', () => {
`)
})
+ test('transform css only when using `with` import attribute', () => {
+ const ctx = createMacroContext({
+ root: '/',
+ conf: createConfigResult({}),
+ })
+ const { panda } = ctx
+ const code = `import 'virtual:panda.css'
+ import { box } from '../styled-system/patterns' with { type: "macro" }
+ import { box as box2 } from '../styled-system/patterns' with { type: "macro" }
+ import { box as box3 } from '../styled-system/patterns'
+ import { box as box4 } from '../styled-system/patterns' with { type: "invalid-macro" }
+ import { box as box5 } from '../styled-system/patterns' with { invalid: "macro" }
+
+ box({ display: 'flex' });
+ box2({ flexDirection: 'column' });
+ box3({ fontWeight: 'semibold' });
+ box4({ color: 'green.300' });
+ box5({ textAlign: 'center' });
+ box6({ textStyle: '4xl' });`
+
+ const sourceFile = panda.project.addSourceFile(id, code)
+ const parserResult = panda.project.parseSourceFile(id)
+
+ const result = tranformPanda(ctx, { code, id, output, sourceFile, parserResult, onlyMacroImports: true })
+ expect(result?.code).toMatchInlineSnapshot(`
+ "import 'virtual:panda.css'
+ import { box } from '../styled-system/patterns' with { type: "macro" }
+ import { box as box2 } from '../styled-system/patterns' with { type: "macro" }
+ import { box as box3 } from '../styled-system/patterns'
+ import { box as box4 } from '../styled-system/patterns' with { type: "invalid-macro" }
+ import { box as box5 } from '../styled-system/patterns' with { invalid: "macro" }
+
+ "d_flex";
+ "flex_column";
+ box3({ fontWeight: 'semibold' });
+ box4({ color: 'green.300' });
+ box5({ textAlign: 'center' });
+ box6({ textStyle: '4xl' });"
+ `)
+ })
+
+ test('transform css only when using with', () => {
+ const ctx = createMacroContext({
+ root: '/',
+ conf: createConfigResult({}),
+ })
+ const { panda } = ctx
+ const code = `import 'virtual:panda.css'
+ import { css } from '../styled-system/css' with { type: "macro" }
+ import { css as css2 } from '../styled-system/css' with { type: "macro" }
+ import { css as css3 } from '../styled-system/css'
+ import { css as css4 } from '../styled-system/css' with { type: "invalid-macro" }
+ import { css as css5 } from '../styled-system/css' with { invalid: "macro" }
+
+ css({ display: 'flex' });
+ css2({ flexDirection: 'column' });
+ css3({ fontWeight: 'semibold' });
+ css4({ color: 'green.300' });
+ css5({ textAlign: 'center' });
+ css6({ textStyle: '4xl' });`
+
+ const sourceFile = panda.project.addSourceFile(id, code)
+ const parserResult = panda.project.parseSourceFile(id)
+
+ const result = tranformPanda(ctx, { code, id, output, sourceFile, parserResult, onlyMacroImports: true })
+ expect(result?.code).toMatchInlineSnapshot(`
+ "import 'virtual:panda.css'
+ import { css } from '../styled-system/css' with { type: "macro" }
+ import { css as css2 } from '../styled-system/css' with { type: "macro" }
+ import { css as css3 } from '../styled-system/css'
+ import { css as css4 } from '../styled-system/css' with { type: "invalid-macro" }
+ import { css as css5 } from '../styled-system/css' with { invalid: "macro" }
+
+ "d_flex";
+ css2({ flexDirection: 'column' });
+ css3({ fontWeight: 'semibold' });
+ css4({ color: 'green.300' });
+ css5({ textAlign: 'center' });
+ css6({ textStyle: '4xl' });"
+ `)
+ })
+
test('unwrap css raw', () => {
const ctx = createMacroContext({
root: '/',
diff --git a/packages/unplugin-panda-macro/src/plugin/core.ts b/packages/unplugin-panda-macro/src/plugin/core.ts
index e1840219d..030925f7d 100644
--- a/packages/unplugin-panda-macro/src/plugin/core.ts
+++ b/packages/unplugin-panda-macro/src/plugin/core.ts
@@ -100,5 +100,6 @@ const resolveOptions = (options: PluginOptions): RequiredBy `const className = 'd_flex flex_column text_red.300'`
+ * ```
+ *
+ */
+ onlyMacroImports?: boolean
}
export interface TransformArgs extends TransformOptions {
@@ -40,7 +52,7 @@ export interface TransformArgs extends TransformOptions {
}
export const tranformPanda = (ctx: MacroContext, options: TransformArgs) => {
- const { code, id, output = 'atomic', keepRecipeClassNames, sourceFile, parserResult } = options
+ const { code, id, output = 'atomic', keepRecipeClassNames, onlyMacroImports, sourceFile, parserResult } = options
if (!parserResult) return null
const { panda, css, mergeCss, sheet, styles } = ctx
@@ -48,6 +60,8 @@ export const tranformPanda = (ctx: MacroContext, options: TransformArgs) => {
const s = new MagicString(code)
+ const importMap = onlyMacroImports ? mapIdentifierToImport(sourceFile) : new Map()
+
/**
* Hash atomic styles and inline the resulting className
*/
@@ -72,6 +86,24 @@ export const tranformPanda = (ctx: MacroContext, options: TransformArgs) => {
const node = result.box.getNode()
const fnName = result.name
+ // Early return if we only want to transform macro imports
+ // and the current result is not coming from one
+ if (onlyMacroImports) {
+ if (!fnName) return
+
+ let identifier: string | undefined
+ if ((result.type?.includes('jsx') && Node.isJsxOpeningElement(node)) || Node.isJsxSelfClosingElement(node)) {
+ identifier = node.getTagNameNode().getText()
+ } else if (Node.isCallExpression(node)) {
+ identifier = node.getExpression().getText()
+ } else {
+ return
+ }
+
+ const importNode = importMap.get(identifier)
+ if (!importNode) return
+ }
+
if (result.type?.includes('jsx')) {
const isJsx = Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node)
if (!isJsx) return
@@ -307,3 +339,51 @@ const extractCvaUsages = (sourceFile: SourceFile, cvaNames: Set) => {
return cvaUsages
}
+
+const getModuleSpecifierValue = (node: ImportDeclaration) => {
+ try {
+ return node.getModuleSpecifierValue()
+ } catch {
+ return
+ }
+}
+
+const hasMacroAttribute = (node: ImportDeclaration) => {
+ const attrs = node.getAttributes()
+ if (!attrs) return
+
+ const elements = attrs.getElements()
+ if (!elements.length) return
+
+ return elements.some((n) => {
+ const name = n.getName()
+ if (name === 'type') {
+ const value = n.getValue()
+ if (!Node.isStringLiteral(value)) return
+
+ const type = value.getLiteralText()
+ if (type === 'macro') {
+ return true
+ }
+ }
+ })
+}
+
+const mapIdentifierToImport = (sourceFile: SourceFile) => {
+ const map = new Map()
+ const imports = sourceFile.getImportDeclarations()
+
+ imports.forEach((node) => {
+ const mod = getModuleSpecifierValue(node)
+ if (!mod) return
+ if (!hasMacroAttribute(node)) return
+
+ node.getNamedImports().forEach((specifier) => {
+ const name = specifier.getNameNode().getText()
+ const alias = specifier.getAliasNode()?.getText() || name
+ map.set(alias, node)
+ })
+ })
+
+ return map
+}