Compile
Boss compile rewrites your source code. Use it in CI/CD or production mode for real builds. In dev mode it writes to a temporary copy so you can inspect the output without mutating your working files. This approach keeps Boss tooling-agnostic (no Babel/Webpack/Vite plugin required).
Boss compile is an optional build mode. It follows your selected strategy and rewrites supported JSX source ahead of your app build.
It is not a strategy.
Current scope:
- JSX source rewriting only
inline-firstandclassname-firstonly- temp mode mirrors transformed source and generated CSS under
compile.tempOutDir - prod mode mutates source in place and does not write CSS files
Quick start
npx boss-css compile
- Default output goes to
compile.tempOutDir. - Use
--prodorNODE_ENV=productionto mutate files in place. - If you use a custom config directory, the default
compile.tempOutDirfollows it (<configDir>/compiled). - Compile reads your existing strategy config. It does not replace the strategy selection in
.bo$$/config.js.
Configuration
Boss config is loaded from .bo$$/config.js plus any bo$$ key in package.json.
// .bo$$/config.js
export default {
content: ["src/**/*.{ts,tsx,js,jsx,mjs,cjs}"],
selectorPrefix: "boss-",
selectorScope: ".scope ",
stylesheetPath: ".bo$$/styles.css",
compile: {
tempOutDir: ".bo$$/compiled",
spread: true,
stats: "quick",
},
}
selectorPrefix prefixes generated class names and CSS variables.
selectorScope scopes selectors and token variables (include a trailing space for descendant selectors).
CLI flags override compile keys:
npx boss-css compile --tempOutDir .bo$$/compiled --spread true
compile.stats supports false | "quick" | "detailed" (default: "quick").
npx boss-css compile --stats detailed
Output layout
When not in prod mode, output files mirror the source tree under compile.tempOutDir.
<root>/src/app.tsx -> <root>/.bo$$/compiled/src/app.tsx
<root>/.bo$$/styles.css -> <root>/.bo$$/compiled/.bo$$/styles.css
Boundary CSS outputs are mirrored there too when CSS is generated.
Non-JS/TS files are copied in temp mode. In prod mode they are left untouched, and CSS files are not written.
Stats output
Quick:
npx boss-css compile stats (quick)
Mode: temp
Runtime free: yes
Runtime files: 0
Files processed: 42
Elements replaced: 128
Time: 1.24s
Detailed:
npx boss-css compile stats (detailed)
Mode: temp
Runtime free: no
Runtime files (2):
- src/components/ClientOnly.tsx
- src/app/layout.tsx
Files processed: 42
Files copied: 5
Files skipped: 0
Files total: 47
Elements replaced: 128
Value helper files (1):
- src/components/Padded.tsx
CSS output: /path/to/project/.bo$$/compiled/.bo$$/styles.css (2048 bytes)
Output dir: /path/to/project/.bo$$/compiled
Time: 1.24s
What compile does
- Follows your configured
inline-firstorclassname-firststrategy. - Parses JS/TS/JSX/TSX with SWC.
- Parses classname strings for boss syntax (including normal elements).
- Converts
$$JSX elements into DOM tags (inline-first or classname-first). - Rewrites
$$.$({ ... })into{ style: { ... } }to support spreads. - Rewrites
$$.$("...")into a string literal for className spreads. $$.$is a no-op marker (returns input at runtime) that tells the parser/compile to treat inputs as Boss props or className markers.- Supports
!importantin className tokens via a trailing!(for examplecolor:red!). - Rewrites className token strings like
color:$$.token.color.whiteto token shorthand (color:white). - Emits generated CSS (minified with LightningCSS) in temp mode when output is non-empty.
- Mirrors boundary CSS outputs in temp mode when boundaries are configured.
- Removes Boss runtime imports when runtime usage is no longer required.
What compile does not do (yet)
- It is not a strategy and does not add a new output mode.
- Only inline-first and classname-first strategies are supported.
- The JSX rewrite path only supports JSX today.
- Classname-first expects dynamic values to be written as functions (
prop={() => value}). - It does not guarantee that every file becomes free of Boss runtime imports. Files that still need runtime behavior keep those imports.
Runtime pruning rules
Boss imports are removed when runtime is not needed. Runtime is kept when any of these remain:
$$is used as a value that cannot be inlined (for example non-token$$references outside JSX).$$.token.*outside JSX is inlined tovar(--...).- A
$$element cannot be compiled (spread whencompile.spreadis false, or unresolved prepared components).
Input to output examples
1) Simple inline props
Input:
export const Example = () => <$$ color="red" padding={8} />
Output:
export const Example = () => <div style={{ color: "red", padding: "8px" }} />
CSS output: none (no CSS file is written when output is empty).
2) Pseudo props become class + CSS variable
Input:
export const Example = () => <$$ hover={{ color: "red" }} />
Output:
export const Example = () => (
<div className="hover:color" style={{ "--hover-color": "red" }} />
)
CSS:
.hover\:color:hover { color: var(--hover-color) }
3) Media queries with at
Input:
export const Example = () => <$$ at={{ dark: { fontStyle: "italic" } }} />
Output:
export const Example = () => (
<div className="at:dark:font-style" style={{ "--at-dark-font-style": "italic" }} />
)
CSS:
@media screen and (prefers-color-scheme: dark) {
.at\:dark\:font-style { font-style: var(--at-dark-font-style) }
}
4) Tokens become CSS variables
Input:
export const Example = () => <$$ color="white" />
Output:
export const Example = () => <div style={{ color: "var(--color-white)" }} />
CSS (default tokens):
:root { --color-white: #fff }
If you reference tokens via $$.token.*, compile inlines them to CSS variables:
export const Example = () => <$$ color={$$.token.color.white} />
5) $$.$({ ... }) marker unwrap
Input:
const props = $$.$({ color: 1 })
Output:
const props = { color: 1 }
$$.$ is a marker only; it does not generate className or style. Use it to mark Boss prop objects (for example when passing them into <$$ {...props} /> or style={$$.$({ ... })}). If you want a spreadable object with valid DOM props, use $$.style(...).
6) Spreads on $$ elements (compile.spread = true)
Input:
const domProps = $$.style({ color: 1 })
export const Example = () => <$$ {...domProps} />
Output:
const domProps = $$.style({ color: 1 })
export const Example = () => <div {...domProps} />
When compile.spread is false, the $$ element is left as-is and runtime imports are preserved.
6b) $$.$("...") className marker
Input:
const className = $$.$("display:flex hover:color:red")
export const Example = () => <div className={className} />
Output:
const className = "display:flex hover:color:red"
export const Example = () => <div className={className} />
6c) Token strings in className are normalized
Input:
export const Example = () => <div className="color:$$.token.color.white" />
Output:
export const Example = () => <div className="color:white" />
7) Existing className/style are preserved and merged
Input:
export const Example = () => (
<$$ className="card" style={{ display: "block" }} hover={{ color: "red" }} />
)
Output:
export const Example = () => (
<div className="card hover:color" style={{ display: "block", "--hover-color": "red" }} />
)
CSS:
.hover\:color:hover { color: var(--hover-color) }
8) Classname parser on normal elements
Input:
export const Example = () => <div className="display:flex hover:color:red" />
Output (JSX unchanged):
export const Example = () => <div className="display:flex hover:color:red" />
CSS:
.display\:flex { display: flex }
.hover\:color\:red:hover { color: red }
9) Grouped selector classnames
Input:
export const Example = () => <div className="hover:{color:red;text-decoration:underline}" />
Output (JSX unchanged):
export const Example = () => (
<div className="hover:color:red hover:text-decoration:underline" />
)
CSS:
.hover\:color\:red:hover { color: red }
.hover\:text-decoration\:underline:hover { text-decoration: underline }
10) Dynamic values use a helper for units
Input:
export const Example = ({ pad }: { pad: number }) => <$$ padding={pad} />
Output:
import { createBossValue } from "boss-css/compile/runtime"
const __bossValue = createBossValue("px")
export const Example = ({ pad }: { pad: number }) => (
<div style={{ padding: __bossValue(pad) }} />
)
11) Dynamic as compiles into a local component
Input:
export const Example = () => <$$ as={tag} />
Output:
export const Example = () => (function () {
const __BossCmp = tag
return <__BossCmp />
})()
12) Prepared components are inlined when available
Input:
$$.PreparedUppercaseA = $$.$({ color: "red", hover: { color: "blue" } })
export const Example = () => <$$.PreparedUppercaseA fontWeight="bold" />
Output:
export const Example = () => (
<div className="hover:color" style={{ color: "red", "--hover-color": "blue", fontWeight: "bold" }} />
)
Prepared definitions can live in any file matched by content. Compile collects them before transforming, so usages in other files can be inlined too. Cross-file inlining only happens when the prepared definition is fully static (no dynamic expressions and as is a static string). Non-static prepared definitions keep runtime behavior and the assignment remains in the output.
13) Custom CSS blocks
Input:
$$.css`
.banner { background: linear-gradient(#fff, #eee); }
`
$$.css({ ".card": { padding: 12, color: "red" } })
Output:
/* removed from JS output when used as a statement */
CSS:
.banner { background: linear-gradient(#fff, #eee); }
.card { padding: 12px; color: red }
Notes:
- Template literals must be static (no
${}expressions). - Object values must be static; nested objects are treated as selectors or
@rules. - Top-level declarations without an explicit selector are wrapped in
:root. - If
$$.css(...)is used in an expression position, it is replaced withvoid 0.
Notes
contentis required for compile to know which files to scan.- CSS output is written only when non-empty.
- Only inline-first and classname-first strategies are supported right now.
- Inline-first keeps first-level CSS props inline (deep contexts use CSS variables).
- Prepared components can be inlined across files when their definitions are static.
- Non-CSS attributes on
$$elements are preserved and not converted into CSS variables.