Skip to content

feat: framework agnostic export#40

Open
Arsanyos wants to merge 12 commits intoolliethedev:mainfrom
Arsanyos:feat/framework-agnostic-registry-generation
Open

feat: framework agnostic export#40
Arsanyos wants to merge 12 commits intoolliethedev:mainfrom
Arsanyos:feat/framework-agnostic-registry-generation

Conversation

@Arsanyos
Copy link
Contributor

@Arsanyos Arsanyos commented Dec 27, 2025

Changes Made

  • Implemented registry-compliant item generation with full shadcn/UI schema adherence
  • Created dedicated API endpoints to fetch and process Tailwind configurations
  • Upgraded Tailwind configuration from CommonJS to ES6 module syntax ( I was forced to do this because the commonJS syntax was becoming trouble some when loading the file VIA the API )
  • Built a recursive component generator that properly handles nested structures

export

Blocker i am facing is that configuring the export for the specific framework to render the export on the entry point of that specific framework ,
I initially did it for Next , but going around doing it for the others ( vite, Next(mono) ) seemed manual , Insights on this will be awesome.
Thank you.


Note

Implements framework-agnostic export of UI Builder pages as a shadcn registry item and wires Tailwind config into the flow.

  • Adds GET /api/getTailwindConfig to serve the Tailwind config
  • Migrates tailwind.config.js to ESM (export default config) to enable server import
  • Introduces lib/registry-utils.ts to collect component deps, generate React component files, and assemble a registry item (deps/devDeps/files/tailwind)
  • Adds ExportProjectFeature to the nav bar to fetch Tailwind config and download a registry-item.json
  • Extends types with Tailwind typing and minor util cleanup; adds axios and pins next to 15.3.8 (lockfile updated)

Written by Cursor Bugbot for commit e7a4a30. This will update automatically on new commits. Configure here.

@olliethedev
Copy link
Owner

bugbot run


console.log('Registry item generated:', registryItem);
console.log('You can now use this with: npx shadcn@latest init <your-api-endpoint>');
}, [pages])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async fetch result always null due to closure capture

The tailwindConfiguration variable is declared as a local let initialized to null, and fetchTailwindConfiguration() is called directly in the component body (not in a useEffect). This causes multiple problems: the API call fires on every render, and the useCallback for exportProject captures tailwindConfiguration in its closure while it's still null (before the async call completes). The exported registry item will always have tailwindConfiguration: null regardless of the API response. The fix requires using useState to store the fetched value and useEffect to trigger the fetch, with the state variable included in the useCallback dependency array.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call actually goes through and populate the registry-item json by the time we interact with export project feature. , but it is true that it should be included in a useEffect to avoid unnecessary calls on render and store it in a useState.
Amending updates now will push in a few minutes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed 👍🏽

