Files
rspade_system/docs/jqhtml_quickstart.md
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
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>
2025-10-21 02:08:33 +00:00

16 KiB
Executable File

JQHTML Webpack Build Process - Quick Start Guide

Overview

This document provides a comprehensive overview of how JQHTML's build tools work in an npm Express project, with contextual information on how it might be applicable to other environments. The JQHTML framework provides a webpack-based build system that compiles .jqhtml template files into efficient JavaScript modules at build time, eliminating the need for runtime template parsing.

Architecture Summary

JQHTML consists of four core packages that work together:

  1. @jqhtml/parser - Compiles templates to JavaScript at build time
  2. @jqhtml/webpack-loader - Integrates the parser with webpack
  3. @jqhtml/core - Runtime library for component lifecycle and rendering
  4. @jqhtml/router - Optional SPA routing functionality

The build process transforms declarative .jqhtml templates into instruction-based JavaScript modules that integrate seamlessly with webpack's module system.

Setting Up Webpack for JQHTML

Basic Configuration

The minimal webpack configuration required to process .jqhtml files:

// webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.jqhtml$/,
        use: '@jqhtml/webpack-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jqhtml', '.json']
  }
};

Using Configuration Helpers

JQHTML provides helper functions to simplify webpack configuration:

// webpack.config.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');

module.exports = addJQHTMLSupport({
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  // Your other webpack configurations
  devtool: 'source-map',
  mode: process.env.NODE_ENV || 'development'
});

Advanced Configuration with Options

const { createJQHTMLRule } = require('@jqhtml/webpack-loader');

module.exports = {
  module: {
    rules: [
      createJQHTMLRule({
        sourceMap: true,
        minify: process.env.NODE_ENV === 'production'
      }),
      // Other rules (CSS, images, etc.)
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

How the Build Process Works

1. Template Compilation Pipeline

The webpack loader processes .jqhtml files through a multi-stage pipeline:

.jqhtml file → Lexer → Tokens → Parser → AST → CodeGenerator → ES Module

Stage 1: Lexical Analysis

  • The Lexer scans the template character by character
  • Generates a stream of tokens representing template elements
  • Tracks line/column positions for error reporting

Stage 2: Parsing

  • The Parser converts tokens into an Abstract Syntax Tree (AST)
  • Validates template syntax and structure
  • Reports errors with precise file locations

Stage 3: Code Generation

  • The CodeGenerator traverses the AST
  • Produces JavaScript instruction arrays
  • Wraps output in ES module exports

2. Template Syntax Processing

The loader handles various template constructs:

<!-- Component Definition -->
<Define:UserCard as="div">
  <!-- Data Binding -->
  <h2 :text="this.data.title"></h2>
  
  <!-- Conditional Rendering -->
  <% if (this.data.showDetails): %>
    <div :html="this.data.description"></div>
  <% endif; %>
  
  <!-- Event Handling -->
  <button @click="handleClick">Click Me</button>
  
  <!-- Slots for Content Projection -->
  <#content />
  
  <!-- Scoped Element IDs -->
  <div $id="output"></div>
</Define:UserCard>

3. Generated Module Format

The loader generates ES modules with this structure:

// Generated from UserCard.jqhtml
const template = {
  name: 'UserCard',
  as: 'div',
  defaultAttributes: {},
  render: function render() {
    const _output = [];
    
    // Opening tag instruction
    _output.push({tag: ["div", {"class": "user-card"}, false]});
    
    // Text binding instruction
    _output.push({tag: ["h2", {}, false]});
    _output.push({expr: () => this.data.title});
    _output.push({tag: ["h2", {}, true]});
    
    // Conditional block
    if (this.data.showDetails) {
      _output.push({tag: ["div", {}, false]});
      _output.push({html: () => this.data.description});
      _output.push({tag: ["div", {}, true]});
    }
    
    // Event handler
    _output.push({tag: ["button", {}, false]});
    _output.push({on: ["click", this.handleClick]});
    _output.push("Click Me");
    _output.push({tag: ["button", {}, true]});
    
    // Slot for content projection
    _output.push({slot: ["content", {}, null]});
    
    // Scoped ID element
    _output.push({tag: ["div", {"id": this.$id("output")}, false]});
    _output.push({tag: ["div", {}, true]});
    
    // Closing tag
    _output.push({tag: ["div", {}, true]});
    
    return [_output, this];
  }
};

export default template;

Express Integration Pattern

Project Structure

my-jqhtml-app/
├── src/
│   ├── components/
│   │   ├── App.jqhtml
│   │   ├── UserCard.jqhtml
│   │   └── UserList.jqhtml
│   ├── index.js
│   └── styles.css
├── server/
│   └── index.js
├── dist/
│   └── (generated bundles)
├── package.json
└── webpack.config.js

Express Server Setup

// server/index.js
const express = require('express');
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('../webpack.config.js');

const app = express();
const compiler = webpack(config);

// Development mode with hot reload
if (process.env.NODE_ENV !== 'production') {
  app.use(webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath
  }));
  
  app.use(webpackHotMiddleware(compiler));
}

// Serve static files
app.use(express.static(path.join(__dirname, '../dist')));

// API routes
app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'John Doe', active: true },
    { id: 2, name: 'Jane Smith', active: false }
  ]);
});

