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>
734 lines
16 KiB
Markdown
Executable File
734 lines
16 KiB
Markdown
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:
|
|
|
|
```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
|
|
<!-- 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:
|
|
|
|
```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<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
|
|
|
|
```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: ['<rootDir>/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 = `
|
|
<div class="user">
|
|
<h2>:text="name"</h2>
|
|
</div>
|
|
`;
|
|
```
|
|
|
|
**After** (v2 with webpack):
|
|
```jqhtml
|
|
<!-- UserCard.jqhtml -->
|
|
<Define:UserCard as="div">
|
|
<h2 :text="this.data.name"></h2>
|
|
</Define:UserCard>
|
|
```
|
|
|
|
### 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. |