mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 14:52:55 +01:00
425 lines
16 KiB
Markdown
425 lines
16 KiB
Markdown
# Code Export: Why It's Hard and What We Can Do Instead
|
|
|
|
## The Question Everyone Asks
|
|
|
|
"Can I export my Noodl project as a regular React codebase?"
|
|
|
|
It's one of the most common feature requests, and for good reason. The appeal is obvious:
|
|
|
|
- **No vendor lock-in** - Know you can leave anytime
|
|
- **Developer handoff** - Give your codebase to a React team
|
|
- **Standard tooling** - Use React DevTools, any bundler, any hosting
|
|
- **Smaller bundles** - Ship React code, not JSON + interpreter
|
|
- **Peace of mind** - Your work isn't trapped in a proprietary format
|
|
|
|
We hear you. This document explains why full code export is genuinely difficult, and proposes a practical alternative that delivers most of the value.
|
|
|
|
## How Noodl Actually Works
|
|
|
|
To understand why code export is hard, you need to understand what Noodl is doing under the hood.
|
|
|
|
When you build in Noodl, you're not writing React code—you're creating a **graph of nodes and connections**. This graph is saved as JSON and interpreted at runtime:
|
|
|
|
```
|
|
Your Noodl Project What Gets Deployed
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ │ │ project.json │ (your node graphs)
|
|
│ Visual Editor │ ──────▶ │ + │
|
|
│ (Node Graphs) │ │ noodl-runtime │ (interprets the JSON)
|
|
│ │ │ + │
|
|
└─────────────────┘ │ react.js │ (renders the UI)
|
|
└─────────────────┘
|
|
```
|
|
|
|
The runtime reads your JSON and dynamically creates React components, wires up connections, and executes logic. This is powerful and flexible, but it means there's no "React code" to export—just data that describes what the code should do.
|
|
|
|
**Code export would mean building a compiler** that transforms this graph representation into equivalent React source code.
|
|
|
|
## What Makes This Hard
|
|
|
|
### The Easy Parts
|
|
|
|
Some Noodl concepts translate cleanly to React:
|
|
|
|
| Noodl | React | Difficulty |
|
|
|-------|-------|------------|
|
|
| Group, Text, Image nodes | `<div>`, `<span>`, `<img>` | Straightforward |
|
|
| Component hierarchy | Component tree | Straightforward |
|
|
| Props passed between components | React props | Straightforward |
|
|
| Basic styling | CSS/Tailwind classes | Straightforward |
|
|
| Repeater node | `array.map()` | Moderate |
|
|
| Page Router | React Router | Moderate |
|
|
| States (hover, pressed, etc.) | `useState` + event handlers | Moderate |
|
|
|
|
If Noodl were purely a UI builder, code export would be very achievable.
|
|
|
|
### The Hard Parts
|
|
|
|
The challenge is Noodl's **logic and data flow system**. This is where the visual programming model diverges significantly from how React thinks.
|
|
|
|
#### The Signal System
|
|
|
|
In Noodl, you connect outputs to inputs, and "signals" flow through the graph:
|
|
|
|
```
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
│ Button │────▶│ Counter │────▶│ Text │
|
|
│ Click ○─┼────▶│─○ Add │ │─○ Value │
|
|
└─────────┘ │ Count ○┼────▶│ │
|
|
└─────────┘ └─────────┘
|
|
```
|
|
|
|
When Button emits "Click", Counter receives "Add", increments, and emits "Count", which Text receives as "Value".
|
|
|
|
This is intuitive in the visual editor. But what's the React equivalent?
|
|
|
|
```jsx
|
|
// Option A: useEffect chains (gets messy fast)
|
|
function MyComponent() {
|
|
const [clicked, setClicked] = useState(false);
|
|
const [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (clicked) {
|
|
setCount(c => c + 1);
|
|
setClicked(false); // reset the "signal"
|
|
}
|
|
}, [clicked]);
|
|
|
|
return (
|
|
<>
|
|
<button onClick={() => setClicked(true)}>Add</button>
|
|
<span>{count}</span>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Option B: Direct handlers (loses the graph-like flow)
|
|
function MyComponent() {
|
|
const [count, setCount] = useState(0);
|
|
return (
|
|
<>
|
|
<button onClick={() => setCount(c => c + 1)}>Add</button>
|
|
<span>{count}</span>
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
Option B is cleaner, but it's a **complete restructuring** of how the logic is expressed. The compiler would need to understand the *intent* of your node graph, not just translate it mechanically.
|
|
|
|
Now imagine this with 50 nodes, branching conditions, and signals that trigger other signals. The generated code either becomes an unreadable mess of `useEffect` chains, or requires sophisticated analysis to restructure into idiomatic React.
|
|
|
|
#### Logic Nodes
|
|
|
|
Noodl has nodes like And, Or, Switch, Condition, Expression. These operate on the signal/value flow model:
|
|
|
|
```
|
|
┌─────────┐
|
|
│ Value A │──┐ ┌─────────┐
|
|
└─────────┘ ├────▶│ And │────▶ Result
|
|
┌─────────┐ │ └─────────┘
|
|
│ Value B │──┘
|
|
└─────────┘
|
|
```
|
|
|
|
In React, this might be:
|
|
- A derived value: `const result = valueA && valueB`
|
|
- A `useMemo`: `useMemo(() => valueA && valueB, [valueA, valueB])`
|
|
- Part of render logic: `{valueA && valueB && <Thing />}`
|
|
|
|
The "right" choice depends on context. A compiler would need to analyze the entire graph to decide.
|
|
|
|
#### Function Nodes (Custom JavaScript)
|
|
|
|
When you write custom JavaScript in Noodl, you're writing code that interacts with Noodl's runtime APIs:
|
|
|
|
```javascript
|
|
// Inside a Noodl Function node
|
|
define({
|
|
inputs: { value: 'number' },
|
|
outputs: { doubled: 'number' },
|
|
|
|
run() {
|
|
this.outputs.doubled = this.inputs.value * 2;
|
|
}
|
|
});
|
|
```
|
|
|
|
This code assumes Noodl's execution model. Translating it to a React hook or component requires understanding what `this.inputs`, `this.outputs`, and `run()` mean in the broader context.
|
|
|
|
#### Database and Cloud Nodes
|
|
|
|
Nodes like Query Records, Create Record, and Cloud Function are deeply integrated with Noodl's backend services. They handle:
|
|
- Authentication state
|
|
- Caching
|
|
- Optimistic updates
|
|
- Error handling
|
|
- Retry logic
|
|
|
|
Exporting these as code would mean either:
|
|
- Generating a lot of boilerplate API code
|
|
- Requiring a companion library (at which point, you still have a "runtime")
|
|
|
|
### The Maintenance Problem
|
|
|
|
Even if we built a compiler, we'd now have **two systems that must behave identically**:
|
|
|
|
1. The runtime (interprets JSON in the browser)
|
|
2. The compiler (generates React code)
|
|
|
|
Every bug fix, every new feature, every edge case would need to be implemented twice and tested for parity. This is a significant ongoing maintenance burden.
|
|
|
|
## What We Propose Instead: The "Eject" Feature
|
|
|
|
Rather than promising perfect code export, we're considering an **"Eject" feature** that's honest about its limitations but still genuinely useful.
|
|
|
|
### The Concept
|
|
|
|
Export your project as a React codebase with:
|
|
- ✅ **Clean, readable code** for all UI components
|
|
- ✅ **Proper React patterns** (hooks, components, props)
|
|
- ✅ **Extracted styles** (CSS modules or Tailwind)
|
|
- ✅ **Project structure** (routing, file organization)
|
|
- ⚠️ **TODO comments** for logic that needs manual implementation
|
|
- ⚠️ **Placeholder functions** for database operations
|
|
|
|
### What It Would Look Like
|
|
|
|
Your Noodl component:
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ UserCard │
|
|
├─────────────────────────────────────────┤
|
|
│ ┌─────────┐ ┌─────────┐ ┌────────────┐ │
|
|
│ │ Image │ │ Text │ │ Button │ │
|
|
│ │ avatar │ │ name │ │ "Edit" │ │
|
|
│ └─────────┘ └─────────┘ └──────┬─────┘ │
|
|
│ │ │
|
|
│ ┌─────▼─────┐ │
|
|
│ │ Function │ │
|
|
│ │ editUser │ │
|
|
│ └───────────┘ │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
Exported as:
|
|
|
|
```jsx
|
|
// components/UserCard/UserCard.jsx
|
|
|
|
import React from 'react';
|
|
import styles from './UserCard.module.css';
|
|
|
|
export function UserCard({ avatar, name, userId }) {
|
|
|
|
const handleEdit = () => {
|
|
// TODO: Implement edit logic
|
|
// Original Noodl Function node contained:
|
|
// ─────────────────────────────────────
|
|
// this.outputs.navigate = `/users/${this.inputs.userId}/edit`;
|
|
// ─────────────────────────────────────
|
|
console.warn('UserCard.handleEdit: Not yet implemented');
|
|
};
|
|
|
|
return (
|
|
<div className={styles.userCard}>
|
|
<img
|
|
src={avatar}
|
|
alt={name}
|
|
className={styles.avatar}
|
|
/>
|
|
<span className={styles.name}>{name}</span>
|
|
<button
|
|
onClick={handleEdit}
|
|
className={styles.editButton}
|
|
>
|
|
Edit
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
```css
|
|
/* components/UserCard/UserCard.module.css */
|
|
|
|
.userCard {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 16px;
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.name {
|
|
flex: 1;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.editButton {
|
|
padding: 8px 16px;
|
|
background: #3b82f6;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
```
|
|
|
|
### Database Operations
|
|
|
|
For database nodes, we'd generate a clear interface:
|
|
|
|
```jsx
|
|
// services/api.js
|
|
|
|
/**
|
|
* Auto-generated API service
|
|
* TODO: Implement these functions with your backend of choice
|
|
*/
|
|
|
|
export const api = {
|
|
/**
|
|
* Fetches users from the database
|
|
*
|
|
* Original Noodl Query:
|
|
* Collection: Users
|
|
* Filter: { role: 'admin' }
|
|
* Sort: createdAt (descending)
|
|
* Limit: 20
|
|
*/
|
|
async getUsers() {
|
|
// TODO: Implement with your API
|
|
// Example with fetch:
|
|
// return fetch('/api/users?role=admin&limit=20').then(r => r.json());
|
|
|
|
throw new Error('api.getUsers: Not implemented');
|
|
},
|
|
|
|
/**
|
|
* Creates a new user record
|
|
*
|
|
* Original Noodl fields:
|
|
* - name (string)
|
|
* - email (string)
|
|
* - role (string)
|
|
*/
|
|
async createUser(data) {
|
|
// TODO: Implement with your API
|
|
throw new Error('api.createUser: Not implemented');
|
|
},
|
|
};
|
|
```
|
|
|
|
### Export Report
|
|
|
|
After export, you'd receive a report:
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ Export Complete │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ✅ Exported successfully to: ./my-app-export/ │
|
|
│ │
|
|
│ Summary: │
|
|
│ ──────────────────────────────────────────────────────── │
|
|
│ Components exported: 23 │
|
|
│ Styles extracted: 23 │
|
|
│ Routes configured: 5 │
|
|
│ │
|
|
│ ⚠️ Manual work required: │
|
|
│ ──────────────────────────────────────────────────────── │
|
|
│ Function nodes: 7 (see TODO comments) │
|
|
│ Database operations: 12 (see services/api.js) │
|
|
│ Cloud functions: 3 (see services/cloud.js) │
|
|
│ │
|
|
│ Next steps: │
|
|
│ 1. Run: cd my-app-export && npm install │
|
|
│ 2. Search for "TODO" comments in your editor │
|
|
│ 3. Implement the placeholder functions │
|
|
│ 4. Run: npm run dev │
|
|
│ │
|
|
│ 📖 Full guide: docs.opennoodl.com/guides/code-export │
|
|
│ │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Who Is This For?
|
|
|
|
The Eject feature would be valuable for:
|
|
|
|
### Prototyping → Production Handoff
|
|
Build your MVP in Noodl, validate with users, then hand the codebase to your engineering team for production development.
|
|
|
|
### Outgrowing Low-Code
|
|
Your project has become complex enough that you need full code control. Export what you have and continue in a traditional development environment.
|
|
|
|
### Learning Tool
|
|
See how your visual designs translate to React code. Great for designers learning to code or developers understanding React patterns.
|
|
|
|
### Component Libraries
|
|
Build UI components visually in Noodl, export them for use in other React projects.
|
|
|
|
## What This Is NOT
|
|
|
|
To be completely clear:
|
|
|
|
- ❌ **Not round-trip** - You cannot re-import exported code back into Noodl
|
|
- ❌ **Not "zero effort"** - You'll need a developer to complete the TODOs
|
|
- ❌ **Not production-ready** - The exported code is a starting point, not a finished product
|
|
- ❌ **Not a replacement for Noodl** - If you want visual development, keep using Noodl!
|
|
|
|
## Comparison: Full Export vs. Eject
|
|
|
|
| Aspect | Full Code Export | Eject Feature |
|
|
|--------|------------------|---------------|
|
|
| Development effort | 6-12 months | 4-6 weeks |
|
|
| UI components | ✅ Complete | ✅ Complete |
|
|
| Styling | ✅ Complete | ✅ Complete |
|
|
| Routing | ✅ Complete | ✅ Complete |
|
|
| Simple logic | ✅ Complete | ⚠️ Best-effort |
|
|
| Complex logic | ✅ Complete | 📝 TODO comments |
|
|
| Database operations | ✅ Complete | 📝 Placeholder stubs |
|
|
| Code quality | Varies (could be messy) | Clean (humans finish it) |
|
|
| Maintenance burden | High (two systems) | Low (one-time export) |
|
|
| Honesty | Promises a lot | Clear expectations |
|
|
|
|
## The Bottom Line
|
|
|
|
We could spend a year building a compiler that produces questionable code for edge cases, or we could spend a few weeks building an export tool that's honest about what it can and can't do.
|
|
|
|
The Eject feature acknowledges that:
|
|
1. Visual development and code development are different paradigms
|
|
2. The best code is written by humans who understand the context
|
|
3. Getting 80% of the way there is genuinely useful
|
|
4. Clear documentation beats magic that sometimes fails
|
|
|
|
We think this approach respects both your time and your intelligence.
|
|
|
|
## We Want Your Input
|
|
|
|
This feature is in the planning stage. We'd love to hear from you:
|
|
|
|
- Would the Eject feature be useful for your workflow?
|
|
- What would you use it for? (Handoff? Learning? Components?)
|
|
- What's the minimum viable version that would help you?
|
|
- Are there specific node types you'd want prioritized?
|
|
|
|
Join the discussion: [Community Link]
|
|
|
|
---
|
|
|
|
*This document reflects our current thinking and is subject to change based on community feedback and technical discoveries.*
|