// Fallback to index.html for SPA
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../dist/index.html'));
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Package.json Scripts

{
  "name": "my-jqhtml-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "NODE_ENV=development nodemon server/index.js",
    "build": "webpack --mode production",
    "start": "NODE_ENV=production node server/index.js",
    "watch": "webpack --watch --mode development"
  },
  "dependencies": {
    "@jqhtml/core": "^2.0.0",
    "@jqhtml/router": "^2.0.0",
    "express": "^4.18.0",
    "jquery": "^3.7.0"
  },
  "devDependencies": {
    "@jqhtml/parser": "^2.0.0",
    "@jqhtml/webpack-loader": "^2.0.0",
    "webpack": "^5.95.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-middleware": "^5.3.0",
    "webpack-hot-middleware": "^2.25.0",
    "nodemon": "^2.0.0",
    "html-webpack-plugin": "^5.5.0"
  }
}

Entry Point Configuration

Main Application Entry

// src/index.js
import { Component, mount } from '@jqhtml/core';
import AppTemplate from './components/App.jqhtml';
import './styles.css';

// Define the root component
class App extends Component {
  static template = AppTemplate;
  
  constructor(element, data, parent) {
    super(element, data, parent);
    this.state = {
      users: [],
      loading: true
    };
  }
  
  async on_load() {
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      this.update({ users, loading: false });
    } catch (error) {
      console.error('Failed to load users:', error);
      this.update({ loading: false, error: error.message });
    }
  }
}

// Mount the application
$(document).ready(() => {
  mount(App, $('#app'), {
    title: 'JQHTML Demo Application'
  });
});

Development vs Production Builds

Development Configuration

// webpack.dev.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = addJQHTMLSupport({
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    'webpack-hot-middleware/client?reload=true',
    './src/index.js'
  ],
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js',
    publicPath: '/'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      inject: true
    }),
    new webpack.HotModuleReplacementPlugin()
  ]
});

Production Configuration

// webpack.prod.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = addJQHTMLSupport({
  mode: 'production',
  devtool: 'source-map',
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.[contenthash].js',
    clean: true
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: -10
        }
      }
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    })
  ]
});

TypeScript Integration

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "lib": ["ES2020", "DOM"],
    "jsx": "preserve",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*", "types/**/*"]
}

Type Declarations

// types/jqhtml.d.ts
declare module '*.jqhtml' {
  import { ComponentTemplate } from '@jqhtml/core';
  const template: ComponentTemplate;
  export default template;
}

// Extend jQuery types
interface JQuery {
  component(): Component | null;
  findComponent(name: string): Component | null;
}

TypeScript Component

// src/components/UserCard.ts
import { Component, ComponentOptions } from '@jqhtml/core';
import UserCardTemplate from './UserCard.jqhtml';

interface UserData {
  id: number;
  name: string;
  email: string;
  active: boolean;
}

export class UserCard extends Component<UserData> {
  static template = UserCardTemplate;
  
  constructor(element: JQuery, data: UserData, parent?: Component) {
    super(element, data, parent);
  }
  
  handleClick(): void {
    this.emit('user-selected', this.data);
  }
  
  async on_load(): Promise<void> {
    // TypeScript ensures type safety
    console.log(`Loading user: ${this.data.name}`);
  }
}

Adapting to Other Environments

Vite Integration

// vite.config.js
import { defineConfig } from 'vite';
import { createJQHTMLPlugin } from '@jqhtml/vite-plugin'; // Hypothetical

export default defineConfig({
  plugins: [createJQHTMLPlugin()],
  build: {
    rollupOptions: {
      input: 'src/index.js'
    }
  }
});

