Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
21 KiB
Executable File
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:
// 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
- Deferred Execution: Instructions can be processed when optimal
- Single DOM Write: All HTML built as string, then one
$.html()call - Complex Attribute Handling: Functions and objects can't go in HTML strings
- Component Coordination: Parent/child relationships preserved through execution
Template Syntax
1. Component Definition
Components are defined using the <Define:> tag:
<Define:ComponentName>
<!-- Component template -->
</Define:ComponentName>
2. $ Attribute System
JQHTML uses the $ prefix for passing data to components as this.args:
Syntax Rules
-
Quoted = Literal String
$title="User Profile" → args.title = "User Profile" $expr="this.user" → args.expr = "this.user" (the string, not evaluated!) -
Unquoted = JavaScript Expression
$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 -
Parentheses for Complex Expressions
$status=(active ? 'online' : 'offline') $data=({...this.user, modified: true}) $items=(this.data.items || [])
Runtime Behavior
- All $ attributes become properties in
this.args(NOTthis.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
<!-- 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):
<%= 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:
<% 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. The parser only intervenes for template-specific syntax that isn't valid JavaScript (like the colon-style below).
4. Template Control Flow
For template-specific control flow, colon style provides PHP-like syntax:
<!-- Colon style (requires endif/endfor) -->
<% if (condition): %>
<p>True branch</p>
<% else: %>
<p>False branch</p>
<% endif; %>
<% for (const item of items): %>
<div><%= item.name %></div>
<% endfor; %>
Note: The brace style shown in Code Blocks above is regular JavaScript, not special template syntax.
Component Invocation
Basic Component Usage
Components are invoked using capital-letter tags:
<UserCard /> <!-- Self-closing, no content -->
<UserCard></UserCard> <!-- Empty paired tags, no content -->
<UserCard>Default content</UserCard> <!-- With innerHTML content (most common) -->
Property Passing
<!-- 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
<div $user=currentUser $theme="dark" $count=42>
<!-- Compiles to: -->
{tag: ["div", {"data-user": currentUser, "data-theme": "dark", "data-count": 42}, false]}
Runtime Behavior:
- The attribute is set via jQuery's
.data()method:$element.data('user', currentUser) - For debugging visibility, if the value is a string or number, it's also set as a DOM attribute
- Objects and arrays are stored in
.data()but not visible in DOM
Special Case: $id="name" → Scoped IDs
The $id attribute has special handling for component-scoped element selection:
<Define:UserCard>
<div $id="container">
<input $id="username" type="text" />
<button $id="submit">Submit</button>
</div>
</Define:UserCard>
Compilation:
// 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:
class UserCard extends Component {
init() {
// Find scoped elements
const $username = this.$id('username'); // Returns $('#username:123')
const $submit = this.$id('submit'); // Returns $('#submit:123')
$submit.on('click', () => {
const value = $username.val();
// ...
});
}
}
Lexical Scoping of _cid
Component IDs flow through lexical scope naturally:
<Define:ParentComponent>
<div $id="parent-element"> <!-- Gets ParentComponent's _cid -->
<ChildComponent>
<div $id="slot-element" /> <!-- Also gets ParentComponent's _cid -->
</ChildComponent>
</div>
</Define:ParentComponent>
<Define:ChildComponent>
<div $id="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:
// 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:
<!-- 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:
<!-- 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:
<!-- 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
- Vue.js Familiarity: Uses the same
:propand@eventsyntax as Vue.js - Always Expressions: Unlike
$attributes which distinguish strings vs expressions, bindings are ALWAYS expressions - Runtime Processing: The runtime will need to handle these specially:
data-bind-*attributes set properties dynamicallydata-on-*attributes attach event listeners
- 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:
<!-- 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()
<!-- 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" $id="wrapper">
<button $onclick="toggle">Toggle</button>
<div class="content" $id="content">
<%= content() %> <!-- Wrapped innerHTML -->
</div>
</div>
</Define:Collapsible>
How content() Works
- Parent passes innerHTML: When invoking
<Component>innerHTML here</Component> - Template receives it: The
contentparameter in render function contains the innerHTML - Output with
<%= content() %>: Call the function to output the HTML where needed - 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:
<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:
<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:
{tag: [...]}- HTML elements{comp: [...]}- Component invocations{slot: [...]}- Slot definitions
1. Tag Instruction
{tag: [tagname, attributes, self_closing]}
Format:
tagname: String - HTML tag nameattributes: Object - Tag attributes (may contain functions!)self_closing: Boolean - Whether tag self-closes
Examples:
{tag: ["div", {"class": "user-card"}, false]}
{tag: ["img", {"src": "/avatar.jpg", "alt": "User"}, true]}
{tag: ["button", {"onclick": this.handleClick}, false]}
{tag: ["div", {"data-id": "header"}, false]} // $id becomes data-id
2. Component Instruction
{comp: [component_name, attributes, optional_innerhtml_function]}
Format:
component_name: String - Component to instantiateattributes: Object - Props/attributes to passoptional_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:
// 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
{slot: [slot_name, attributes, render_function]}
Format:
slot_name: String - Slot identifierattributes: Object - Props for slotrender_function: Function - Returns[instructions, context]
Example:
{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:
_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:
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:
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
let [instructions, context] = template.render.bind(component)(
component,
component.data,
args,
content_fn
);
Phase 2: Instruction Processing
- Build HTML: Process all string and simple tag instructions
- Track Complex Elements: Store elements with function attributes
- Single DOM Write:
component.$.html(html.join('')) - Apply Attributes: Attach event handlers and complex attributes
- 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,inputlink,meta,param,source,track,wbr
Complete Example
Template
<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
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:
<!-- For a UserCard extending BaseCard extending Component -->
<div class="UserCard BaseCard Component" ...>
This enables CSS targeting at any level of the hierarchy:
.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
- Instruction Array Structure: The
{tag:...},{comp:...},{slot:...}format - Return Tuple: Functions MUST return
[instructions, context] - Deferred Component Init: Components render as placeholders first
- Single DOM Write: Build all HTML, write once
- Content Function: Parent components receive content function to access slots
Common Pitfalls
- Forgetting Return Format: Must return
[array, context], not just array - String vs Instruction: Closing tags are strings, opening tags are instructions
- Context Loss: Without proper binding, nested functions lose component reference
- Direct DOM Manipulation: Never manipulate DOM during instruction building
- Missing html() Escaping: User content must be escaped
- Slots Outside Components: Slots must be inside component content functions