20 KiB
BadGUI Workflow Analysis: From Python to Vue.js
Overview
BadGUI is a Python framework that generates complete Vue.js/Quasar projects using a NiceGUI-like syntax. This document traces the complete workflow from Python code to a running Vue.js application.
Table of Contents
- Python Code Structure
- Component Creation and Storage
- Page Management
- Build Process
- Vue.js Generation
- Project Structure Creation
- Development Server
- Areas for Improvement
1. Python Code Structure
Example Input Code
import badgui as bg
with bg.page("/", "HomePage", "Home"):
with bg.column() as main_col:
main_col.classes("q-pa-lg q-gutter-md")
bg.label("Hello BadGUI!").classes("text-h3")
name_input = bg.input("Enter your name")
name_input.bind_value("userName")
bg.button("Greet").on_click("greetUser").classes("q-btn-primary")
bg.script("""
const userName = ref('');
const greetUser = () => {
$q.notify(`Hello, ${userName.value}!`);
};
""")
bg.dev(port=3000)
Key Framework Entry Points
File: badgui/__init__.py
from .core import App
# Main app instance
app = App()
# Convenience functions that delegate to the main app
def label(text: str, **kwargs):
return app.label(text, **kwargs)
def button(text: str, icon: str = None, color: str = 'primary', **kwargs):
return app.button(text, icon, color, **kwargs)
Analysis: All component functions are convenience wrappers around a singleton App instance. This centralizes state management but could limit flexibility for multiple app instances.
2. Component Creation and Storage
Component Class Structure
File: badgui/core.py
class Component:
def __init__(self, component_type: str, **props):
self.component_type = component_type
self.id = f"{component_type}_{id(self)}"
self._props = props
self._classes = []
self._style = {}
self.children = []
self._vue_ref = None
def classes(self, class_names: Union[str, List[str]]):
if isinstance(class_names, str):
self._classes = class_names.split()
else:
self._classes = class_names
return self
def vue_ref(self, ref_name: str):
self._vue_ref = ref_name
self._props['ref'] = ref_name
return self
Analysis: Components use method chaining for fluent API. Each component gets a unique ID based on memory address. Props, classes, and styles are stored separately but could be unified.
Component Addition Process
File: badgui/core.py
class App:
def _add_component(self, component: Component) -> Component:
page = self._get_current_page()
# Add to current container (if in context) or page
if page.current_container:
page.current_container.children.append(component)
else:
page.components.append(component)
return component
def label(self, text: str, **kwargs) -> Component:
component = Component('label', text=text, **kwargs)
return self._add_component(component)
Analysis: Components are added to either the current page or the current container (for nested structures). The container stack manages nesting levels.
3. Page Management
Page Class and Context Management
File: badgui/core.py
class Page:
def __init__(self, path: str, name: str, title: str = None):
self.path = path
self.name = name
self.title = title or name
self.components = []
self.current_container = None
self._container_stack = []
self._scripts = []
class App:
def __init__(self):
self.pages = {}
self.current_page = None
@contextmanager
def page(self, path: str, name: str = None, title: str = None):
# Create or get existing page
page = Page(path, name or self._generate_page_name(path), title)
self.pages[path] = page
# Set as current page
old_page = self.current_page
self.current_page = page
try:
yield page
finally:
self.current_page = old_page
Analysis: Pages use context managers to maintain state. The current page is tracked globally, which could cause issues with concurrent page creation.
Container Context Management
@contextmanager
def column(self, **kwargs):
component = Component('column', classes=['column'], **kwargs)
component = self._add_component(component)
# Enter container context
page = self._get_current_page()
page._container_stack.append(page.current_container)
page.current_container = component
try:
yield component
finally:
# Exit container context
page.current_container = page._container_stack.pop()
Analysis: Container nesting uses a stack-based approach. This works well but could be error-prone if exceptions occur during context management.
4. Build Process
Build Initiation
File: badgui/core.py
def build(self, output_dir: str = "./badgui-output", project_name: str = "badgui-app", **kwargs):
from .generator import VueGenerator
print(f"🔧 Building BadGUI project...")
print(f"📁 Output directory: {output_dir}")
generator = VueGenerator(self)
generator.generate_project(output_dir, project_name, **kwargs)
print("✅ BadGUI project generated successfully!")
Analysis: Build process is straightforward but lacks error handling and progress reporting for large projects.
Generator Initialization
File: badgui/generator.py
class VueGenerator:
def __init__(self, app: App):
self.app = app
def generate_project(self, output_dir: str, project_name: str = "badgui-app", **kwargs):
os.makedirs(output_dir, exist_ok=True)
self._create_project_structure(output_dir, project_name)
self._generate_package_json(output_dir, project_name)
self._generate_quasar_config(output_dir)
self._generate_main_layout(output_dir)
self._generate_pages(output_dir)
self._generate_router(output_dir)
self._generate_app_vue(output_dir)
self._generate_boot_files(output_dir)
self._generate_css_files(output_dir)
self._copy_static_files(output_dir)
Analysis: Generation follows a fixed sequence. Each step is independent, which is good for maintainability but doesn't allow for conditional generation based on features used.
5. Vue.js Generation
Component-to-Vue Template Conversion
File: badgui/core.py
def to_vue_template(self, indent: int = 0) -> str:
spaces = " " * indent
# Build attributes
attrs = []
attrs.append(f'id="{self.id}"')
if self._classes:
attrs.append(f'class="{" ".join(self._classes)}"')
# Add other props
for key, value in self._props.items():
if key not in ['classes', 'style', 'id']:
if isinstance(value, bool) and value:
attrs.append(key)
elif isinstance(value, str):
attrs.append(f'{key}="{value}"')
attrs_str = " " + " ".join(attrs)
vue_tag = self.get_vue_tag()
# Generate template
if self.children:
template = f"{spaces}<{vue_tag}{attrs_str}>\n"
for child in self.children:
template += child.to_vue_template(indent + 1)
template += f"{spaces}</{vue_tag}>\n"
else:
content = self.get_content()
if content:
template = f"{spaces}<{vue_tag}{attrs_str}>{content}</{vue_tag}>\n"
else:
template = f"{spaces}<{vue_tag}{attrs_str} />\n"
return template
def get_vue_tag(self) -> str:
component_map = {
'label': 'q-item-label',
'button': 'q-btn',
'input': 'q-input',
'column': 'div',
'card': 'q-card',
'dialog': 'q-dialog',
'div': 'div'
}
return component_map.get(self.component_type, self.component_type)
Analysis: Template generation is recursive and handles nested components well. The component mapping is hardcoded, which makes adding new components require code changes.
Page Component Generation
File: badgui/generator.py
def _generate_page_component(self, output_dir: str, page):
template_content = self._generate_vue_template_for_page(page)
script_imports, script_content = self._process_page_scripts(page, output_dir)
page_content = f'''<template>
<q-page class="row items-center justify-evenly">
{template_content}
</q-page>
</template>
<script setup>
import {{ ref, onMounted, watch }} from 'vue'
import {{ useQuasar }} from 'quasar'
{script_imports}
// Component state
const state = ref({{}})
const $q = useQuasar()
// Dialog state for Quasar dialogs
const dialogOpen = ref(false)
// Make window object available to template
const window = globalThis.window
{script_content}
</script>'''
page_path = os.path.join(output_dir, 'src', 'pages', f'{page.name}.vue')
with open(page_path, 'w') as f:
f.write(page_content)
Analysis: Page generation is template-based with string formatting. This approach works but is fragile and hard to modify. A proper template engine would be more robust.
Script Processing
def _process_page_scripts(self, page, output_dir: str) -> tuple:
script_imports = ""
script_content = ""
# Collect Vue refs
vue_refs = self._collect_vue_refs(page)
if vue_refs:
for ref_name in vue_refs:
script_content += f"\n// Vue ref for component\nconst {ref_name} = ref(null);\n"
# Process script components
if hasattr(page, '_scripts'):
for script_component in page._scripts:
props = script_component._props
if props.get('_is_inline'):
content = props.get('_script_content', '')
script_content += f"\n// Inline script\n{content}\n"
elif props.get('_external_file'):
filename = props.get('_external_file')
import_mode = props.get('_import_mode', False)
if import_mode:
# ES6 module import
utils_dir = os.path.join(output_dir, 'src', 'utils')
os.makedirs(utils_dir, exist_ok=True)
module_name = os.path.splitext(filename)[0]
script_imports += f"import * as {module_name} from '../utils/{filename}';\n"
else:
# Traditional script tag
js_dir = os.path.join(output_dir, 'public', 'js')
os.makedirs(js_dir, exist_ok=True)
return script_imports, script_content
Analysis: Script processing handles both inline and external scripts with ES6 module support. The dual approach (traditional vs module imports) adds complexity but provides flexibility.
6. Project Structure Creation
Package.json Generation
File: badgui/generator.py
def _generate_package_json(self, output_dir: str, project_name: str):
package_json = {
"name": project_name,
"version": "0.0.1",
"scripts": {
"dev": "quasar dev",
"build": "quasar build"
},
"dependencies": {
"@quasar/extras": "^1.16.4",
"quasar": "^2.12.0",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@quasar/app-vite": "^1.3.0",
"typescript": "^5.1.6"
}
}
with open(os.path.join(output_dir, 'package.json'), 'w') as f:
json.dump(package_json, f, indent=2)
Analysis: Dependencies are hardcoded with specific versions. This ensures consistency but makes updates difficult and doesn't allow for feature-based dependency inclusion.
Router Generation
def _generate_router(self, output_dir: str):
page_routes = []
for path, page in self.app.pages.items():
route = f" {{ path: '{path}', component: () => import('pages/{page.name}.vue') }}"
page_routes.append(route)
routes_content = f'''const routes = [
{{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{",".join([chr(10) + route for route in page_routes])}
]
}}
]
export default routes'''
Analysis: Router generation is dynamic based on defined pages. This works well for basic routing but doesn't support advanced features like route guards or nested routing.
7. Development Server
Dev Server Implementation
File: badgui/core.py
def dev(self, port: int = 9000, host: str = "localhost", auto_reload: bool = False,
project_name: str = "badgui-dev", **kwargs):
import tempfile
import subprocess
# Create temporary build
temp_dir = tempfile.mkdtemp(prefix="badgui_dev_")
print(f"🔧 Creating temporary build...")
print(f"📁 Temp directory: {temp_dir}")
self.build(temp_dir, project_name, **kwargs)
# Install dependencies and run dev server
print("📦 Installing dependencies...")
subprocess.run(["npm", "install"], cwd=temp_dir, check=True, capture_output=True)
print(f"🚀 Starting dev server at http://{host}:{port}")
env = os.environ.copy()
env['PORT'] = str(port)
env['HOST'] = host
try:
subprocess.run(["npm", "run", "dev"], cwd=temp_dir, env=env, check=True)
except KeyboardInterrupt:
print("\n🛑 Dev server stopped")
finally:
# Cleanup temp directory
shutil.rmtree(temp_dir, ignore_errors=True)
Analysis: Dev server creates temporary builds, which is inefficient for development. A proper dev server would support hot reloading and incremental builds.
8. Areas for Improvement
8.1 Architecture Improvements
Issue: Singleton App Instance
Current Code:
# badgui/__init__.py
app = App() # Global singleton
def label(text: str, **kwargs):
return app.label(text, **kwargs)
Problem: Single global instance limits flexibility and testing.
Improvement: Support multiple app instances:
class BadGUI:
def __init__(self):
self.app = App()
def label(self, text: str, **kwargs):
return self.app.label(text, **kwargs)
# Default instance for convenience
bg = BadGUI()
label = bg.label
Issue: Component Mapping Hardcoded
Current Code:
def get_vue_tag(self) -> str:
component_map = {
'label': 'q-item-label',
'button': 'q-btn',
# ... hardcoded mapping
}
return component_map.get(self.component_type, self.component_type)
Improvement: Plugin-based component system:
class ComponentRegistry:
def __init__(self):
self._components = {}
def register(self, component_type: str, vue_tag: str, props_mapper=None):
self._components[component_type] = {
'vue_tag': vue_tag,
'props_mapper': props_mapper
}
def get_vue_tag(self, component_type: str) -> str:
return self._components.get(component_type, {}).get('vue_tag', component_type)
8.2 Performance Improvements
Issue: Inefficient Dev Server
Current Code:
def dev(self, port: int = 9000, **kwargs):
temp_dir = tempfile.mkdtemp(prefix="badgui_dev_")
self.build(temp_dir, project_name, **kwargs) # Full rebuild every time
subprocess.run(["npm", "install"], cwd=temp_dir) # Reinstall dependencies
Improvement: Incremental builds and dependency caching:
class DevServer:
def __init__(self, app: App):
self.app = app
self._build_cache = {}
self._dependency_cache = None
def start(self, port: int = 9000):
if not self._needs_full_rebuild():
self._incremental_build()
else:
self._full_build()
if not self._dependency_cache:
self._install_dependencies()
Issue: String-based Template Generation
Current Code:
page_content = f'''<template>
<q-page class="row items-center justify-evenly">
{template_content}
</q-page>
</template>'''
Improvement: Template engine with caching:
from jinja2 import Environment, FileSystemLoader
class TemplateGenerator:
def __init__(self):
self.env = Environment(loader=FileSystemLoader('templates'))
self._template_cache = {}
def render_page(self, page: Page) -> str:
template = self.env.get_template('page.vue.j2')
return template.render(page=page, components=page.components)
8.3 Error Handling and Validation
Issue: No Component Validation
Current Code:
def button(self, text: str, icon: str = None, **kwargs) -> Component:
props = {'label': text, **kwargs}
component = Component('button', **props)
return self._add_component(component)
Improvement: Schema validation:
from pydantic import BaseModel, validator
class ButtonProps(BaseModel):
label: str
icon: Optional[str] = None
color: str = 'primary'
@validator('color')
def validate_color(cls, v):
valid_colors = ['primary', 'secondary', 'accent', 'positive', 'negative']
if v not in valid_colors:
raise ValueError(f'Invalid color: {v}')
return v
def button(self, text: str, **kwargs) -> Component:
props = ButtonProps(label=text, **kwargs)
component = Component('button', **props.dict())
return self._add_component(component)
8.4 Type Safety and IDE Support
Issue: Limited Type Hints
Current Code:
def classes(self, class_names): # No type hints
# Implementation
Improvement: Full type safety:
from typing import Union, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from .core import Component
def classes(self, class_names: Union[str, List[str]]) -> 'Component':
"""Add CSS classes to the component."""
# Implementation
8.5 Testing and Debugging
Issue: No Built-in Testing Support
Improvement: Add testing utilities:
class TestRenderer:
def __init__(self, app: App):
self.app = app
def render_component(self, component: Component) -> Dict:
"""Render component to testable structure."""
return {
'type': component.component_type,
'props': component._props,
'classes': component._classes,
'children': [self.render_component(child) for child in component.children]
}
def find_components(self, component_type: str) -> List[Component]:
"""Find all components of a specific type."""
# Implementation
Conclusion
BadGUI provides a solid foundation for generating Vue.js applications from Python code. The workflow is straightforward and functional, but there are several areas where improvements could significantly enhance the framework:
- Architecture: Move away from singleton pattern, implement plugin system
- Performance: Add incremental builds, template caching, dependency management
- Developer Experience: Improve error handling, add type safety, better IDE support
- Extensibility: Plugin-based component system, customizable generators
- Testing: Built-in testing utilities and debugging tools
The current implementation demonstrates the core concepts well but would benefit from a more modular, extensible architecture for production use.
Document generated on: September 28, 2025
BadGUI Version: 0.2.0