# 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: ```javascript // 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: ```javascript // 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 ```javascript 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: ```jqhtml

<% if (this.data.showDetails): %>
<% endif; %> <#content />
``` ### 3. Generated Module Format The loader generates ES modules with this structure: ```javascript // 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 ```javascript // 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 ```json { "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 ```javascript // 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 ```javascript // 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 ```javascript // 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 ```typescript // 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 ```typescript // 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 ```typescript // 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 { 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 { // TypeScript ensures type safety console.log(`Loading user: ${this.data.name}`); } } ``` ## Adapting to Other Environments ### Vite Integration ```javascript // 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: ```json { "extends": "@parcel/config-default", "transformers": { "*.jqhtml": ["@jqhtml/parcel-transformer"] } } ``` ### Create React App Ejected After ejecting, modify `webpack.config.js`: ```javascript // 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: ```javascript 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: ```javascript { test: /\.jqhtml$/, use: { loader: '@jqhtml/webpack-loader', options: { sourceMap: true } } } ``` ### Issue 3: jQuery Not Available **Problem**: `$` is not defined errors **Solution**: Provide jQuery globally: ```javascript 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`: ```javascript watchOptions: { ignored: /node_modules/, poll: 1000 // For network filesystems } ``` ## Performance Optimization ### Code Splitting ```javascript // 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 ```javascript // 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: ```javascript // Good - tree-shakeable import { Component, mount } from '@jqhtml/core'; // Bad - imports entire library import * as jqhtml from '@jqhtml/core'; ``` ## Testing Setup ### Jest Configuration ```javascript // jest.config.js module.exports = { transform: { '^.+\\.jqhtml$': '@jqhtml/jest-transformer', '^.+\\.jsx?$': 'babel-jest' }, moduleFileExtensions: ['js', 'jqhtml'], testEnvironment: 'jsdom', setupFilesAfterEnv: ['/test/setup.js'] }; ``` ### Test Setup File ```javascript // 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 ```javascript // Enable verbose output module.exports = { stats: 'verbose', infrastructureLogging: { level: 'verbose' } }; ``` ### Template Compilation Debugging ```javascript // Add to loader options { loader: '@jqhtml/webpack-loader', options: { debug: true, logLevel: 'verbose' } } ``` ### Runtime Debugging ```javascript // Enable debug mode in core import { setDebugMode } from '@jqhtml/core'; setDebugMode(true); ``` ## Migration Guide ### From Inline Templates **Before** (v1 style): ```javascript const template = `

:text="name"

`; ``` **After** (v2 with webpack): ```jqhtml

``` ### From Runtime Compilation **Before**: ```javascript Component.create('UserCard', { template: templateString, // ... }); ``` **After**: ```javascript 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.