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>
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:
- @jqhtml/parser - Compiles templates to JavaScript at build time
- @jqhtml/webpack-loader - Integrates the parser with webpack
- @jqhtml/core - Runtime library for component lifecycle and rendering
- @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.