Core Concepts
Compiler
The @nucleify/compiler package is how Nucleify migrates UI across shells: an IR-first portable compiler that turns *.nuc.tsx into Vue SFCs, React and Solid components, and shared CSS — and can convert entire Nuxt product shells (web/, admin/) to Next.js or Solid.
Coverage: Vue · React · Nuxt · Next · Astro · Svelte · Solid. React/Next or Vue/Nuxt are beachheads, not the boundary.
Two operating modes
| Mode | What it does | Example |
|---|---|---|
| Portable emit | *.nuc.tsx → IR → .vue + React .tsx + Solid .tsx + .css |
pnpm compiler:build / make solid |
| Product convert | Nuxt app → Next.js or Solid app | pnpm compiler -- convert web --target=next |
Portable emit is for presentational components. Product convert scaffolds web-next/, admin-next/, web-solid/, or admin-solid/ from the Vue source tree — same routes, shared modules, different framework shell.
Portable authoring (*.nuc.tsx)
Author framework-agnostic UI with #nuc-compiler/runtime:
import { component, state, handler } from '#nuc-compiler/runtime'
export default component({
name: 'Counter',
props: { label: { type: 'string', default: 'Count' } },
setup(props) {
const count = state(0)
const onInc = handler(() => count.set(count.value + 1))
return () => (
<button type="button" onClick={onInc}>
{props.label}: {count.value}
</button>
)
},
})Running pnpm compiler:build emits siblings next to the source:
portable/components/
├── Counter.nuc.tsx # source (committed)
├── Counter.vue # generated Vue SFC
├── Counter.tsx # generated React component
└── Counter.css # extracted stylesAllowed in portable components
- Serializable props (
string,number,boolean,unknown) - HTML elements and
nui-*Lit custom elements - Local state via
state,derived,handler - Plain CSS string or
style={{ … }}object binds - Default slot /
childrenonly
Forbidden
- Vue/React framework APIs (
defineComponent, hooks as host coupling) - Stores, routers, i18n, Supabase, Pinia/Zustand inside portable files
- Named slots, portals, dynamic components, async setup
Full rules: compiler/PORTABLE.md.
CLI reference
pnpm compiler:build # emit all *.nuc.tsx
pnpm compiler:check # verify fingerprints (exit 1 if dirty)
pnpm compiler:test # golden-file test suite
pnpm compiler -- import --from=vue path/Foo.vue # import emit edits back to source
pnpm compiler -- import --from=react path/Foo.tsx
pnpm compiler -- build --force # discard emit edits; regenerate
pnpm compiler -- convert web --target=next # product convert → web-next/
pnpm compiler -- convert admin --target=next # product convert → admin-next/
pnpm compiler -- convert web --target=solid # product convert → web-solid/
pnpm compiler -- scaffold solid # throwaway demo → solid/demo/
pnpm compiler -- scaffold next # throwaway demo → next/demo/
pnpm compiler -- build --app=solid # emit into solid/demoSkip compiler during bootstrap:
SKIP_COMPILER=1 make runCycle A — author first
- Create or edit
Foo.nuc.tsx - Run
pnpm compiler:build - Commit source + generated siblings
- CI runs
pnpm compiler:checkto ensure emit is up to date
Cycle B — edit emit first
- Tweak generated
Foo.vueorFoo.tsxdirectly - Import changes back:
pnpm compiler -- import --from=vue path/Foo.vue - Source of truth returns to
Foo.nuc.tsx
If both .vue and .tsx are dirty, you must pass --from=. Use pnpm compiler -- build --force to discard emit edits.
Package layout
compiler/
├── src/
│ ├── parse/ # *.nuc.tsx → AST
│ ├── ir/ # intermediate representation
│ ├── emit/ # IR → .vue / React .tsx / Solid .tsx / .css
│ └── sync/ # import back, convert product shells
├── runtime/ # #nuc-compiler/runtime
├── templates/ # scaffold sources for gitignored demos
├── tests/ # golden-file tests
├── fixtures/
└── PORTABLE.md # authoring rulesGitignored emit demos: {vue,react,nuxt,next,solid}/demo/ (via make vue, make next, make solid, etc.).
Product shell conversion
make web TARGET=next
# equivalent:
pnpm compiler -- convert web --target=next
cd web-next && pnpm dev
make web TARGET=solid
pnpm compiler -- convert web --target=solidThe converter reads the Nuxt source tree in web/, maps pages and composables to React (or Solid) equivalents, and writes output to web-next/ or web-solid/. Shared modules are consumed via framework barrels.
Treat web-next/, admin-next/, web-solid/, and admin-solid/ as generated output. Develop in web/ or admin/, then convert.
Runtime alias
Nuxt resolves the runtime through web/.config/nuxt/structure.ts:
'#nuc-compiler/runtime': resolve(process.cwd(), '../compiler/runtime/index.ts')Runtime helpers (component, state, derived, handler) are compile-time markers — they must not appear in emitted bundles.
Testing
Compiler tests live in compiler/tests/ with a dedicated Vitest config (.config/vitest.compiler.config.ts). Golden tests compare emit output against fixtures in compiler/tests/fixtures/.
pnpm compiler:test
pnpm compiler:checkSee Vitest for the full testing setup.
Related docs
- Monorepo Layout — where compiler fits in the workspace
- Quick Start — build your first portable component
- Web & Admin — Nuxt config that wires the runtime alias