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:
root
2025-11-24 09:41:48 +00:00
parent 0143f6ae9f
commit bd5809fdbd
20716 changed files with 387 additions and 6444 deletions

View File

@@ -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
View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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": [