Parcel Integration

Parcel should work out of the box with a .parcelrc configuration:

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.jqhtml": ["@jqhtml/parcel-transformer"]
  }
}

Create React App Ejected

After ejecting, modify webpack.config.js:

// Add to module.rules array
{
  test: /\.jqhtml$/,
  use: '@jqhtml/webpack-loader',
  exclude: /node_modules/
}

Common Webpack Pitfalls and Solutions

Issue 1: Module Resolution

Problem: Webpack can't find .jqhtml files

Solution: Add to resolve.extensions:

resolve: {
  extensions: ['.js', '.jqhtml', '.json']
}

Issue 2: Source Maps Not Working

Problem: Can't debug original template code

Solution: Enable source maps in loader options:

{
  test: /\.jqhtml$/,
  use: {
    loader: '@jqhtml/webpack-loader',
    options: {
      sourceMap: true
    }
  }
}

Issue 3: jQuery Not Available

Problem: $ is not defined errors

Solution: Provide jQuery globally:

plugins: [
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery'
  })
]

Issue 4: Template Changes Not Detected

Problem: Webpack doesn't rebuild on template changes

Solution: Ensure watch mode includes .jqhtml:

watchOptions: {
  ignored: /node_modules/,
  poll: 1000 // For network filesystems
}

Performance Optimization

Code Splitting

// Dynamic imports for lazy loading
async loadUserModule() {
  const { UserCard } = await import('./components/UserCard.js');
  const template = await import('./components/UserCard.jqhtml');
  
  // Use the dynamically loaded component
  mount(UserCard, $('#user-container'), userData);
}

Bundle Analysis

// Add to webpack config
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins: [
  new BundleAnalyzerPlugin({
    analyzerMode: 'static',
    openAnalyzer: false
  })
]

Tree Shaking

Ensure proper ES module usage:

// Good - tree-shakeable
import { Component, mount } from '@jqhtml/core';

// Bad - imports entire library
import * as jqhtml from '@jqhtml/core';

Testing Setup

Jest Configuration

// jest.config.js
module.exports = {
  transform: {
    '^.+\\.jqhtml$': '@jqhtml/jest-transformer',
    '^.+\\.jsx?$': 'babel-jest'
  },
  moduleFileExtensions: ['js', 'jqhtml'],
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/test/setup.js']
};

Test Setup File

// test/setup.js
import $ from 'jquery';
global.$ = global.jQuery = $;

// Mock template loader for tests
jest.mock('*.jqhtml', () => ({
  name: 'MockComponent',
  render: jest.fn(() => [[], this])
}), { virtual: true });

Debugging Techniques

Webpack Build Debugging

// Enable verbose output
module.exports = {
  stats: 'verbose',
  infrastructureLogging: {
    level: 'verbose'
  }
};

Template Compilation Debugging

// Add to loader options
{
  loader: '@jqhtml/webpack-loader',
  options: {
    debug: true,
    logLevel: 'verbose'
  }
}

Runtime Debugging

// Enable debug mode in core
import { setDebugMode } from '@jqhtml/core';
setDebugMode(true);

Migration Guide

From Inline Templates

Before (v1 style):

const template = `
  <div class="user">
    <h2>:text="name"</h2>
  </div>
`;

After (v2 with webpack):

<!-- UserCard.jqhtml -->
<Define:UserCard as="div">
  <h2 :text="this.data.name"></h2>
</Define:UserCard>

From Runtime Compilation

Before:

Component.create('UserCard', {
  template: templateString,
  // ...
});

After:

import UserCardTemplate from './UserCard.jqhtml';

class UserCard extends Component {
  static template = UserCardTemplate;
  // ...
}

Conclusion

The JQHTML webpack loader provides a robust build-time compilation system that transforms declarative templates into efficient JavaScript modules. By processing templates at build time rather than runtime, applications benefit from:

  • Better Performance: No runtime template parsing overhead
  • Improved Developer Experience: Build-time error detection and source maps
  • Standard Tooling: Integration with webpack ecosystem
  • Type Safety: Full TypeScript support
  • Code Splitting: Dynamic imports and lazy loading
  • Production Optimization: Minification and tree shaking

The system is designed to work seamlessly with Express servers and can be adapted to other build tools and environments. The key to successful integration is understanding the transformation pipeline and properly configuring webpack to handle .jqhtml files as modules.