Reorganize RSpade directory structure for clarity
Improve Jqhtml_Integration.js documentation with hydration system explanation Add jqhtml-laravel integration packages for traditional Laravel projects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
369
node_modules/@jqhtml/core/LLM_REFERENCE.md
generated
vendored
369
node_modules/@jqhtml/core/LLM_REFERENCE.md
generated
vendored
@@ -1,369 +0,0 @@
|
||||
# JQHTML Template & Component System - LLM Reference
|
||||
|
||||
## Component-Template Association
|
||||
- Template files (.jqhtml) compile to ES6 classes extending Component base class
|
||||
- Template name matches component class name: `UserCard.jqhtml` → `class UserCard extends Component`
|
||||
- Components are jQuery objects: `this.$` is the component's root jQuery element
|
||||
- No virtual DOM, no reconciliation, direct DOM manipulation only
|
||||
|
||||
## Template Syntax
|
||||
|
||||
### Basic Structure
|
||||
```jqhtml
|
||||
<Define:ComponentName tag="tagname">
|
||||
<!-- template content -->
|
||||
</Define:ComponentName>
|
||||
```
|
||||
- `tag` attribute optional, defaults to "div"
|
||||
- Component instantiation: `<ComponentName />` or `<ComponentName tag="span" />`
|
||||
- Tag can be overridden at invocation time
|
||||
- Self-closing for no children, paired tags for slots/children
|
||||
|
||||
### Data Binding & Expressions
|
||||
- `<%= expression %>` - Escaped output (HTML entities encoded)
|
||||
- `<%! expression %>` - Raw output (no escaping)
|
||||
- `<% statement; %>` - JavaScript statements (no output)
|
||||
- Access component data via `this.data.property`
|
||||
- Access component methods via `this.methodName()`
|
||||
|
||||
### Attribute System
|
||||
- `$sid="name"` → `id="name:_cid"` (component-scoped ID)
|
||||
- `$attr="value"` → `data-attr="value"` (data attributes)
|
||||
- `@click="handler"` → jQuery event binding to component method
|
||||
- `@submit.prevent="handler"` → Event with preventDefault
|
||||
- Regular attributes pass through unchanged
|
||||
|
||||
### Control Flow
|
||||
Two equivalent syntaxes supported:
|
||||
|
||||
**Colon Syntax:**
|
||||
```jqhtml
|
||||
<% for (let item of items): %>
|
||||
<div><%= item %></div>
|
||||
<% endfor; %>
|
||||
|
||||
<% if (condition): %>
|
||||
<div>True branch</div>
|
||||
<% else: %>
|
||||
<div>False branch</div>
|
||||
<% endif; %>
|
||||
```
|
||||
|
||||
**Brace Syntax:**
|
||||
```jqhtml
|
||||
<% for (let item of items) { %>
|
||||
<div><%= item %></div>
|
||||
<% } %>
|
||||
```
|
||||
|
||||
### Component Content & innerHTML (Primary Pattern)
|
||||
|
||||
**Most components use the simple `content()` function** to output innerHTML:
|
||||
|
||||
```jqhtml
|
||||
<!-- Define a component that outputs innerHTML -->
|
||||
<Define:Card>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<%= content() %> <!-- Outputs whatever innerHTML was passed -->
|
||||
</div>
|
||||
</div>
|
||||
</Define:Card>
|
||||
|
||||
<!-- Usage - just pass innerHTML like regular HTML -->
|
||||
<Card>
|
||||
<h3>Card Title</h3>
|
||||
<p>Card content goes here</p>
|
||||
<button>Action</button>
|
||||
</Card>
|
||||
```
|
||||
|
||||
This is the **recommended pattern for 95% of components**. It's simple, intuitive, and works like regular HTML.
|
||||
|
||||
### Slots (Advanced Feature)
|
||||
|
||||
**Slots are only needed when you have multiple distinct content areas:**
|
||||
|
||||
```jqhtml
|
||||
<Define:ComplexCard>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= content('header') %>
|
||||
</div>
|
||||
<div class="body">
|
||||
<%= content() %>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<%= content('footer') %>
|
||||
</div>
|
||||
</div>
|
||||
</Define:ComplexCard>
|
||||
|
||||
<!-- Usage with slots (advanced): -->
|
||||
<ComplexCard>
|
||||
<#header>Title</#header>
|
||||
Main content
|
||||
<#footer>Actions</#footer>
|
||||
</ComplexCard>
|
||||
```
|
||||
|
||||
**Important:** You cannot mix modes - if using slots, ALL content must be in slots.
|
||||
|
||||
## Component Lifecycle
|
||||
|
||||
### Execution Order
|
||||
1. **constructor** - Synchronous, sets up jQuery wrapper
|
||||
2. **render** - Creates DOM structure (top-down traversal)
|
||||
3. **create** - Quick initialization (bottom-up traversal)
|
||||
4. **load** - Async data fetching (parallel execution)
|
||||
5. **ready** - Final setup, DOM complete (bottom-up traversal)
|
||||
|
||||
### Phase Characteristics
|
||||
- **render**: Executes parent before children, can access `this.$` and `this.data`
|
||||
- **create**: Executes children before parents, component visible but may lack data
|
||||
- **load**: All components at same level run in parallel, NO DOM manipulation allowed
|
||||
- **ready**: Executes children before parents, all data loaded, safe for DOM queries
|
||||
|
||||
### Lifecycle Methods
|
||||
```javascript
|
||||
class MyComponent extends Component {
|
||||
async on_render() {
|
||||
// Called during render phase, after template DOM created
|
||||
// Parent's on_render completes before children render
|
||||
}
|
||||
|
||||
async on_create() {
|
||||
// Bottom-up, children complete before parent
|
||||
// Used for quick setup, event binding
|
||||
}
|
||||
|
||||
async on_load() {
|
||||
// Parallel execution, data fetching only
|
||||
// MUST NOT touch DOM or component will break
|
||||
this.data.users = await fetch('/api/users').then(r => r.json());
|
||||
}
|
||||
|
||||
async on_ready() {
|
||||
// Bottom-up, all components fully initialized
|
||||
// Safe to query children, cross-component communication
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Args vs Data: Critical Distinction
|
||||
|
||||
### this.args - Input Parameters
|
||||
Input parameters passed to the component during invocation. These come from:
|
||||
- `$` attributes: `$user=this.user` (expression) or `$title="literal"` (string)
|
||||
- `data-*` attributes: `data-role="admin"`
|
||||
- Regular attributes: `name="John"` (becomes `data-name` in args)
|
||||
|
||||
```javascript
|
||||
// Parent template
|
||||
<UserCard
|
||||
$user=this.user // args.user = parent's user object
|
||||
$title="User Profile" // args.title = "User Profile" (literal string)
|
||||
data-role="admin" // args.role = "admin"
|
||||
email="john@example" // args.email = "john@example"
|
||||
/>
|
||||
|
||||
// In UserCard component
|
||||
on_create() {
|
||||
console.log(this.args.user); // {name: "Alice", id: 123}
|
||||
console.log(this.args.title); // "User Profile"
|
||||
console.log(this.args.role); // "admin"
|
||||
console.log(this.args.email); // "john@example"
|
||||
}
|
||||
```
|
||||
|
||||
### this.data - Async Loaded Data
|
||||
Data loaded asynchronously in the `on_load()` lifecycle method:
|
||||
|
||||
```javascript
|
||||
class UserCard extends Jqhtml_Component {
|
||||
async on_load() {
|
||||
// Load data from API - this populates this.data
|
||||
this.data.posts = await fetch(`/api/users/${this.args.user.id}/posts`)
|
||||
.then(r => r.json());
|
||||
|
||||
// this.args = input parameters (unchanged)
|
||||
// this.data = fetched data (just loaded)
|
||||
}
|
||||
|
||||
on_ready() {
|
||||
// Both args and data are now available
|
||||
console.log('User:', this.args.user); // Input parameter
|
||||
console.log('Posts:', this.data.posts); // Loaded data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Differences
|
||||
| Property | `this.args` | `this.data` |
|
||||
|----------|------------|------------|
|
||||
| **Source** | Parent component attributes | `on_load()` method |
|
||||
| **When Set** | Component construction | Load lifecycle phase |
|
||||
| **Purpose** | Configuration/parameters | Dynamic/async data |
|
||||
| **Reactivity** | No (static after construction) | Yes (changes can trigger re-render) |
|
||||
| **Example** | User ID, config options | API responses, computed values |
|
||||
|
||||
## Component API
|
||||
|
||||
### Core Properties
|
||||
- `this.$` - jQuery object for component root element
|
||||
- `this.args` - Input parameters from component invocation (attributes passed from parent)
|
||||
- `this.data` - Data loaded asynchronously in `on_load()` method
|
||||
- `this._cid` - Unique component instance ID
|
||||
- `this._ready_state` - Lifecycle phase (0=created, 1=rendering, 2=creating, 3=loading, 4=ready)
|
||||
|
||||
### DOM Access Methods
|
||||
- `this.$sid('name')` - Get element by scoped ID (returns jQuery object)
|
||||
- `this.$child('name')` - Get child component by name
|
||||
- `this.$children()` - Get all direct child components
|
||||
- `this.$parent()` - Get parent component
|
||||
- `this.$find('.selector')` - Query within component scope
|
||||
|
||||
### Data Management
|
||||
```javascript
|
||||
// Setting data triggers re-render after current lifecycle phase
|
||||
this.data.property = value; // Triggers render after load phase
|
||||
this.set_data({ multiple: values }); // Batch update
|
||||
|
||||
// Prevent re-render during load phase
|
||||
async on_load() {
|
||||
this.data.items = await fetchItems(); // Won't trigger render
|
||||
this.data.cache = processItems(this.data.items); // Still won't render
|
||||
// Render happens once after all load phases complete
|
||||
}
|
||||
```
|
||||
|
||||
### Component Registration
|
||||
```javascript
|
||||
// Global registration (optional)
|
||||
jqhtml.register_component('UserCard', UserCardClass);
|
||||
|
||||
// Template registration (for dynamic components)
|
||||
jqhtml.register_template('UserCard', {
|
||||
as: 'article',
|
||||
render: compiledRenderFunction
|
||||
});
|
||||
|
||||
// Dynamic instantiation
|
||||
const ComponentClass = jqhtml.get_component_class('UserCard');
|
||||
const instance = new ComponentClass({ data: initialData });
|
||||
$('#container').append(instance.$);
|
||||
```
|
||||
|
||||
### jQuery Method Override
|
||||
```javascript
|
||||
class CustomInput extends Component {
|
||||
val(value) {
|
||||
if (arguments.length === 0) {
|
||||
// Getter - return processed value
|
||||
return this.parse_value(this.$sid('input').val());
|
||||
} else {
|
||||
// Setter - validate and set
|
||||
if (this.validate(value)) {
|
||||
this.$sid('input').val(this.format_value(value));
|
||||
this.data.value = value;
|
||||
}
|
||||
return this.$; // Maintain jQuery chaining
|
||||
}
|
||||
}
|
||||
}
|
||||
// Usage: $('#my-input').val() calls component's val method
|
||||
```
|
||||
|
||||
## Instruction Format (Compiled Template Output)
|
||||
Templates compile to instruction arrays:
|
||||
```javascript
|
||||
[
|
||||
{tag: ['div', {class: 'container', $sid: 'root'}, false]},
|
||||
{text: 'Static content'},
|
||||
{expr: function() { return this.data.title; }},
|
||||
{comp: ['ChildComponent', {props: 'values'}, [
|
||||
{slot: ['header', [...instructions]]}
|
||||
]]},
|
||||
{ctrl: ['if', function() { return this.data.show; }, [...instructions]]}
|
||||
]
|
||||
```
|
||||
|
||||
## Component Instantiation Patterns
|
||||
|
||||
### jQuery Plugin
|
||||
```javascript
|
||||
$('#target').component(ComponentClass, { data: initialData });
|
||||
```
|
||||
|
||||
### Direct Instantiation
|
||||
```javascript
|
||||
const component = new ComponentClass({ data: initialData });
|
||||
$('#target').append(component.$);
|
||||
await component.wait_ready(); // Wait for ready state
|
||||
```
|
||||
|
||||
### Within Templates
|
||||
```jqhtml
|
||||
<UserCard $data="this.data.user" @click="handleUserClick" />
|
||||
```
|
||||
|
||||
## Re-render Behavior
|
||||
- Data changes during `load` phase are batched, single render after all loads complete
|
||||
- Data changes during `ready` phase trigger immediate re-render
|
||||
- Re-render preserves component instance, only updates DOM
|
||||
- Child components maintain state through re-render unless key changes
|
||||
- `should_rerender()` method can prevent unnecessary renders
|
||||
|
||||
## Event Handling
|
||||
```jqhtml
|
||||
<!-- Method binding -->
|
||||
<button @click="handleClick">Click</button>
|
||||
|
||||
<!-- Inline expression -->
|
||||
<button @click="this.data.count++">Increment</button>
|
||||
|
||||
<!-- With event modifiers -->
|
||||
<form @submit.prevent="handleSubmit">
|
||||
```
|
||||
|
||||
```javascript
|
||||
class Component {
|
||||
handleClick(event) {
|
||||
// jQuery event object
|
||||
// 'this' is component instance
|
||||
event.preventDefault();
|
||||
this.data.clicked = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Component Communication
|
||||
```javascript
|
||||
// Parent to child - via props
|
||||
<ChildComponent $data="this.data.childData" />
|
||||
|
||||
// Child to parent - via events
|
||||
this.$.trigger('custom-event', [data]);
|
||||
|
||||
// Parent listening
|
||||
this.$child('name').on('custom-event', (e, data) => {});
|
||||
|
||||
// Cross-component - via registry
|
||||
const other = jqhtml.get_component_instance('id');
|
||||
```
|
||||
|
||||
## Performance Characteristics
|
||||
- Template compilation happens build-time, zero runtime parsing
|
||||
- jQuery operations are synchronous and immediate
|
||||
- No virtual DOM overhead, direct DOM manipulation
|
||||
- Component creation is fast, most cost in load phase
|
||||
- Re-renders only update changed portions via jQuery
|
||||
|
||||
## Critical Invariants
|
||||
1. Never modify DOM during `load` phase - causes race conditions
|
||||
2. Always return jQuery object from overridden jQuery methods for chaining
|
||||
3. Component `this.$` is always valid after constructor
|
||||
4. Data modifications during load are batched to prevent render thrashing
|
||||
5. Bottom-up phases (create, ready) guarantee children initialize first
|
||||
6. Parallel load execution means no component dependencies in load phase
|
||||
7. Template expressions have access to full component context via `this`
|
||||
565
node_modules/@jqhtml/core/README.md
generated
vendored
565
node_modules/@jqhtml/core/README.md
generated
vendored
@@ -1,565 +1,24 @@
|
||||
# @jqhtml/core
|
||||
|
||||
Core runtime library for JQHTML v2 - jQuery-first component framework.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @jqhtml/core jquery
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
The core package exports two separate entry points:
|
||||
|
||||
### Main Runtime (Production)
|
||||
```javascript
|
||||
import { Component, jqhtml } from '@jqhtml/core';
|
||||
```
|
||||
|
||||
### Debug Utilities (Development Only)
|
||||
```javascript
|
||||
import { showDebugOverlay, hideDebugOverlay } from '@jqhtml/core/debug';
|
||||
```
|
||||
|
||||
The debug module is kept separate to avoid including debug code in production bundles. It provides:
|
||||
- Visual debug overlay with performance profiling
|
||||
- Component flash visualization
|
||||
- Lifecycle logging
|
||||
- Slow render detection
|
||||
|
||||
## Overview
|
||||
|
||||
The core package provides:
|
||||
- Component base class with 5-stage lifecycle
|
||||
- Lifecycle manager for phase coordination
|
||||
- Component registry for dynamic instantiation
|
||||
- Instruction processor for template rendering
|
||||
- jQuery plugin integration
|
||||
- Template rendering with data bindings
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Component
|
||||
|
||||
```javascript
|
||||
import { Component } from '@jqhtml/core';
|
||||
|
||||
class UserCard extends Component {
|
||||
async init() {
|
||||
// Quick setup, hide elements
|
||||
this.$.addClass('loading');
|
||||
}
|
||||
|
||||
async load() {
|
||||
// Fetch data (runs in parallel with siblings)
|
||||
this.data.user = await fetch('/api/user').then(r => r.json());
|
||||
}
|
||||
|
||||
async render() {
|
||||
// Create DOM structure
|
||||
this.$.html(`
|
||||
<div class="card">
|
||||
<h2>${this.data.user.name}</h2>
|
||||
<p>${this.data.user.email}</p>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
async ready() {
|
||||
// Component fully initialized
|
||||
this.$.removeClass('loading');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using jQuery Plugin
|
||||
|
||||
```javascript
|
||||
// Create component on existing element
|
||||
$('#user-container').component(UserCard, { userId: 123 });
|
||||
|
||||
// Get component instance
|
||||
const card = $('#user-container').component();
|
||||
```
|
||||
|
||||
## Component Lifecycle
|
||||
|
||||
Components follow a multi-stage lifecycle with automatic re-rendering:
|
||||
|
||||
1. **constructor** - Instance creation, jQuery element setup (`this.data` is `{}`)
|
||||
2. **render** - Create initial DOM structure (top-down, atomic/synchronous operation)
|
||||
3. **create** - Quick setup after DOM creation (bottom-up, siblings parallel)
|
||||
4. **load** - Fetch data (bottom-up, fully parallel, **ABSOLUTELY NO DOM MODIFICATIONS**)
|
||||
5. **render** (automatic) - Re-render if data changed during load (empties DOM first)
|
||||
6. **create** (automatic) - Re-setup if component was re-rendered
|
||||
7. **ready** - Fully initialized (bottom-up, siblings parallel)
|
||||
8. **destroy** - Cleanup when removed
|
||||
|
||||
### Critical: NO DOM Modifications in load()
|
||||
|
||||
**The requirement that `on_load()` must not modify the DOM is ABSOLUTE.** This is not a guideline but a strict architectural requirement. Violating this will cause rendering issues and race conditions.
|
||||
|
||||
**Data Initialization Timeline:**
|
||||
- Before `load()`: `this.data = {}` (empty object)
|
||||
- After `load()`: `this.data` contains fetched data (remains `{}` if no data loaded)
|
||||
|
||||
### Correct Patterns for Loading States
|
||||
|
||||
If you need to show different DOM states before and after loading, use these patterns:
|
||||
|
||||
**Pattern 1: Conditional Rendering in Template**
|
||||
```jqhtml
|
||||
<Define:UserProfile>
|
||||
<div class="user-profile">
|
||||
<% if (Object.keys(this.data).length === 0): %>
|
||||
<!-- Show loading state (no data loaded yet) -->
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading user profile...</p>
|
||||
</div>
|
||||
<% else: %>
|
||||
<!-- Show loaded content -->
|
||||
<h2><%= this.data.user?.name || 'Unknown User' %></h2>
|
||||
<p><%= this.data.user?.bio || 'No bio available' %></p>
|
||||
<% endif; %>
|
||||
</div>
|
||||
</Define:UserProfile>
|
||||
```
|
||||
|
||||
**Pattern 2: DOM Changes in create() and ready()**
|
||||
```javascript
|
||||
class DataComponent extends Component {
|
||||
async on_create() {
|
||||
// Set loading state in the DOM during create phase
|
||||
this.$sid('status').addClass('loading').text('Loading...');
|
||||
}
|
||||
|
||||
async on_load() {
|
||||
// ONLY fetch data - NO DOM modifications here!
|
||||
this.data.user = await fetch('/api/user').then(r => r.json());
|
||||
}
|
||||
|
||||
async on_ready() {
|
||||
// Update DOM after data is loaded
|
||||
this.$sid('status').removeClass('loading').text('Loaded');
|
||||
this.$sid('username').text(this.data.user.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**NEVER do this:**
|
||||
```javascript
|
||||
class BadComponent extends Component {
|
||||
async on_load() {
|
||||
// ❌ WRONG - DOM modification in load()
|
||||
this.$sid('status').text('Loading...'); // VIOLATION!
|
||||
|
||||
this.data.user = await fetch('/api/user').then(r => r.json());
|
||||
|
||||
// ❌ WRONG - More DOM modification
|
||||
this.$sid('status').text('Loaded'); // VIOLATION!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** The `render()` method is atomic and essentially synchronous - it empties the DOM and rebuilds it in a single operation. While marked `async` for technical reasons, it completes immediately without yielding control.
|
||||
|
||||
### Phase Batching
|
||||
|
||||
All components complete each phase before moving to the next:
|
||||
- Parents render before children
|
||||
- Children create/load/ready before parents
|
||||
- Siblings execute in parallel where safe
|
||||
|
||||
### Automatic Re-rendering
|
||||
|
||||
After the `load` phase, if `this.data` has changed, the component automatically:
|
||||
1. Calls `render()` again (which empties the DOM with `$.empty()` first)
|
||||
2. Calls `create()` again to re-setup the new DOM
|
||||
3. Then proceeds to `ready()`
|
||||
|
||||
This ensures components can fetch data and re-render with that data seamlessly.
|
||||
|
||||
### Controlling Re-rendering
|
||||
|
||||
By default, components automatically re-render if `this.data` changes during `load()`:
|
||||
|
||||
```javascript
|
||||
class DataComponent extends Component {
|
||||
async on_load() {
|
||||
// If this modifies this.data, component will re-render
|
||||
this.data.user = await fetch('/api/user').then(r => r.json());
|
||||
}
|
||||
|
||||
// Override to customize re-render logic
|
||||
should_rerender() {
|
||||
// Default: returns true if JSON.stringify(this.data) changed
|
||||
// Override for custom logic:
|
||||
return this.data.user && !this.data.cached;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Re-rendering
|
||||
|
||||
You can manually re-render a component at any time:
|
||||
|
||||
```javascript
|
||||
class InteractiveComponent extends Component {
|
||||
async handleUpdate(newData) {
|
||||
// Update data
|
||||
this.data = { ...this.data, ...newData };
|
||||
|
||||
// Manually re-render (atomic operation)
|
||||
await this.render();
|
||||
|
||||
// Optionally re-initialize (call create/ready as needed)
|
||||
await this.on_create(); // Re-setup event handlers
|
||||
await this.on_ready(); // Re-initialize component state
|
||||
}
|
||||
|
||||
// Lifecycle manipulation methods
|
||||
redraw() {
|
||||
// Synchronously re-renders with current data
|
||||
}
|
||||
|
||||
async reload_data() {
|
||||
// Re-fetches data via on_load(), then redraws
|
||||
}
|
||||
|
||||
async reinitialize() {
|
||||
// Full lifecycle reset from stage 0
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Cleanup component and children
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The re-render process:
|
||||
1. Empties the component's DOM with `this.$.empty()`
|
||||
2. Calls `render()` to rebuild the DOM atomically
|
||||
3. For automatic re-renders, calls `create()` again for the new DOM
|
||||
4. Finally calls `ready()`
|
||||
5. For manual re-renders, you control which lifecycle methods to call
|
||||
|
||||
## Scoped IDs
|
||||
|
||||
Components use `_cid` for scoped element selection:
|
||||
|
||||
```javascript
|
||||
class TabsComponent extends Component {
|
||||
async render() {
|
||||
this.$.html(`
|
||||
<button id="tab1:${this._cid}">Tab 1</button>
|
||||
<button id="tab2:${this._cid}">Tab 2</button>
|
||||
<div id="content:${this._cid}"></div>
|
||||
`);
|
||||
}
|
||||
|
||||
selectTab(tabId) {
|
||||
// Use $sid() for scoped selection
|
||||
this.$sid('tab1').removeClass('active');
|
||||
this.$sid('tab2').removeClass('active');
|
||||
this.$sid(tabId).addClass('active');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Template Integration
|
||||
|
||||
The core runtime processes templates compiled by `@jqhtml/parser`:
|
||||
|
||||
```javascript
|
||||
import { render_template, with_template } from '@jqhtml/core';
|
||||
|
||||
// Template function (usually generated by parser)
|
||||
const template = function(data, args, content) {
|
||||
const _output = [];
|
||||
_output.push({tag: ["h1", {"data-bind-text": "title"}, false]});
|
||||
_output.push({tag: ["h1", {}, true]});
|
||||
return [_output, this];
|
||||
};
|
||||
|
||||
// Component with template
|
||||
class Article extends Component {
|
||||
async on_render() {
|
||||
await render_template(this, template);
|
||||
}
|
||||
}
|
||||
|
||||
// Or use mixin
|
||||
const Article = with_template(template)(Component);
|
||||
```
|
||||
|
||||
## Attribute Rules
|
||||
|
||||
JQHTML has specific rules for attribute quoting and value passing:
|
||||
- **@ Event attributes**: MUST be unquoted (pass function references)
|
||||
Example: `@click=this.handleClick`
|
||||
- **$ Data attributes**: Can be quoted OR unquoted (flexible)
|
||||
Example: `$sid="my-id"` or `$data=this.complexObject`
|
||||
- **Regular HTML attributes**: MUST be quoted (strings only)
|
||||
Example: `class="container <%= this.args.theme %>"`
|
||||
|
||||
See [ATTRIBUTE_RULES.md](../../docs/ATTRIBUTE_RULES.md) for comprehensive documentation.
|
||||
|
||||
## Data Bindings
|
||||
|
||||
Templates support reactive data bindings:
|
||||
|
||||
- `:text="expression"` - Update text content
|
||||
- `:value="expression"` - Form input values
|
||||
- `:class="{active: isActive}"` - Dynamic classes
|
||||
- `:style="{color: textColor}"` - Dynamic styles
|
||||
- `@click=handler` - Event handlers (unquoted)
|
||||
|
||||
## Browser Bundle
|
||||
|
||||
For browser usage without a build tool:
|
||||
A jQuery-based component framework for people who think in systems, not pixels.
|
||||
|
||||
```html
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="jqhtml-bundle.js"></script>
|
||||
<script>
|
||||
const { Component } = JQHTML;
|
||||
|
||||
class MyComponent extends Component {
|
||||
// ...
|
||||
}
|
||||
</script>
|
||||
<Define:User_Card class="card">
|
||||
<h3><%= this.data.name %></h3>
|
||||
<p><%= this.data.email %></p>
|
||||
</Define:User_Card>
|
||||
```
|
||||
|
||||
## Lifecycle Event Callbacks
|
||||
Components have a simple lifecycle: `on_load()` fetches data, `on_render()` sets up the DOM, `on_ready()` fires when everything's ready. No virtual DOM, no complex state management - just jQuery under the hood.
|
||||
|
||||
JQHTML provides a `.on()` method for registering callbacks that fire after lifecycle events complete. This is useful for external code that needs to know when a component reaches a certain state.
|
||||
**vs React/Vue:** JQHTML is for when you want component structure without the SPA complexity. Great for server-rendered apps (Laravel, Rails, Django) where you need interactive islands, not a full frontend framework.
|
||||
|
||||
### Supported Events
|
||||
## Status
|
||||
|
||||
- `'render'` - Fires after the render phase completes
|
||||
- `'create'` - Fires after the create phase completes
|
||||
- `'load'` - Fires after the load phase completes
|
||||
- `'ready'` - Fires after the ready phase completes (component fully initialized)
|
||||
Alpha release. It works and I use it daily, but expect rough edges. Full documentation and framework plugins coming soon.
|
||||
|
||||
### Usage
|
||||
If you try it in a project, I'd love to hear about it.
|
||||
|
||||
```javascript
|
||||
// Get component instance and register callback
|
||||
const component = $('#my-component').component();
|
||||
---
|
||||
|
||||
component.on('ready', (comp) => {
|
||||
console.log('Component is ready!', comp);
|
||||
// Access component data, DOM, etc.
|
||||
});
|
||||
|
||||
// If the event already occurred, callback fires immediately
|
||||
// AND still registers for future occurrences (e.g., on re-render)
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
1. **Immediate Execution**: If the lifecycle event has already occurred when you register the callback, it fires immediately
|
||||
2. **Future Events**: The callback also registers for future occurrences of the same event (useful for re-renders)
|
||||
3. **Multiple Callbacks**: You can register multiple callbacks for the same event
|
||||
4. **Error Safety**: Errors in callbacks are caught and logged without breaking the component
|
||||
5. **Validation**: Only lifecycle events are allowed; other event names trigger a console error
|
||||
|
||||
### Example: Wait for Component to be Ready
|
||||
|
||||
```javascript
|
||||
// Register callback before or after component initialization
|
||||
$('#user-profile').component().on('ready', (component) => {
|
||||
// Component is fully initialized, data is loaded
|
||||
console.log('User data:', component.data.user);
|
||||
|
||||
// Safe to access all DOM elements
|
||||
component.$sid('email').addClass('verified');
|
||||
});
|
||||
```
|
||||
|
||||
### Example: Track Multiple Lifecycle Events
|
||||
|
||||
```javascript
|
||||
const component = $('#dashboard').component();
|
||||
|
||||
component
|
||||
.on('render', () => console.log('Dashboard rendered'))
|
||||
.on('create', () => console.log('Dashboard created'))
|
||||
.on('load', () => console.log('Dashboard data loaded'))
|
||||
.on('ready', () => console.log('Dashboard ready'));
|
||||
```
|
||||
|
||||
### Example: Invalid Event (Error)
|
||||
|
||||
```javascript
|
||||
// This will log an error to console
|
||||
$('#my-component').component().on('click', callback);
|
||||
// Error: Component.on() only supports lifecycle events: render, create, load, ready
|
||||
```
|
||||
|
||||
### Note on Re-renders
|
||||
|
||||
When a component re-renders (manually or automatically after `load()`), lifecycle events fire again:
|
||||
|
||||
```javascript
|
||||
const component = $('#widget').component();
|
||||
|
||||
component.on('render', () => {
|
||||
console.log('Widget rendered');
|
||||
// Fires on initial render AND every re-render
|
||||
});
|
||||
|
||||
// Later, trigger re-render
|
||||
await component.render(); // "Widget rendered" logs again
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Component Class
|
||||
|
||||
- `constructor(element, args)` - Create component instance
|
||||
- `async render()` - Create DOM structure
|
||||
- `async init()` - Quick setup phase
|
||||
- `async load()` - Data fetching phase
|
||||
- `async ready()` - Final initialization
|
||||
- `destroy()` - Cleanup
|
||||
- `should_rerender()` - Control re-rendering after load
|
||||
- `emit(event, data)` - Emit jQuery events
|
||||
- `on(event, callback)` - Register lifecycle event callback
|
||||
- `$sid(localId)` - Get scoped jQuery element
|
||||
- `id(localId)` - Get scoped component instance
|
||||
- `parent()` - Get parent component
|
||||
- `children()` - Get direct child components
|
||||
- `find(selector)` - Find descendant components
|
||||
|
||||
### jQuery Plugin
|
||||
|
||||
- `$(el).component()` - Get component instance
|
||||
- `$(el).component(Class, args)` - Create component
|
||||
- `$.jqhtml.register(name, Class)` - Register component
|
||||
- `$.jqhtml.create(name, args)` - Create by name
|
||||
|
||||
### Template Functions
|
||||
|
||||
- `render_template(component, template)` - Render template
|
||||
- `with_template(template)` - Component mixin
|
||||
- `process_instructions(instructions, target, context)` - Process instruction array
|
||||
|
||||
## Component and Template Registration Flexibility
|
||||
|
||||
JQHTML provides complete flexibility in how you define components - you can use JavaScript classes, `.jqhtml` template files, both, or neither. The framework automatically handles all combinations:
|
||||
|
||||
### All Possible Combinations
|
||||
|
||||
1. **Both JS Class and .jqhtml Template** (Standard approach)
|
||||
```javascript
|
||||
// UserCard.js
|
||||
class UserCard extends Jqhtml_Component {
|
||||
async load() {
|
||||
this.data.user = await fetch('/api/user').then(r => r.json());
|
||||
}
|
||||
}
|
||||
jqhtml.register_component('UserCard', UserCard);
|
||||
```
|
||||
```html
|
||||
<!-- UserCard.jqhtml -->
|
||||
<Define:UserCard>
|
||||
<div class="card">
|
||||
<h2><%= this.data.user.name %></h2>
|
||||
</div>
|
||||
</Define:UserCard>
|
||||
```
|
||||
**Result**: Uses your custom class logic + your custom template
|
||||
|
||||
2. **.jqhtml Template Only** (No JS class needed)
|
||||
```html
|
||||
<!-- SimpleWidget.jqhtml -->
|
||||
<Define:SimpleWidget>
|
||||
<div class="widget">
|
||||
Hello, <%= this.args.name %>!
|
||||
</div>
|
||||
</Define:SimpleWidget>
|
||||
```
|
||||
**Result**: Uses default `Jqhtml_Component` class + your template. Perfect for presentational components that don't need custom logic.
|
||||
|
||||
3. **JS Class Only** (No .jqhtml template)
|
||||
```javascript
|
||||
class DynamicComponent extends Jqhtml_Component {
|
||||
async render() {
|
||||
this.$.html(`<div>Rendered at ${Date.now()}</div>`);
|
||||
}
|
||||
}
|
||||
jqhtml.register_component('DynamicComponent', DynamicComponent);
|
||||
```
|
||||
**Result**: Uses your class + default passthrough template. Useful when you want full programmatic control.
|
||||
|
||||
4. **Neither Class nor Template** (Passthrough)
|
||||
```html
|
||||
<!-- In your JQHTML file -->
|
||||
<FooBar>
|
||||
<p>This content just passes through!</p>
|
||||
</FooBar>
|
||||
```
|
||||
**Result**: Uses default `Jqhtml_Component` class + default passthrough template. Acts as a simple wrapper that renders its inner content.
|
||||
|
||||
### How It Works
|
||||
|
||||
When you use `<ComponentName>`:
|
||||
- **JS Class Resolution**: Looks for registered class, falls back to `Jqhtml_Component`
|
||||
- **Template Resolution**: Looks for registered template by class, then by name, then uses default passthrough
|
||||
- **Default Passthrough Template**: Simply renders the component's inner HTML as-is
|
||||
|
||||
This means you can start simple and progressively add complexity:
|
||||
```html
|
||||
<!-- Start with just markup -->
|
||||
<UserBadge>Basic HTML content</UserBadge>
|
||||
|
||||
<!-- Later, add a .jqhtml template for consistent styling -->
|
||||
<Define:UserBadge>
|
||||
<div class="badge"><%= this.args.username %></div>
|
||||
</Define:UserBadge>
|
||||
|
||||
<!-- Finally, add JS class for complex behavior -->
|
||||
class UserBadge extends Jqhtml_Component {
|
||||
async load() { /* fetch user data */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Laravel Integration
|
||||
|
||||
This package includes a Laravel bridge for error handling and source map support. After installing via npm, you can load it directly from `node_modules` in your Laravel application:
|
||||
|
||||
```php
|
||||
// In app/Providers/AppServiceProvider.php
|
||||
public function register()
|
||||
{
|
||||
$jqhtmlBridge = base_path('node_modules/@jqhtml/core/laravel-bridge/autoload.php');
|
||||
if (file_exists($jqhtmlBridge)) {
|
||||
require_once $jqhtmlBridge;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [laravel-bridge/LARAVEL_INTEGRATION.md](./laravel-bridge/LARAVEL_INTEGRATION.md) for complete integration instructions.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build TypeScript
|
||||
npm run build
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Watch mode
|
||||
npm run watch
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
**hansonxyz** · [hanson.xyz](https://hanson.xyz/) · [github](https://github.com/hansonxyz)
|
||||
|
||||
2
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
2
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
@@ -4237,7 +4237,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.221';
|
||||
const version = '2.2.222';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
2
node_modules/@jqhtml/core/dist/index.js
generated
vendored
2
node_modules/@jqhtml/core/dist/index.js
generated
vendored
@@ -4233,7 +4233,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.221';
|
||||
const version = '2.2.222';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
4
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
4
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* JQHTML Core v2.2.221
|
||||
* JQHTML Core v2.2.222
|
||||
* (c) 2025 JQHTML Team
|
||||
* Released under the MIT License
|
||||
*/
|
||||
@@ -4238,7 +4238,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.221';
|
||||
const version = '2.2.222';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
339
node_modules/@jqhtml/core/laravel-bridge/LARAVEL_INTEGRATION.md
generated
vendored
339
node_modules/@jqhtml/core/laravel-bridge/LARAVEL_INTEGRATION.md
generated
vendored
@@ -1,339 +0,0 @@
|
||||
# JQHTML Laravel Integration
|
||||
|
||||
The JQHTML Laravel Bridge is included with the `@jqhtml/core` npm package, making it easy to integrate JQHTML error handling into your Laravel application without requiring a separate Composer package.
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Install JQHTML via npm
|
||||
|
||||
```bash
|
||||
npm install @jqhtml/core
|
||||
```
|
||||
|
||||
### Step 2: Load the Laravel Bridge in your Laravel application
|
||||
|
||||
#### Option A: In `AppServiceProvider` (Recommended)
|
||||
|
||||
Add to `app/Providers/AppServiceProvider.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// Load JQHTML Laravel Bridge from node_modules
|
||||
$jqhtmlBridge = base_path('node_modules/@jqhtml/core/laravel-bridge/autoload.php');
|
||||
|
||||
if (file_exists($jqhtmlBridge)) {
|
||||
require_once $jqhtmlBridge;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Option B: In `composer.json` autoload
|
||||
|
||||
Add to your `composer.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"files": [
|
||||
"node_modules/@jqhtml/core/laravel-bridge/autoload.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
```bash
|
||||
composer dump-autoload
|
||||
```
|
||||
|
||||
#### Option C: Manual inclusion in `bootstrap/app.php`
|
||||
|
||||
For Laravel 11+, add to `bootstrap/app.php`:
|
||||
|
||||
```php
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withProviders([
|
||||
// After creating the app, load JQHTML
|
||||
function ($app) {
|
||||
$jqhtmlBridge = base_path('node_modules/@jqhtml/core/laravel-bridge/autoload.php');
|
||||
if (file_exists($jqhtmlBridge)) {
|
||||
require_once $jqhtmlBridge;
|
||||
}
|
||||
}
|
||||
])
|
||||
->create();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Publish the configuration file (optional)
|
||||
|
||||
Create `config/jqhtml.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'source_maps_path' => storage_path('jqhtml-sourcemaps'),
|
||||
'show_source_context' => env('APP_DEBUG', false),
|
||||
'context_lines' => 5,
|
||||
'cache_compiled' => !env('APP_DEBUG', false),
|
||||
'compiled_path' => storage_path('jqhtml-compiled'),
|
||||
'enable_source_maps' => env('APP_DEBUG', false),
|
||||
'source_map_mode' => 'external', // 'inline', 'external', or 'both'
|
||||
];
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Error Handling
|
||||
|
||||
The bridge automatically catches and formats JQHTML template errors. When a JQHTML error occurs, it will be displayed with:
|
||||
|
||||
- Template file location with line and column numbers
|
||||
- Source code context with error highlighting
|
||||
- Helpful suggestions for common mistakes
|
||||
- Source map resolution (if available)
|
||||
|
||||
### In Your Blade Templates
|
||||
|
||||
If you're compiling JQHTML templates and want to catch errors:
|
||||
|
||||
```php
|
||||
use Jqhtml\LaravelBridge\JqhtmlException;
|
||||
|
||||
Route::post('/compile-template', function (Request $request) {
|
||||
$templatePath = resource_path('jqhtml/' . $request->input('template'));
|
||||
|
||||
// Use Node.js to compile (via shell_exec, Process, etc.)
|
||||
$result = shell_exec("node compile-jqhtml.js " . escapeshellarg($templatePath));
|
||||
$data = json_decode($result, true);
|
||||
|
||||
if (!$data['success']) {
|
||||
// Create exception from JS error data
|
||||
throw JqhtmlException::createFromJsError($data['error']);
|
||||
}
|
||||
|
||||
return response()->json(['compiled' => $data['code']]);
|
||||
});
|
||||
```
|
||||
|
||||
### Middleware Setup
|
||||
|
||||
Add to your middleware groups in `app/Http/Kernel.php`:
|
||||
|
||||
```php
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
// ... other middleware
|
||||
\Jqhtml\LaravelBridge\Middleware\JqhtmlErrorMiddleware::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Manual Exception Handling
|
||||
|
||||
```php
|
||||
use Jqhtml\LaravelBridge\JqhtmlException;
|
||||
|
||||
try {
|
||||
// Your JQHTML compilation or execution
|
||||
$compiled = compileJqhtmlTemplate($source);
|
||||
} catch (\Exception $e) {
|
||||
// Wrap in JQHTML exception for better display
|
||||
throw new JqhtmlException(
|
||||
$e->getMessage(),
|
||||
'templates/my-template.jqhtml',
|
||||
$lineNumber,
|
||||
$columnNumber,
|
||||
$sourceCode,
|
||||
'Check your template syntax'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Node.js Integration
|
||||
|
||||
### Compilation Script
|
||||
|
||||
Create `compile-jqhtml.js` in your Laravel project root:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { parse, generate } from '@jqhtml/core';
|
||||
import fs from 'fs';
|
||||
|
||||
const templatePath = process.argv[2];
|
||||
|
||||
try {
|
||||
const source = fs.readFileSync(templatePath, 'utf8');
|
||||
const ast = parse(source, templatePath);
|
||||
const { code, map } = generate(ast, { sourceMap: true });
|
||||
|
||||
console.log(JSON.stringify({
|
||||
success: true,
|
||||
code,
|
||||
map
|
||||
}));
|
||||
} catch (error) {
|
||||
// Format error for Laravel
|
||||
console.log(JSON.stringify({
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
filename: error.filename || templatePath,
|
||||
line: error.line,
|
||||
column: error.column,
|
||||
source: error.source,
|
||||
suggestion: error.suggestion
|
||||
}
|
||||
}));
|
||||
process.exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
### Using with Laravel Mix/Vite
|
||||
|
||||
In `vite.config.js`:
|
||||
|
||||
```javascript
|
||||
import { defineConfig } from 'vite';
|
||||
import { parse, generate } from '@jqhtml/core';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
{
|
||||
name: 'jqhtml',
|
||||
transform(source, id) {
|
||||
if (id.endsWith('.jqhtml')) {
|
||||
try {
|
||||
const ast = parse(source, id);
|
||||
const { code, map } = generate(ast, { sourceMap: true });
|
||||
return { code, map };
|
||||
} catch (error) {
|
||||
// Error will be caught by Laravel bridge
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## API Endpoint Example
|
||||
|
||||
Create an API endpoint for compiling JQHTML templates:
|
||||
|
||||
```php
|
||||
// routes/api.php
|
||||
use Jqhtml\LaravelBridge\JqhtmlException;
|
||||
|
||||
Route::post('/api/jqhtml/compile', function (Request $request) {
|
||||
$template = $request->input('template');
|
||||
$filename = $request->input('filename', 'template.jqhtml');
|
||||
|
||||
// Execute Node.js compilation
|
||||
$process = new Process([
|
||||
'node',
|
||||
base_path('compile-jqhtml.js'),
|
||||
'--stdin'
|
||||
]);
|
||||
|
||||
$process->setInput(json_encode([
|
||||
'template' => $template,
|
||||
'filename' => $filename
|
||||
]));
|
||||
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$output = json_decode($process->getOutput(), true);
|
||||
|
||||
if (isset($output['error'])) {
|
||||
throw JqhtmlException::createFromJsError($output['error']);
|
||||
}
|
||||
|
||||
throw new \Exception('Compilation failed');
|
||||
}
|
||||
|
||||
return response()->json(json_decode($process->getOutput(), true));
|
||||
});
|
||||
```
|
||||
|
||||
## Laravel Ignition Integration
|
||||
|
||||
The bridge automatically integrates with Laravel Ignition (if installed) to provide enhanced error display. No additional configuration needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bridge not loading
|
||||
|
||||
Ensure `node_modules/@jqhtml/core` exists:
|
||||
```bash
|
||||
ls -la node_modules/@jqhtml/core/laravel-bridge/
|
||||
```
|
||||
|
||||
### Class not found errors
|
||||
|
||||
Clear Laravel's cache:
|
||||
```bash
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
composer dump-autoload
|
||||
```
|
||||
|
||||
### Source maps not working
|
||||
|
||||
Ensure the source map directory exists and is writable:
|
||||
```bash
|
||||
mkdir -p storage/jqhtml-sourcemaps
|
||||
chmod 755 storage/jqhtml-sourcemaps
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
After installation, your Laravel project will have:
|
||||
|
||||
```
|
||||
your-laravel-project/
|
||||
├── node_modules/
|
||||
│ └── @jqhtml/
|
||||
│ └── core/
|
||||
│ ├── dist/ # JavaScript runtime
|
||||
│ └── laravel-bridge/ # PHP integration
|
||||
│ ├── src/ # PHP classes
|
||||
│ ├── config/ # Laravel config
|
||||
│ └── autoload.php # Autoloader
|
||||
├── config/
|
||||
│ └── jqhtml.php # Your config (optional)
|
||||
├── storage/
|
||||
│ ├── jqhtml-compiled/ # Compiled templates
|
||||
│ └── jqhtml-sourcemaps/ # Source maps
|
||||
└── compile-jqhtml.js # Node compilation script
|
||||
```
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Single Package**: Everything comes from npm, no Composer package needed
|
||||
2. **Version Sync**: PHP and JS code always match versions
|
||||
3. **Simple Updates**: Just `npm update @jqhtml/core`
|
||||
4. **No Private Packagist**: No need for private Composer repositories
|
||||
5. **CI/CD Friendly**: Works with standard Node.js deployment pipelines
|
||||
|
||||
## License
|
||||
|
||||
MIT - Same as @jqhtml/core
|
||||
6
node_modules/@jqhtml/core/package.json
generated
vendored
Normal file → Executable file
6
node_modules/@jqhtml/core/package.json
generated
vendored
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/core",
|
||||
"version": "2.2.221",
|
||||
"version": "2.2.222",
|
||||
"description": "Core runtime library for JQHTML",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
@@ -31,14 +31,12 @@
|
||||
},
|
||||
"browser": true,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://privatenpm.hanson.xyz/"
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"laravel-bridge",
|
||||
"README.md",
|
||||
"LLM_REFERENCE.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"keywords": [
|
||||
|
||||
721
node_modules/@jqhtml/parser/LLM_REFERENCE.md
generated
vendored
721
node_modules/@jqhtml/parser/LLM_REFERENCE.md
generated
vendored
@@ -1,721 +0,0 @@
|
||||
# JQHTML v2 Complete Syntax & Instruction Format Specification
|
||||
|
||||
## Overview
|
||||
|
||||
JQHTML v2 is a composable component templating language for jQuery that compiles to efficient JavaScript instruction arrays. This document defines the complete syntax specification and the instruction format that templates compile to.
|
||||
|
||||
## Critical Concepts
|
||||
|
||||
### The Instruction Array Pattern
|
||||
|
||||
Templates compile to functions that return arrays of rendering instructions:
|
||||
|
||||
```javascript
|
||||
// Every template function returns this structure:
|
||||
function render(Component, data, args, content) {
|
||||
const _output = []; // Instruction array
|
||||
// ... push instructions ...
|
||||
return [_output, this]; // CRITICAL: Returns tuple [instructions, context]
|
||||
}
|
||||
```
|
||||
|
||||
### Why This Pattern Exists
|
||||
|
||||
1. **Deferred Execution**: Instructions can be processed when optimal
|
||||
2. **Single DOM Write**: All HTML built as string, then one `$.html()` call
|
||||
3. **Complex Attribute Handling**: Functions and objects can't go in HTML strings
|
||||
4. **Component Coordination**: Parent/child relationships preserved through execution
|
||||
|
||||
## Template Syntax
|
||||
|
||||
### 1. Component Definition
|
||||
|
||||
Components are defined using the `<Define:>` tag:
|
||||
|
||||
```jqhtml
|
||||
<Define:ComponentName>
|
||||
<!-- Component template -->
|
||||
</Define:ComponentName>
|
||||
```
|
||||
|
||||
### 2. $ Attribute System
|
||||
|
||||
JQHTML uses the `$` prefix for passing data to components as `this.args`:
|
||||
|
||||
#### Syntax Rules
|
||||
|
||||
1. **Quoted = Literal String**
|
||||
```jqhtml
|
||||
$title="User Profile" → args.title = "User Profile"
|
||||
$expr="this.user" → args.expr = "this.user" (the string, not evaluated!)
|
||||
```
|
||||
|
||||
2. **Unquoted = JavaScript Expression**
|
||||
```jqhtml
|
||||
$user=this.user → args.user = {actual user object}
|
||||
$count=42 → args.count = 42
|
||||
$enabled=true → args.enabled = true
|
||||
$handler=this.onClick → args.handler = function reference
|
||||
```
|
||||
|
||||
3. **Parentheses for Complex Expressions**
|
||||
```jqhtml
|
||||
$status=(active ? 'online' : 'offline')
|
||||
$data=({...this.user, modified: true})
|
||||
$items=(this.data.items || [])
|
||||
```
|
||||
|
||||
#### Runtime Behavior
|
||||
|
||||
- All $ attributes become properties in `this.args` (NOT `this.data`)
|
||||
- Stringable values (strings/numbers) appear as `data-*` DOM attributes
|
||||
- Complex values (objects/arrays/functions) accessible only via `this.args`
|
||||
- Component constructor syncs stringable args to DOM
|
||||
|
||||
#### Example
|
||||
|
||||
```jqhtml
|
||||
<!-- Parent component with this.user = {name: "Alice", id: 123} -->
|
||||
<UserCard
|
||||
$user=this.user <!-- args.user = {name: "Alice", id: 123} -->
|
||||
$title="User Profile" <!-- args.title = "User Profile" -->
|
||||
$count=42 <!-- args.count = 42 -->
|
||||
data-role="admin" <!-- args.role = "admin" -->
|
||||
/>
|
||||
```
|
||||
|
||||
Result in UserCard component:
|
||||
- `this.args.user` = {name: "Alice", id: 123} (object)
|
||||
- `this.args.title` = "User Profile" (string)
|
||||
- `this.args.count` = 42 (number)
|
||||
- `this.args.role` = "admin" (from data- attribute)
|
||||
|
||||
DOM shows: `<div data-title="User Profile" data-count="42" data-role="admin">` (no data-user since it's an object)
|
||||
|
||||
### 3. Expressions
|
||||
|
||||
Output JavaScript expressions using `<%= %>` (escaped) or `<%!= %>` (unescaped):
|
||||
|
||||
```jqhtml
|
||||
<%= this.data.title %> <!-- Escaped output -->
|
||||
<%!= this.data.rawHtml %> <!-- Unescaped output -->
|
||||
<%@= this.data.maybeUndefined %> <!-- Escaped with error suppression -->
|
||||
<%!@= this.data.maybeUndefinedHtml %> <!-- Unescaped with error suppression -->
|
||||
```
|
||||
|
||||
### 3. Code Blocks
|
||||
|
||||
Execute JavaScript code using `<% %>`. Regular JavaScript passes through unchanged:
|
||||
|
||||
```jqhtml
|
||||
<% const items = this.data.items || []; %>
|
||||
<% console.log('Rendering:', items.length); %>
|
||||
|
||||
<!-- Regular JavaScript control flow -->
|
||||
<% if (condition) { %>
|
||||
<p>True branch</p>
|
||||
<% } else { %>
|
||||
<p>False branch</p>
|
||||
<% } %>
|
||||
|
||||
<% for (const item of items) { %>
|
||||
<div><%= item.name %></div>
|
||||
<% } %>
|
||||
```
|
||||
|
||||
**Design Rationale**: JavaScript code blocks pass through directly into the generated function. This allows developers to use any JavaScript construct without parser limitations.
|
||||
|
||||
## Component Invocation
|
||||
|
||||
### Basic Component Usage
|
||||
|
||||
Components are invoked using capital-letter tags:
|
||||
|
||||
```jqhtml
|
||||
<UserCard /> <!-- Self-closing, no content -->
|
||||
<UserCard></UserCard> <!-- Empty paired tags, no content -->
|
||||
<UserCard>Default content</UserCard> <!-- With innerHTML content (most common) -->
|
||||
```
|
||||
|
||||
### Property Passing
|
||||
|
||||
```jqhtml
|
||||
<!-- String literals -->
|
||||
<UserCard name="John Doe" role="admin" />
|
||||
|
||||
<!-- String interpolation -->
|
||||
<UserCard title="Welcome <%= this.data.userName %>!" />
|
||||
<div class="card <%= this.data.active ? 'active' : '' %>">
|
||||
|
||||
<!-- Data attributes ($property syntax) -->
|
||||
<UserCard $user=this.data.currentUser $active=true />
|
||||
<!-- Compiles to: data-user and data-active attributes -->
|
||||
|
||||
<!-- Property binding (:property syntax) -->
|
||||
<UserCard :user="currentUser" :settings="{ theme: 'dark' }" />
|
||||
<!-- Compiles to: data-bind-user and data-bind-settings attributes -->
|
||||
|
||||
<!-- Event binding (@event syntax) -->
|
||||
<UserCard @save="handleSave" @cancel="() => closeModal()" />
|
||||
<!-- Compiles to: data-on-save and data-on-cancel attributes -->
|
||||
```
|
||||
|
||||
### $ Attribute System
|
||||
|
||||
JQHTML uses the `$` prefix as a shorthand for data attributes with special handling:
|
||||
|
||||
#### General Case: `$foo="bar"` → `data-foo`
|
||||
|
||||
```jqhtml
|
||||
<div $user=currentUser $theme="dark" $count=42>
|
||||
<!-- Compiles to: -->
|
||||
{tag: ["div", {"data-user": currentUser, "data-theme": "dark", "data-count": 42}, false]}
|
||||
```
|
||||
|
||||
**Runtime Behavior**:
|
||||
1. The attribute is set via jQuery's `.data()` method: `$element.data('user', currentUser)`
|
||||
2. For debugging visibility, if the value is a string or number, it's also set as a DOM attribute
|
||||
3. Objects and arrays are stored in `.data()` but not visible in DOM
|
||||
|
||||
#### Special Case: `$sid="name"` → Scoped IDs
|
||||
|
||||
The `$sid` attribute has special handling for component-scoped element selection:
|
||||
|
||||
```jqhtml
|
||||
<Define:UserCard>
|
||||
<div $sid="container">
|
||||
<input $sid="username" type="text" />
|
||||
<button $sid="submit">Submit</button>
|
||||
</div>
|
||||
</Define:UserCard>
|
||||
```
|
||||
|
||||
**Compilation**:
|
||||
```javascript
|
||||
// In render function (receives _cid parameter)
|
||||
function render(_cid) {
|
||||
const _output = [];
|
||||
_output.push({tag: ["div", {"id": "container:" + _cid}, false]});
|
||||
_output.push({tag: ["input", {"id": "username:" + _cid, "type": "text"}, false]});
|
||||
_output.push({tag: ["button", {"id": "submit:" + _cid}, false]});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Runtime Usage**:
|
||||
```javascript
|
||||
class UserCard extends Component {
|
||||
init() {
|
||||
// Find scoped elements
|
||||
const $username = this.$sid('username'); // Returns $('#username:123')
|
||||
const $submit = this.$sid('submit'); // Returns $('#submit:123')
|
||||
|
||||
$submit.on('click', () => {
|
||||
const value = $username.val();
|
||||
// ...
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Lexical Scoping of `_cid`
|
||||
|
||||
Component IDs flow through lexical scope naturally:
|
||||
|
||||
```jqhtml
|
||||
<Define:ParentComponent>
|
||||
<div $sid="parent-element"> <!-- Gets ParentComponent's _cid -->
|
||||
<ChildComponent>
|
||||
<div $sid="slot-element" /> <!-- Also gets ParentComponent's _cid -->
|
||||
</ChildComponent>
|
||||
</div>
|
||||
</Define:ParentComponent>
|
||||
|
||||
<Define:ChildComponent>
|
||||
<div $sid="child-element"> <!-- Gets ChildComponent's _cid -->
|
||||
<%= content() %> <!-- Slot content preserves parent's _cid -->
|
||||
</div>
|
||||
</Define:ChildComponent>
|
||||
```
|
||||
|
||||
This happens because content functions capture their defining scope:
|
||||
```javascript
|
||||
// ParentComponent render
|
||||
function render(_cid) { // Parent's _cid = 123
|
||||
_output.push({comp: ["ChildComponent", {}, () => {
|
||||
// This arrow function captures Parent's _cid
|
||||
const _output = [];
|
||||
_output.push({tag: ["div", {"id": "slot-element:" + _cid}, false]}); // slot-element:123
|
||||
return [_output, this];
|
||||
}]});
|
||||
}
|
||||
|
||||
// ChildComponent render
|
||||
function render(_cid) { // Child's _cid = 456
|
||||
_output.push({tag: ["div", {"id": "child-element:" + _cid}, false]}); // child-element:456
|
||||
}
|
||||
```
|
||||
|
||||
#### Attribute Value Interpolation
|
||||
|
||||
Within quoted attribute values, `<%= %>` and `<%!= %>` perform string interpolation:
|
||||
|
||||
```jqhtml
|
||||
<!-- Both of these work identically in attributes -->
|
||||
<div title="User: <%= user.name %>">
|
||||
<div title="User: <%!= user.name %>">
|
||||
|
||||
<!-- Compiles to -->
|
||||
{tag: ["div", {"title": "User: " + user.name}, false]}
|
||||
```
|
||||
|
||||
**Design Rationale**: Inside attribute values, HTML escaping is inappropriate because we're building a JavaScript string, not HTML content. The JavaScript string serialization handles all necessary escaping (quotes, backslashes, etc.). This is semantically different from top-level `<%= %>` which outputs to HTML and requires escaping.
|
||||
|
||||
## Binding Syntax (v2)
|
||||
|
||||
### Property Binding with `:`
|
||||
|
||||
The `:` prefix creates dynamic property bindings that are always evaluated as JavaScript expressions:
|
||||
|
||||
```jqhtml
|
||||
<!-- Simple property binding -->
|
||||
<input :value="formData.name" :disabled="isLoading" />
|
||||
|
||||
<!-- Complex expressions -->
|
||||
<div :style="{ color: textColor, fontSize: size + 'px' }" />
|
||||
|
||||
<!-- Method calls -->
|
||||
<select :options="getOptions(category)" />
|
||||
```
|
||||
|
||||
**Compilation**:
|
||||
- `:prop="expr"` → `{"data-bind-prop": expr}` (no quotes around expr)
|
||||
- Values are ALWAYS treated as JavaScript expressions
|
||||
- Even quoted strings like `:prop="value"` become expressions
|
||||
|
||||
### Event Binding with `@`
|
||||
|
||||
The `@` prefix binds event handlers:
|
||||
|
||||
```jqhtml
|
||||
<!-- Simple handler -->
|
||||
<button @click="handleClick">Click Me</button>
|
||||
|
||||
<!-- With arguments -->
|
||||
<button @click="deleteItem(item.id)">Delete</button>
|
||||
|
||||
<!-- Inline arrow function -->
|
||||
<input @input="(e) => updateValue(e.target.value)" />
|
||||
|
||||
<!-- Multiple events -->
|
||||
<form @submit="save" @reset="clear">
|
||||
```
|
||||
|
||||
**Compilation**:
|
||||
- `@event="handler"` → `{"data-on-event": handler}`
|
||||
- Handlers are JavaScript expressions (function references or inline functions)
|
||||
|
||||
### Binding Design Rationale
|
||||
|
||||
1. **Vue.js Familiarity**: Uses the same `:prop` and `@event` syntax as Vue.js
|
||||
2. **Always Expressions**: Unlike `$` attributes which distinguish strings vs expressions, bindings are ALWAYS expressions
|
||||
3. **Runtime Processing**: The runtime will need to handle these specially:
|
||||
- `data-bind-*` attributes set properties dynamically
|
||||
- `data-on-*` attributes attach event listeners
|
||||
4. **Separate from `$` System**: Bindings serve a different purpose than data attributes
|
||||
|
||||
## Component Content & innerHTML (Primary Pattern)
|
||||
|
||||
### Using `content()` Function - The Most Common Approach
|
||||
|
||||
**The vast majority of components will use the simple `content()` function to output innerHTML passed by parent components.** This is the primary, recommended pattern for component composition:
|
||||
|
||||
```jqhtml
|
||||
<!-- Define a component that outputs innerHTML -->
|
||||
<Define:Container>
|
||||
<div class="container">
|
||||
<div class="container-body">
|
||||
<%= content() %> <!-- Outputs whatever innerHTML was passed -->
|
||||
</div>
|
||||
</div>
|
||||
</Define:Container>
|
||||
|
||||
<!-- Usage - just pass innerHTML like regular HTML -->
|
||||
<Container>
|
||||
<p>This is the content</p>
|
||||
<span>More content here</span>
|
||||
<!-- Any HTML/components can go here -->
|
||||
</Container>
|
||||
```
|
||||
|
||||
### Common Patterns with `content()`
|
||||
|
||||
```jqhtml
|
||||
<!-- Card component with innerHTML -->
|
||||
<Define:Card>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<%= content() %> <!-- Simple innerHTML output -->
|
||||
</div>
|
||||
</div>
|
||||
</Define:Card>
|
||||
|
||||
<!-- Panel with conditional innerHTML -->
|
||||
<Define:Panel>
|
||||
<div class="panel">
|
||||
<% if (this.args.title): %>
|
||||
<div class="panel-header"><%= this.args.title %></div>
|
||||
<% endif; %>
|
||||
<div class="panel-body">
|
||||
<%= content() %>
|
||||
</div>
|
||||
</div>
|
||||
</Define:Panel>
|
||||
|
||||
<!-- Wrapper that adds behavior to innerHTML -->
|
||||
<Define:Collapsible>
|
||||
<div class="collapsible" $sid="wrapper">
|
||||
<button $onclick="toggle">Toggle</button>
|
||||
<div class="content" $sid="content">
|
||||
<%= content() %> <!-- Wrapped innerHTML -->
|
||||
</div>
|
||||
</div>
|
||||
</Define:Collapsible>
|
||||
```
|
||||
|
||||
### How `content()` Works
|
||||
|
||||
1. **Parent passes innerHTML**: When invoking `<Component>innerHTML here</Component>`
|
||||
2. **Template receives it**: The `content` parameter in render function contains the innerHTML
|
||||
3. **Output with `<%= content() %>`**: Call the function to output the HTML where needed
|
||||
4. **No special syntax needed**: Just regular HTML/components as children
|
||||
|
||||
### Important Rules
|
||||
|
||||
- **No mixing**: If a component uses ANY slots (`<#slotname>`), ALL content must be in slots
|
||||
- **Most components don't need slots**: The simple `content()` pattern handles 95% of use cases
|
||||
- **Slots are for advanced scenarios**: Only use slots when you need multiple named content areas
|
||||
|
||||
## Slot System (Advanced Feature)
|
||||
|
||||
**Note: Slots are an advanced feature for specific use cases. Most components should use the simpler `content()` pattern shown above.**
|
||||
|
||||
### When to Use Slots vs `content()`
|
||||
|
||||
Use `content()` (recommended for most cases):
|
||||
- Single content area
|
||||
- Wrapping/decorating content
|
||||
- Simple component composition
|
||||
- Standard container components
|
||||
|
||||
Use slots (advanced cases only):
|
||||
- Multiple distinct content areas (header/body/footer)
|
||||
- Complex layouts with specific placement
|
||||
- Data table templates with row/header/footer sections
|
||||
|
||||
### 1. Named Slots
|
||||
|
||||
Pass content to specific slots using `<#slotname>` syntax:
|
||||
|
||||
```jqhtml
|
||||
<DataTable $items=this.data.users>
|
||||
<#header>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
</#header>
|
||||
|
||||
<#row>
|
||||
<td><%= row.id %></td>
|
||||
<td><%= row.name %></td>
|
||||
<td><%= row.email %></td>
|
||||
</#row>
|
||||
|
||||
<#empty /> <!-- Self-closing slot -->
|
||||
</DataTable>
|
||||
```
|
||||
|
||||
### 2. Accessing Slots in Components (Advanced)
|
||||
|
||||
For the rare cases where slots are needed, components can check for and render specific named slots:
|
||||
|
||||
```jqhtml
|
||||
<Define:Card>
|
||||
<div class="card">
|
||||
<!-- Render default slot -->
|
||||
<%= content() %>
|
||||
|
||||
<!-- Render named slot -->
|
||||
<% if (content('footer')) { %>
|
||||
<footer>
|
||||
<%= content('footer', { timestamp: new Date() }) %>
|
||||
</footer>
|
||||
<% } %>
|
||||
</div>
|
||||
</Define:Card>
|
||||
```
|
||||
|
||||
## Instruction Format Reference
|
||||
|
||||
### Instruction Types
|
||||
|
||||
Templates compile to three instruction types:
|
||||
|
||||
1. **`{tag: [...]}`** - HTML elements
|
||||
2. **`{comp: [...]}`** - Component invocations
|
||||
3. **`{slot: [...]}`** - Slot definitions
|
||||
|
||||
### 1. Tag Instruction
|
||||
|
||||
```javascript
|
||||
{tag: [tagname, attributes, self_closing]}
|
||||
```
|
||||
|
||||
**Format:**
|
||||
- `tagname`: String - HTML tag name
|
||||
- `attributes`: Object - Tag attributes (may contain functions!)
|
||||
- `self_closing`: Boolean - Whether tag self-closes
|
||||
|
||||
**Examples:**
|
||||
```javascript
|
||||
{tag: ["div", {"class": "user-card"}, false]}
|
||||
{tag: ["img", {"src": "/avatar.jpg", "alt": "User"}, true]}
|
||||
{tag: ["button", {"onclick": this.handleClick}, false]}
|
||||
{tag: ["div", {"data-sid": "header"}, false]} // $sid becomes data-id
|
||||
```
|
||||
|
||||
### 2. Component Instruction
|
||||
|
||||
```javascript
|
||||
{comp: [component_name, attributes, optional_innerhtml_function]}
|
||||
```
|
||||
|
||||
**Format:**
|
||||
- `component_name`: String - Component to instantiate
|
||||
- `attributes`: Object - Props/attributes to pass
|
||||
- `optional_innerhtml_function`: Function - Contains slots and default content
|
||||
|
||||
**CRITICAL**: When a component invocation contains slots or any child content, it MUST use the three-parameter form with a content function. Slots are NOT separate instructions after the component - they are contained within the component's content function.
|
||||
|
||||
**Examples:**
|
||||
```javascript
|
||||
// Simple component (no children)
|
||||
{comp: ["UserAvatar", {"data-userId": this.data.id}]}
|
||||
|
||||
// Component with slots
|
||||
{comp: ["DataTable", {"data-rows": this.data.items}, (DataTable) => {
|
||||
const _output = [];
|
||||
_output.push({slot: ["header", {}, (header) => {
|
||||
const _output = [];
|
||||
_output.push({tag: ["th", {}, false]});
|
||||
_output.push("Name");
|
||||
_output.push("</th>");
|
||||
return [_output, this];
|
||||
}]});
|
||||
return [_output, this];
|
||||
}]}
|
||||
```
|
||||
|
||||
### 3. Slot Instruction
|
||||
|
||||
```javascript
|
||||
{slot: [slot_name, attributes, render_function]}
|
||||
```
|
||||
|
||||
**Format:**
|
||||
- `slot_name`: String - Slot identifier
|
||||
- `attributes`: Object - Props for slot
|
||||
- `render_function`: Function - Returns `[instructions, context]`
|
||||
|
||||
**Example:**
|
||||
```javascript
|
||||
{slot: ["actions", {}, (actions) => {
|
||||
const _output = [];
|
||||
_output.push({tag: ["button", {"onclick": this.delete}, false]});
|
||||
_output.push("Delete");
|
||||
_output.push("</button>");
|
||||
return [_output, this];
|
||||
}]}
|
||||
```
|
||||
|
||||
### 4. String Output
|
||||
|
||||
Raw strings are pushed directly:
|
||||
|
||||
```javascript
|
||||
_output.push({tag: ["div", {}, false]}); // Opening tag (instruction)
|
||||
_output.push("Hello, "); // Raw text
|
||||
_output.push(html(this.data.name)); // Escaped text
|
||||
_output.push("</div>"); // Closing tag (raw string!)
|
||||
```
|
||||
|
||||
**CRITICAL**: Closing tags are ALWAYS raw strings, not instructions.
|
||||
|
||||
## Context Preservation
|
||||
|
||||
### v1 Pattern: `_that = this`
|
||||
|
||||
The v1 pattern preserves component context through nested functions:
|
||||
|
||||
```javascript
|
||||
function render(Component, data, args, content) {
|
||||
let _that = this; // Preserve component instance
|
||||
let _output = [];
|
||||
|
||||
_output.push({comp: ["Child", {}, function(Child) {
|
||||
let _output = [];
|
||||
// _that still refers to parent component
|
||||
_output.push(html(_that.data.value));
|
||||
return [_output, _that];
|
||||
}.bind(_that)]});
|
||||
|
||||
return [_output, _that];
|
||||
}
|
||||
```
|
||||
|
||||
### v2 Pattern: Arrow Functions
|
||||
|
||||
v2 uses arrow functions for natural context preservation:
|
||||
|
||||
```javascript
|
||||
function render(Component, data, args, content) {
|
||||
const _output = [];
|
||||
|
||||
_output.push({comp: ["Child", {}, (Child) => {
|
||||
const _output = [];
|
||||
// 'this' refers to parent component naturally
|
||||
_output.push(html(this.data.value));
|
||||
return [_output, this];
|
||||
}]});
|
||||
|
||||
return [_output, this];
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime Processing
|
||||
|
||||
### Phase 1: Instruction Execution
|
||||
|
||||
```javascript
|
||||
let [instructions, context] = template.render.bind(component)(
|
||||
component,
|
||||
component.data,
|
||||
args,
|
||||
content_fn
|
||||
);
|
||||
```
|
||||
|
||||
### Phase 2: Instruction Processing
|
||||
|
||||
1. **Build HTML**: Process all string and simple tag instructions
|
||||
2. **Track Complex Elements**: Store elements with function attributes
|
||||
3. **Single DOM Write**: `component.$.html(html.join(''))`
|
||||
4. **Apply Attributes**: Attach event handlers and complex attributes
|
||||
5. **Initialize Components**: Create child component instances
|
||||
|
||||
## Self-Closing HTML Tags
|
||||
|
||||
The following HTML tags are automatically treated as self-closing:
|
||||
- `area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input`
|
||||
- `link`, `meta`, `param`, `source`, `track`, `wbr`
|
||||
|
||||
## Complete Example
|
||||
|
||||
### Template
|
||||
|
||||
```jqhtml
|
||||
<Define:UserDashboard>
|
||||
<div class="dashboard">
|
||||
<Header $title="User Management">
|
||||
<#actions>
|
||||
<button $onclick=this.addUser>Add User</button>
|
||||
</#actions>
|
||||
</Header>
|
||||
|
||||
<% if (this.data.loading): %>
|
||||
<LoadingSpinner />
|
||||
<% else: %>
|
||||
<UserTable $users=this.data.users>
|
||||
<#header>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</#header>
|
||||
|
||||
<#row>
|
||||
<td><%= row.id %></td>
|
||||
<td><%= row.name %></td>
|
||||
<td>
|
||||
<button $onclick=this.editUser>Edit</button>
|
||||
</td>
|
||||
</#row>
|
||||
</UserTable>
|
||||
<% endif; %>
|
||||
</div>
|
||||
</Define:UserDashboard>
|
||||
```
|
||||
|
||||
### Compiled Output Structure
|
||||
|
||||
```javascript
|
||||
function render(Component, data, args, content) {
|
||||
const _output = [];
|
||||
|
||||
_output.push({tag: ["div", {"class": "dashboard"}, false]});
|
||||
|
||||
// Header component with slots
|
||||
_output.push({comp: ["Header", {"data-title": "User Management"}, (Header) => {
|
||||
const _output = [];
|
||||
_output.push({slot: ["actions", {}, (actions) => {
|
||||
const _output = [];
|
||||
_output.push({tag: ["button", {"data-onclick": this.addUser}, false]});
|
||||
_output.push("Add User");
|
||||
_output.push("</button>");
|
||||
return [_output, this];
|
||||
}]});
|
||||
return [_output, this];
|
||||
}]});
|
||||
|
||||
// ... rest of compilation
|
||||
|
||||
_output.push("</div>");
|
||||
return [_output, this];
|
||||
}
|
||||
```
|
||||
|
||||
## Component CSS Classes
|
||||
|
||||
When components are rendered, the runtime automatically applies CSS classes based on the component's inheritance hierarchy:
|
||||
|
||||
```html
|
||||
<!-- For a UserCard extending BaseCard extending Component -->
|
||||
<div class="UserCard BaseCard Component" ...>
|
||||
```
|
||||
|
||||
This enables CSS targeting at any level of the hierarchy:
|
||||
```css
|
||||
.Component { /* all components */ }
|
||||
.UserCard { /* specific component type */ }
|
||||
.BaseCard { /* all cards */ }
|
||||
```
|
||||
|
||||
**Implementation Note**: The Component base class automatically applies these classes during initialization. This feature works regardless of minification since class names are preserved through the component's constructor name.
|
||||
|
||||
## Critical Implementation Rules
|
||||
|
||||
### What MUST Be Preserved
|
||||
|
||||
1. **Instruction Array Structure**: The `{tag:...}`, `{comp:...}`, `{slot:...}` format
|
||||
2. **Return Tuple**: Functions MUST return `[instructions, context]`
|
||||
3. **Deferred Component Init**: Components render as placeholders first
|
||||
4. **Single DOM Write**: Build all HTML, write once
|
||||
5. **Content Function**: Parent components receive content function to access slots
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
1. **Forgetting Return Format**: Must return `[array, context]`, not just array
|
||||
2. **String vs Instruction**: Closing tags are strings, opening tags are instructions
|
||||
3. **Context Loss**: Without proper binding, nested functions lose component reference
|
||||
4. **Direct DOM Manipulation**: Never manipulate DOM during instruction building
|
||||
5. **Missing html() Escaping**: User content must be escaped
|
||||
6. **Slots Outside Components**: Slots must be inside component content functions
|
||||
449
node_modules/@jqhtml/parser/README.md
generated
vendored
449
node_modules/@jqhtml/parser/README.md
generated
vendored
@@ -1,445 +1,30 @@
|
||||
# JQHTML Parser
|
||||
# @jqhtml/parser
|
||||
|
||||
The JQHTML parser converts template files into JavaScript functions that use the component system.
|
||||
Compiles `.jqhtml` templates into JavaScript for the JQHTML component framework.
|
||||
|
||||
## Quick Start - Common Patterns
|
||||
|
||||
### Basic Component with innerHTML (Most Common)
|
||||
|
||||
```jqhtml
|
||||
<!-- Define a component with default attributes -->
|
||||
<Define:Card tag="article" class="card" style="padding: 10px">
|
||||
<div class="card-body">
|
||||
<%= content() %> <!-- Outputs whatever innerHTML was passed -->
|
||||
</div>
|
||||
</Define:Card>
|
||||
|
||||
<!-- Usage - just pass innerHTML like regular HTML -->
|
||||
<Card class="featured">
|
||||
<h3>Card Title</h3>
|
||||
<p>This is the card content.</p>
|
||||
<button>Click me</button>
|
||||
</Card>
|
||||
|
||||
<!-- Result: <article class="card featured Card Jqhtml_Component" style="padding: 10px"> -->
|
||||
```bash
|
||||
npx jqhtml-compile compile components/*.jqhtml -o bundle.js --sourcemap
|
||||
```
|
||||
|
||||
This `content()` pattern is the **primary way** to compose components in JQHTML. It works like regular HTML - the innerHTML you pass gets rendered where `content()` is called.
|
||||
Templates look like HTML with embedded JavaScript:
|
||||
|
||||
### Container Component Example
|
||||
|
||||
```jqhtml
|
||||
<!-- A container with default size that can be overridden -->
|
||||
<Define:Container $size="medium" class="container">
|
||||
<div class="container-inner <%= this.args.size %>">
|
||||
<%= content() %> <!-- Simple innerHTML output -->
|
||||
</div>
|
||||
</Define:Container>
|
||||
|
||||
<!-- Usage - override the default size -->
|
||||
<Container $size="large">
|
||||
<p>Any content here</p>
|
||||
<UserList />
|
||||
<Footer />
|
||||
</Container>
|
||||
```
|
||||
|
||||
**Note:** Slots (`<#slotname>`) are an advanced feature only needed when you have multiple distinct content areas. For 95% of use cases, the simple `content()` pattern shown above is recommended.
|
||||
|
||||
### Define Tag Attributes (Component Configuration)
|
||||
|
||||
Define tags support three types of attributes for configuring components:
|
||||
|
||||
```jqhtml
|
||||
<Define:Contacts_DataGrid
|
||||
extends="DataGrid_Abstract"
|
||||
$ajax_endpoint=Frontend_Contacts_Controller.datagrid_fetch
|
||||
$per_page=25
|
||||
class="card DataGrid">
|
||||
<div>Content here</div>
|
||||
</Define:Contacts_DataGrid>
|
||||
```
|
||||
|
||||
**1. `extends=""` - Template Inheritance**
|
||||
Explicitly declare parent template for inheritance (without requiring a JavaScript class).
|
||||
|
||||
**2. `$property=value` - Default Args**
|
||||
Set default values for `this.args` that component invocations can override:
|
||||
- **Quoted**: `$user_id="123"` → String literal `"123"`
|
||||
- **Unquoted**: `$handler=MyController.fetch` → Raw JavaScript expression
|
||||
- These become defaults; invocations can override them
|
||||
|
||||
**3. Regular Attributes**
|
||||
Standard HTML attributes like `class=""`, `tag=""` applied to root element.
|
||||
|
||||
**Generated Output:**
|
||||
```javascript
|
||||
{
|
||||
name: 'Contacts_DataGrid',
|
||||
tag: 'div',
|
||||
defaultAttributes: {"class": "card DataGrid"},
|
||||
defineArgs: {"ajax_endpoint": Frontend_Contacts_Controller.datagrid_fetch, "per_page": "25"},
|
||||
extends: 'DataGrid_Abstract',
|
||||
render: function() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Slot-Based Template Inheritance
|
||||
|
||||
When a template contains **ONLY slots** (no HTML), it automatically inherits the parent class template:
|
||||
|
||||
```jqhtml
|
||||
<!-- Parent: DataGrid_Abstract.jqhtml -->
|
||||
<Define:DataGrid_Abstract>
|
||||
<table>
|
||||
<thead><tr><%= content('header') %></tr></thead>
|
||||
<tbody>
|
||||
<% for (let record of this.data.records) { %>
|
||||
<tr><%= content('row', record) %></tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</Define:DataGrid_Abstract>
|
||||
|
||||
<!-- Child: Users_DataGrid.jqhtml (slot-only) -->
|
||||
<Define:Users_DataGrid>
|
||||
<#header>
|
||||
<th>ID</th><th>Name</th><th>Email</th>
|
||||
</#header>
|
||||
<#row>
|
||||
<td><%= row.id %></td>
|
||||
<td><%= row.name %></td>
|
||||
<td><%= row.email %></td>
|
||||
</#row>
|
||||
</Define:Users_DataGrid>
|
||||
```
|
||||
|
||||
The parser detects slot-only templates and generates `{_slots: {...}}` format. Runtime walks prototype chain to find parent templates. **Data passing**: `content('row', record)` passes data to slot parameter `function(row) { ... }`. **Reserved words**: Slot names cannot be JavaScript keywords (`function`, `if`, etc.) - parser rejects with fatal error.
|
||||
|
||||
## Lexer (Task 1 - COMPLETE)
|
||||
|
||||
The lexer is the first stage of the parser. It converts raw JQHTML template text into a stream of tokens.
|
||||
|
||||
### Features
|
||||
|
||||
- **No regex** - Uses simple character scanning for maintainability
|
||||
- **Position tracking** - Every token includes line, column, and absolute positions for source maps
|
||||
- **Simple token types** - Clear, unambiguous token categories
|
||||
- **Efficient scanning** - Single pass through the input
|
||||
|
||||
### Token Types
|
||||
|
||||
- `TEXT` - Plain HTML/text content
|
||||
- `EXPRESSION_START` - `<%=` opening tag
|
||||
- `CODE_START` - `<%` opening tag
|
||||
- `TAG_END` - `%>` closing tag
|
||||
- `DEFINE_START`, `DEFINE_END` - Component definition tags
|
||||
- `COMPONENT_NAME` - Component identifier
|
||||
- `JAVASCRIPT` - JavaScript code within tags
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { Lexer } from '@jqhtml/parser';
|
||||
|
||||
const template = `
|
||||
<Define:MyComponent>
|
||||
<h1><%= this.data.title %></h1>
|
||||
<% if (this.data.show) { %>
|
||||
<p>Content here</p>
|
||||
```html
|
||||
<Define:Product_Card class="card">
|
||||
<h3><%= this.data.name %></h3>
|
||||
<% if (this.data.on_sale) { %>
|
||||
<span class="badge">Sale!</span>
|
||||
<% } %>
|
||||
</Define:MyComponent>
|
||||
`;
|
||||
|
||||
const lexer = new Lexer(template);
|
||||
const tokens = lexer.tokenize();
|
||||
|
||||
// tokens is an array of Token objects with:
|
||||
// - type: TokenType
|
||||
// - value: string
|
||||
// - line: number
|
||||
// - column: number
|
||||
// - start: number (absolute position)
|
||||
// - end: number
|
||||
</Define:Product_Card>
|
||||
```
|
||||
|
||||
### Testing
|
||||
The parser handles the template syntax - `<Define:>` blocks, `<%= %>` expressions, control flow, slots, event bindings. Output is JavaScript that the `@jqhtml/core` runtime understands.
|
||||
|
||||
```bash
|
||||
# Build the parser
|
||||
npm run build
|
||||
## Status
|
||||
|
||||
# Run the test suite
|
||||
node test-lexer.js
|
||||
Alpha release. Works well enough that I use it daily, but documentation is sparse and you might hit edge cases. Webpack loader and proper docs coming soon.
|
||||
|
||||
# Run the demo
|
||||
node demo-lexer.js
|
||||
```
|
||||
Found a bug or built something cool? Drop me a line.
|
||||
|
||||
### Implementation Notes
|
||||
---
|
||||
|
||||
The lexer uses a simple state machine approach:
|
||||
|
||||
1. **Text scanning** - Default mode, captures plain text
|
||||
2. **Tag detection** - Looks for `<%`, `<%=`, `%>` sequences
|
||||
3. **Keyword matching** - After `<%`, checks for control flow keywords
|
||||
4. **JavaScript capture** - Captures code between tags
|
||||
5. **Position tracking** - Updates line/column for every character
|
||||
|
||||
No regex patterns are used. All scanning is done with:
|
||||
- `match_sequence()` - Check for exact string match
|
||||
- `match_keyword()` - Check for keyword with word boundary
|
||||
- Character-by-character advancement
|
||||
|
||||
## Parser/AST Builder (Task 2 - COMPLETE)
|
||||
|
||||
The parser takes the token stream from the lexer and builds an Abstract Syntax Tree (AST) representing the template structure.
|
||||
|
||||
### Features
|
||||
|
||||
- **Recursive descent parsing** - Simple, predictable parsing algorithm
|
||||
- **Clear node types** - Each AST node represents a specific construct
|
||||
- **Position preservation** - All nodes include source positions
|
||||
- **Error reporting** - Clear messages with line/column information
|
||||
|
||||
### AST Node Types
|
||||
|
||||
- `Program` - Root node containing all top-level definitions
|
||||
- `ComponentDefinition` - Component template definition
|
||||
- `Text` - Plain text/HTML content
|
||||
- `Expression` - JavaScript expressions (`<%= ... %>`)
|
||||
- `IfStatement` - Conditional rendering with optional else
|
||||
- `ForStatement` - Loop constructs
|
||||
- `CodeBlock` - Generic JavaScript code blocks
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { Lexer, Parser } from '@jqhtml/parser';
|
||||
|
||||
const template = `
|
||||
<Define:Card>
|
||||
<div class="card">
|
||||
<h3><%= title %></h3>
|
||||
<% if (showContent) { %>
|
||||
<p><%= content %></p>
|
||||
<% } %>
|
||||
</div>
|
||||
</Define:Card>
|
||||
`;
|
||||
|
||||
// Tokenize
|
||||
const lexer = new Lexer(template);
|
||||
const tokens = lexer.tokenize();
|
||||
|
||||
// Parse to AST
|
||||
const parser = new Parser(tokens);
|
||||
const ast = parser.parse();
|
||||
|
||||
// AST structure:
|
||||
// {
|
||||
// type: 'Program',
|
||||
// body: [{
|
||||
// type: 'ComponentDefinition',
|
||||
// name: 'Card',
|
||||
// body: [...]
|
||||
// }]
|
||||
// }
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run the parser test suite
|
||||
node test-parser.js
|
||||
|
||||
# Run the interactive demo
|
||||
node demo-parser.js
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
The parser uses straightforward techniques:
|
||||
|
||||
1. **Token consumption** - Advances through tokens one at a time
|
||||
2. **Lookahead** - Peeks at upcoming tokens to decide parsing path
|
||||
3. **Context tracking** - Knows when inside components, loops, etc.
|
||||
4. **Error recovery** - Provides helpful error messages
|
||||
|
||||
No parser generators or complex algorithms - just simple recursive functions that build nodes.
|
||||
|
||||
## Code Generator (Task 3 - COMPLETE)
|
||||
|
||||
The code generator takes the AST and produces executable JavaScript functions that work with the JQHTML component system.
|
||||
|
||||
### Features
|
||||
|
||||
- **jQuery-based DOM manipulation** - Generates efficient jQuery code
|
||||
- **Component render functions** - Each template becomes a render function
|
||||
- **Control flow handling** - Properly handles if/else and for loops
|
||||
- **Expression evaluation** - Safely evaluates and renders expressions
|
||||
- **Colon syntax support** - Strips trailing colons from control statements
|
||||
|
||||
### Generated Code Structure
|
||||
|
||||
```javascript
|
||||
// Each component gets a render function
|
||||
jqhtml_components.set('ComponentName', {
|
||||
name: 'ComponentName',
|
||||
render: function render() {
|
||||
const $root = $('<div></div>');
|
||||
const $current = $root;
|
||||
|
||||
// Generated DOM manipulation code here
|
||||
|
||||
return $root.children();
|
||||
},
|
||||
dependencies: []
|
||||
});
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { Lexer, Parser, CodeGenerator } from '@jqhtml/parser';
|
||||
|
||||
const template = `
|
||||
<Define:MyComponent>
|
||||
<h1><%= this.data.title %></h1>
|
||||
<% if (this.data.items) { %>
|
||||
<ul>
|
||||
<% for (const item of this.data.items) { %>
|
||||
<li><%= item %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</Define:MyComponent>
|
||||
`;
|
||||
|
||||
// Generate the code
|
||||
const lexer = new Lexer(template);
|
||||
const tokens = lexer.tokenize();
|
||||
const parser = new Parser(tokens);
|
||||
const ast = parser.parse();
|
||||
const generator = new CodeGenerator();
|
||||
const result = generator.generate(ast);
|
||||
|
||||
// Use in a component
|
||||
class MyComponent extends Component {
|
||||
async on_render() {
|
||||
const template = result.components.get('MyComponent');
|
||||
const elements = template.render.call(this);
|
||||
this.$.append(elements);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run the code generator test suite
|
||||
node test-codegen.js
|
||||
|
||||
# Run the interactive demo
|
||||
node demo-codegen.js
|
||||
|
||||
# View the integration example
|
||||
# Open example-integration.html in a browser
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
The code generator:
|
||||
|
||||
1. **Traverses the AST** - Visits each node and generates appropriate code
|
||||
2. **Maintains context** - Tracks current jQuery element for appending
|
||||
3. **Handles nesting** - For loops create temporary containers
|
||||
4. **Strips syntax** - Removes trailing colons from control flow
|
||||
5. **Escapes strings** - Properly escapes text content for JavaScript
|
||||
|
||||
### v1 JavaScript Compilation Analysis
|
||||
|
||||
A comprehensive analysis of how JQHTML v1 compiles templates to JavaScript has been documented in [JQHTML_V1_JAVASCRIPT_COMPILATION_PATTERNS.md](../../JQHTML_V1_JAVASCRIPT_COMPILATION_PATTERNS.md). This analysis reveals:
|
||||
|
||||
- **Instruction-based output**: Templates compile to arrays of rendering instructions
|
||||
- **Three instruction types**: `{tag:...}`, `{comp:...}`, `{block:...}`
|
||||
- **Context preservation**: The `_that = this` pattern for nested functions
|
||||
- **Single-pass DOM construction**: Efficient rendering with one `$.html()` call
|
||||
- **Deferred component initialization**: Two-phase process for component creation
|
||||
|
||||
These patterns inform v2 implementation decisions, particularly around maintaining the efficient instruction-based architecture while modernizing the JavaScript output.
|
||||
|
||||
## Implementation Status (December 2024)
|
||||
|
||||
### ✅ Completed Tasks
|
||||
|
||||
1. **Lexer** - Full tokenization with position tracking
|
||||
2. **Parser** - AST generation with all v2 syntax
|
||||
3. **Code Generator** - Instruction array generation
|
||||
4. **Component Integration** - Runtime integration complete
|
||||
5. **Slot System** - `<#name>` syntax fully implemented
|
||||
6. **Nested Components** - `<ComponentName>` recognition
|
||||
7. **Binding Syntax** - `:prop` and `@event` support
|
||||
|
||||
### Current Features
|
||||
|
||||
- **Template Syntax**: HTML with embedded JavaScript
|
||||
- **Control Flow**: `if/else/elseif` and `for` loops (both colon and brace styles)
|
||||
- **Components**: `<Define:Name>` definitions and `<ComponentName>` invocations
|
||||
- **Slots**: `<#name>content</#name>` and `<#name />` syntax
|
||||
- **Expressions**: `<%= expression %>` for output
|
||||
- **Bindings**: `:text`, `:value`, `:class`, `:style` for data binding
|
||||
- **Events**: `@click`, `@change`, etc. for event handlers
|
||||
- **Scoped IDs**: `$sid` attribute for component-scoped IDs
|
||||
|
||||
### Instruction Format
|
||||
|
||||
The parser generates v1-compatible instruction arrays:
|
||||
|
||||
```javascript
|
||||
// HTML tags
|
||||
{tag: ["div", {"class": "card"}, false]} // open tag
|
||||
{tag: ["div", {}, true]} // close tag
|
||||
|
||||
// Components
|
||||
{comp: ["UserCard", {name: "John"}]}
|
||||
|
||||
// Slots
|
||||
{slot: ["header", {}, (props) => {
|
||||
const _output = [];
|
||||
_output.push("Header content");
|
||||
return [_output, this];
|
||||
}]}
|
||||
|
||||
// Text content
|
||||
"Plain text"
|
||||
|
||||
// Expressions
|
||||
{expr: () => this.data.title}
|
||||
```
|
||||
|
||||
### Integration with Core
|
||||
|
||||
Templates compile to functions that return `[instructions, context]`:
|
||||
|
||||
```javascript
|
||||
function render() {
|
||||
const _output = [];
|
||||
|
||||
// Generate instructions...
|
||||
_output.push({tag: ["h1", {}, false]});
|
||||
_output.push("Hello World");
|
||||
_output.push({tag: ["h1", {}, true]});
|
||||
|
||||
return [_output, this];
|
||||
}
|
||||
```
|
||||
|
||||
The core runtime's instruction processor handles these arrays to create DOM.
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. **Build Tools** - Webpack loader for `.jqhtml` imports
|
||||
2. **Source Maps** - Full debugging support
|
||||
3. **VS Code Extension** - Syntax highlighting
|
||||
4. **Autoformatter** - Code formatting for `.jqhtml` files
|
||||
**hansonxyz** · [hanson.xyz](https://hanson.xyz/) · [github](https://github.com/hansonxyz)
|
||||
|
||||
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -1348,7 +1348,7 @@ export class CodeGenerator {
|
||||
for (const [name, component] of this.components) {
|
||||
code += `// Component: ${name}\n`;
|
||||
code += `jqhtml_components.set('${name}', {\n`;
|
||||
code += ` _jqhtml_version: '2.2.221',\n`; // Version will be replaced during build
|
||||
code += ` _jqhtml_version: '2.2.222',\n`; // Version will be replaced during build
|
||||
code += ` name: '${name}',\n`;
|
||||
code += ` tag: '${component.tagName}',\n`;
|
||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||
|
||||
7
node_modules/@jqhtml/parser/package.json
generated
vendored
Normal file → Executable file
7
node_modules/@jqhtml/parser/package.json
generated
vendored
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/parser",
|
||||
"version": "2.2.221",
|
||||
"version": "2.2.222",
|
||||
"description": "JQHTML template parser - converts templates to JavaScript",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -23,13 +23,12 @@
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://privatenpm.hanson.xyz/"
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"bin",
|
||||
"README.md",
|
||||
"LLM_REFERENCE.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"keywords": [
|
||||
|
||||
2
node_modules/@jqhtml/router/dist/jqhtml-router.esm.js
generated
vendored
2
node_modules/@jqhtml/router/dist/jqhtml-router.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* JQHTML Router v2.2.221
|
||||
* JQHTML Router v2.2.222
|
||||
* (c) 2025 JQHTML Team
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
2
node_modules/@jqhtml/router/package.json
generated
vendored
Normal file → Executable file
2
node_modules/@jqhtml/router/package.json
generated
vendored
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/router",
|
||||
"version": "2.2.221",
|
||||
"version": "2.2.222",
|
||||
"description": "Client-side routing for JQHTML applications",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
@@ -1 +1 @@
|
||||
2.2.221
|
||||
2.2.222
|
||||
|
||||
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.221.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.222.vsix
generated
vendored
Normal file → Executable file
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.221.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.222.vsix
generated
vendored
Normal file → Executable file
Binary file not shown.
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
Normal file → Executable file
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
Normal file → Executable file
@@ -2,7 +2,7 @@
|
||||
"name": "@jqhtml/vscode-extension",
|
||||
"displayName": "JQHTML",
|
||||
"description": "Syntax highlighting and language support for JQHTML template files",
|
||||
"version": "2.2.221",
|
||||
"version": "2.2.222",
|
||||
"publisher": "jqhtml",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
|
||||
4
node_modules/@jqhtml/webpack-loader/package.json
generated
vendored
Normal file → Executable file
4
node_modules/@jqhtml/webpack-loader/package.json
generated
vendored
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/webpack-loader",
|
||||
"version": "2.2.221",
|
||||
"version": "2.2.222",
|
||||
"description": "Webpack loader for JQHTML templates",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"template"
|
||||
],
|
||||
"dependencies": {
|
||||
"@jqhtml/parser": "2.2.221",
|
||||
"@jqhtml/parser": "2.2.222",
|
||||
"@types/loader-utils": "^2.0.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
|
||||
Reference in New Issue
Block a user