English | 中文
A React visual editor based on Babel AST transformation that supports editing component styles and text directly in the browser, with changes written back to source code.
With the rapid advancement of large language models, many people (including non-programmers) have started using AI to build their own Web Apps. However, AI-generated results don't always meet our expectations - sometimes you just want to change a font color but have to re-generate everything. In such cases, a simple style code change would suffice. With visual editing capabilities integrated as part of an AI-generated Web App boilerplate, you can simply switch to edit mode after AI generates the code, easily modify page styles (background color, font size, etc.), significantly reducing token consumption while achieving WYSIWYG precise control.
This repository is just a technical proof-of-concept.
- Visual Editing - Edit component styles and text directly in the browser
- Live Preview - Changes are reflected immediately
- Inline Styles - Styles are written as
style={{ ... }}in source code - Code Writeback - Automatically updates source files on save
- Hot Module Replacement - No page refresh needed
- Dynamic Content Protection - Elements containing state are marked as non-editable
-
Compile-time Injection - Vite plugin parses JSX via Babel during the transform phase, injecting unique
data-vididentifiers into each element while recording VID to source location mappings -
AST Path Positioning - Uses AST paths (e.g.,
body.4.body.body.1.argument) instead of line numbers to locate elements, ensuring accurate node targeting even after code formatting -
Runtime Editing - Identifies editable elements via
data-vidin the browser, style modifications directly manipulate DOM for live preview -
Backend AST Writeback - On save, backend looks up mapping by VID, re-parses source file into AST, locates target node and updates
styleattribute, then generates new code and writes to file -
Hot Update Sync - File write triggers Vite HMR for seamless page updates
┌─────────────────────────────────────────────────────────────────┐
│ Compile Time (Vite Plugin) │
├─────────────────────────────────────────────────────────────────┤
│ JSX Source │
│ <h1 className="title">Hello</h1> │
│ ↓ │
│ Babel AST Parse + Traverse │
│ ↓ │
│ Inject data-vid attribute + Detect dynamic content │
│ ↓ │
│ <h1 className="title" data-vid="App.tsx:h1:0">Hello</h1> │
│ │
│ Also record VID → source location mapping (vidMap) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Runtime (Browser) │
├─────────────────────────────────────────────────────────────────┤
│ User clicks element │
│ ↓ │
│ Get data-vid → Show edit panel │
│ ↓ │
│ User modifies styles → Live preview (direct el.style changes) │
│ ↓ │
│ Click save → Send request to /__visual_edit │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Backend Processing (Vite Middleware) │
├─────────────────────────────────────────────────────────────────┤
│ Receive request { vid, styles, newText } │
│ ↓ │
│ Get file path and AST path from vidMap │
│ ↓ │
│ Read source file → Babel parse → Locate JSX node │
│ ↓ │
│ Update style attribute (AST operation) │
│ ↓ │
│ Generate new code → Write to file │
│ ↓ │
│ Trigger HMR hot update │
└─────────────────────────────────────────────────────────────────┘
# Install dependencies
npm install
# Start development server
npm run devVisit http://localhost:5173
visual-edit-demo-for-vite/
├── src/
│ ├── plugins/ # Vite plugin (compile-time + backend)
│ │ ├── index.ts # Plugin entry point
│ │ └── utils/
│ │ ├── ast.ts # AST operation utilities
│ │ └── types.ts # Type definitions
│ ├── utils/
│ │ └── overlay/ # Frontend edit interface
│ │ ├── index.ts # Export entry
│ │ ├── domElements.ts # UI element creation
│ │ └── visualEditOverlay.ts # Interaction logic
│ ├── App.tsx # Demo application
│ ├── main.tsx # App entry point
│ └── index.css # Global styles
├── vite.config.ts # Vite configuration
└── package.json
The plugin processes JSX files in Vite's transform hook:
// Main functions:
// 1. Inject data-vid attribute into each JSX element
// 2. Detect dynamic content, mark with data-has-state
// 3. Build VID → source location mapping
// 4. Provide /__visual_edit API endpoint for edit requestsVID Format: filename:elementName:index, e.g., App.tsx:h1:0
VID Map Structure:
interface VidMapEntry {
file: string; // Source file path
astPath: string; // AST navigation path
elementName: string; // Element name
classLiteralStart: number;
classLiteralEnd: number;
}Dynamic Content Detection:
// Check if JSX children contain expressions
// <button>{count}</button> → data-has-state="true"
for (const child of jsxElement.children) {
if (t.isJSXExpressionContainer(child)) {
hasDynamicContent = true;
}
}Provides precise source code modification capabilities:
// Core functions:
// Generate AST path (compile-time)
generateASTPath(path) → "body.4.body.body.1.argument.children.3"
// Locate node by path (runtime)
findNodeByPath(ast, pathString) → JSXElement
// Update code
updateCodeWithAST(code, astPath, {
newClassName?: string,
newText?: string,
newStyles?: StyleConfig // inline styles
})Style Update Logic:
// Update or create style attribute
// If style={{ ... }} exists, merge styles
// Example output:
// style={{ color: "#ff0000", fontSize: "18px" }}domElements.ts - UI element factory:
createHighlightBox() // Highlight box
createFileLabel() // Element name label
createToggleButton() // Edit mode toggle button
createStylePanel() // Right-side drawer panel
showToast() // Toast notificationsvisualEditOverlay.ts - Interaction logic:
// State management
let enabled = false;
let currentEditingElement: HTMLElement | null = null;
// Core functions
highlight(el) // Highlight element
enterEditMode(el) // Enter edit mode
exitEditMode() // Exit edit mode
saveStyles() // Save to backendUser clicks save
↓
Collect data: { vid, styles, newText }
↓
POST /__visual_edit
↓
Backend parses vid → Get VidMapEntry
↓
Read source file → Parse to AST with Babel
↓
findNodeByPath() locates JSX node
↓
updateStyleAttribute() updates style attribute
↓
generate() produces new code
↓
Write to file → Trigger HMR
Request Body:
interface EditRequest {
vid: string;
currentClassName?: string;
newText?: string;
styles?: {
color?: string;
backgroundColor?: string;
fontSize?: string;
fontWeight?: string;
};
}Response:
{ "ok": true }| Category | Technology |
|---|---|
| UI Framework | React 19 |
| Build Tool | Vite 7 |
| AST Parsing | @babel/parser |
| AST Traversal | @babel/traverse |
| AST Generation | @babel/generator |
| Type System | TypeScript |
| Styling | Tailwind CSS 4 |
- Development Mode Only - Edit functionality is only available when running
npm run dev - Version Control - Changes are written directly to source files, Git usage is recommended
- Dynamic Content - Elements containing
{state}expressions cannot be edited - Text Elements - Only supports p, span, button, h1-h6, a, label, div and similar text elements
MIT