/**
* Generates import statements for complex components
*/
const registryDependencies: Array<string | undefined> = [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module-level array accumulates data across function calls

The registryDependencies array is declared at module scope and values are pushed to it in generateImports, but it's never cleared between calls to generateRegistryItem. Each export will include dependencies from all previous exports during the session, causing incorrect registry items with duplicated or unrelated dependencies. This array needs to be reset at the start of each generateRegistryItem call or moved to local scope.

Additional Locations (1)

Fix in Cursor Fix in Web

.get("/api/getTailwindConfig")
.then((response) => {
console.log(tailwindConfiguration, "tailwindConfig");
tailwindConfiguration = response.data.data.plugins[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracts plugin instead of full tailwind configuration

The code assigns response.data.data.plugins[0] to tailwindConfiguration, which extracts the first Tailwind plugin (a function like tailwindcss-animate) rather than the actual configuration object. The Tailwind interface expects a Config object with properties like content, theme, prefix, and extend, but a plugin function doesn't match this structure. The exported registry item will contain incorrect/unusable tailwind configuration data.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted and pushed

tailwind: tailwindConfiguration,
registryDependencies: registryDependencies
? registryDependencies
: ["card"],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty array fallback never triggers due to truthiness

The ternary registryDependencies ? registryDependencies : ["card"] will never fall back to ["card"] because registryDependencies is an array, and empty arrays are truthy in JavaScript. If the intent was to provide a default when no dependencies were collected, the condition needs to check registryDependencies.length > 0 instead.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted and pushed

@olliethedev
Copy link
Owner

Hey @Arsanyos! Thanks for the contribution 🙌

Before diving into the feedback, I want to make sure I understand the purpose of this feature correctly:

My understanding: This change allows users who have built pages with ui-builder to export them as a shadcn registry item (registry-item.json). That JSON can then be hosted and used with npx shadcn@latest init <url> to scaffold a complete React project from those pages. Is that right?


Architectural Concerns

Besides the bugbot suggestions and the CI linting check issues, I have some concerns about this approach:

1. API Route Dependency

Since ui-builder is distributed as a shadcn component, when someone runs npx shadcn add ..., shadcn only installs:

  • Component files (.tsx)
  • Dependencies listed in dependencies/registryDependencies

It does NOT install:

  • API routes (/api/getTailwindConfig)
  • Server-side infrastructure

What happens when someone installs ui-builder via shadcn add and tries to use this feature:

  1. They get nav.tsx with the ExportProjectFeature component
  2. Component calls axios.get("/api/getTailwindConfig")
  3. 404 error — the route doesn't exist in their project
  4. Export silently fails (or shows a console error)

Additionally, even if the API route existed, tailwind.config.js plugins like require("tailwindcss-animate") return functions which can't be serialized to JSON.

2. Custom Components Can't Be Exported

ui-builder's component registry <UIBuilder ... componentRegistry={{ ... }} /> allows users to provide any React component from their own project. These user-provided components can't be easily serialized into a registry-item.json — the export would only work for the built-in shadcn components, not the custom ones users have registered.


Let me know if I've misunderstood the purpose of this feature! Happy to discuss further.

@Arsanyos
Copy link
Contributor Author

Thank you for your time and giving this a look 🙌

Yes you are right , it is meant to export JSON that is compliant with shadcn registry structure and allow to scaffold a project from it.

I am giving your other comments a look now , will provide response in a bit

@Arsanyos
Copy link
Contributor Author

@olliethedev Thanks for the thoughtful review! I think there's a misunderstanding about the architecture:

Comments on the First review ( API Route Dependency )

What am thinking is like this

  1. The ui-builder is a standalone application where users design pages
  2. The export functionality (including /api/getTailwindConfig) is part of this builder app, not something that gets installed via shadcn add
  3. When a user exports from the builder, they get a registry-item.json file
  4. They can then use npx shadcn@latest init <url-to-that-json> to scaffold a new, separate project with their designed pages

The API routes never leave the builder app. The exported JSON contains serialized component data, not API routes or server infrastructure.

Hope this offer clarification on what I'm trying to build.

@Arsanyos
Copy link
Contributor Author

Arsanyos commented Dec 30, 2025

@olliethedev if am getting you right
2 ) You mean custom components by components the contains primitive elements right ? If thats the case , yes they are exportable

Screenshot 2025-12-30 at 14 45 41 the top three components are made purely using **
** and **** --- If i am missing what you are trying to convey , i'm here to be corrected.

@olliethedev
Copy link
Owner

Hey @Arsanyos, I appreciate the effort here, but I think there's still a core misunderstanding about the architecture.

There is no "builder app", ui-builder is a React component that users install into their own projects:

# User runs this in THEIR project
npx shadcn@latest add https://raw.githubusercontent.com/olliethedev/ui-builder/main/registry/block-registry.json

# This installs the component files into THEIR project:
# - components/ui/ui-builder/
# - lib/ui-builder/
# - hooks/

After installation, users import and render <UIBuilder /> inside their own Next.js/Vite/etc. app. The builder runs in their project, using their components and their Tailwind config.

Why the current approach doesn't work:

Issue What happens
/api/getTailwindConfig route Only exists in our demo site. When users install ui-builder, no API routes are added. The axios call will 404.
lib/registry-utils.ts Placed in lib/ not lib/ui-builder/, so it won't be included in the distributed component.

If you want to pursue this feature, it would need to:

  1. Be pure client-side (no API calls)
  2. Live in components/ui/ui-builder/internal/ or lib/ui-builder/ so it's distributed
  3. Extend the existing code generation in templates.ts
  4. Not depend on any specific Tailwind config (or make it optional/configurable)

The existing "Copy Code" feature already converts layers to React components client-side, this would be an extension of that pattern.

Happy to help brainstorm if you want to take a different approach!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants