Fix code quality violations for publish
Progressive breadcrumb resolution with caching, fix double headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
464
node_modules/@jqhtml/ssr/README.md
generated
vendored
Normal file
464
node_modules/@jqhtml/ssr/README.md
generated
vendored
Normal file
@@ -0,0 +1,464 @@
|
||||
# JQHTML Server-Side Rendering (SSR)
|
||||
|
||||
> **See [SPECIFICATION.md](./SPECIFICATION.md) for the complete technical specification.**
|
||||
|
||||
## Overview
|
||||
|
||||
The JQHTML SSR system renders components to HTML on the server for SEO purposes. Unlike React/Vue/Angular which require separate data-fetching abstractions, JQHTML SSR runs the exact same component code - including `on_load()` with real HTTP requests.
|
||||
|
||||
**Primary use case:** SEO - rendering meaningful HTML for search engine crawlers.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @jqhtml/ssr
|
||||
```
|
||||
|
||||
Or install globally for CLI access:
|
||||
|
||||
```bash
|
||||
npm install -g @jqhtml/ssr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Starting the Server
|
||||
|
||||
```bash
|
||||
cd /var/www/html/jqhtml/aux/ssr
|
||||
npm install
|
||||
|
||||
# Start on TCP port
|
||||
node src/server.js --tcp 9876
|
||||
|
||||
# Or with Unix socket (better performance, local only)
|
||||
node src/server.js --socket /tmp/jqhtml-ssr.sock
|
||||
```
|
||||
|
||||
### Server Options
|
||||
|
||||
```
|
||||
--tcp <port> Listen on TCP port
|
||||
--socket <path> Listen on Unix socket
|
||||
--max-bundles <n> Max cached bundle sets (default: 10)
|
||||
--timeout <ms> Default render timeout (default: 30000)
|
||||
--help Show help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference CLI Example
|
||||
|
||||
The package includes `jqhtml-ssr-example`, a complete reference implementation that demonstrates the full SSR workflow. **Use this as the canonical source of truth for building integrations in any language.**
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Start the SSR server in one terminal
|
||||
jqhtml-ssr --tcp 9876
|
||||
|
||||
# In another terminal, run the example
|
||||
jqhtml-ssr-example \
|
||||
--vendor ./bundles/vendor.js \
|
||||
--app ./bundles/app.js \
|
||||
--component Dashboard_Index_Action \
|
||||
--base-url http://localhost:3000
|
||||
```
|
||||
|
||||
### Example Options
|
||||
|
||||
```
|
||||
REQUIRED:
|
||||
--vendor, -v <path> Path to vendor bundle (contains @jqhtml/core)
|
||||
--app, -a <path> Path to app bundle (contains components)
|
||||
--component, -c <name> Component name to render
|
||||
|
||||
OPTIONS:
|
||||
--args <json> Component arguments as JSON (default: {})
|
||||
--base-url, -b <url> Base URL for fetch requests (default: http://localhost:3000)
|
||||
--timeout, -t <ms> Render timeout in milliseconds (default: 30000)
|
||||
--port, -p <port> SSR server port (default: 9876)
|
||||
--socket, -s <path> Use Unix socket instead of TCP
|
||||
--format, -f <format> Output format: pretty, json, html-only (default: pretty)
|
||||
--help, -h Show help message
|
||||
```
|
||||
|
||||
### Output Formats
|
||||
|
||||
**Pretty (default)** - Human-readable output showing:
|
||||
- Rendered HTML (formatted)
|
||||
- localStorage cache entries
|
||||
- sessionStorage cache entries
|
||||
- Timing information
|
||||
|
||||
```bash
|
||||
jqhtml-ssr-example -v vendor.js -a app.js -c My_Component
|
||||
```
|
||||
|
||||
**JSON** - Raw server response for programmatic use:
|
||||
|
||||
```bash
|
||||
jqhtml-ssr-example -v vendor.js -a app.js -c My_Component --format json > result.json
|
||||
```
|
||||
|
||||
**HTML-only** - Just the rendered HTML:
|
||||
|
||||
```bash
|
||||
jqhtml-ssr-example -v vendor.js -a app.js -c My_Component --format html-only > output.html
|
||||
```
|
||||
|
||||
### Example with Component Arguments
|
||||
|
||||
```bash
|
||||
jqhtml-ssr-example \
|
||||
-v ./bundles/vendor.js \
|
||||
-a ./bundles/app.js \
|
||||
-c User_Profile \
|
||||
--args '{"user_id": 123, "show_avatar": true}'
|
||||
```
|
||||
|
||||
### Integration Reference
|
||||
|
||||
The `jqhtml-ssr-example` source code (`bin/jqhtml-ssr-example.js`) is the **canonical reference** for building integrations. Key implementation details:
|
||||
|
||||
1. **Connection** - TCP socket to `localhost:PORT` or Unix socket
|
||||
2. **Request format** - Newline-delimited JSON
|
||||
3. **Bundle loading** - Read JS files, send as `content` strings
|
||||
4. **Request structure** - See the commented request object in the source
|
||||
5. **Response parsing** - JSON with `status`, `payload`, and `error` fields
|
||||
|
||||
When building integrations in PHP, Python, Go, etc., refer to this example for the exact protocol and data structures.
|
||||
|
||||
---
|
||||
|
||||
## Integration
|
||||
|
||||
### Protocol
|
||||
|
||||
The server uses a simple newline-delimited JSON protocol over TCP or Unix socket.
|
||||
|
||||
**Request format:**
|
||||
```json
|
||||
{
|
||||
"id": "unique-request-id",
|
||||
"type": "render",
|
||||
"payload": {
|
||||
"bundles": [
|
||||
{ "id": "vendor", "content": "..." },
|
||||
{ "id": "app", "content": "..." }
|
||||
],
|
||||
"component": "Dashboard_Index_Action",
|
||||
"args": { "user_id": 123 },
|
||||
"options": {
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"timeout": 30000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response format:**
|
||||
```json
|
||||
{
|
||||
"id": "unique-request-id",
|
||||
"status": "success",
|
||||
"payload": {
|
||||
"html": "<div class=\"Dashboard_Index_Action Component\">...</div>",
|
||||
"cache": {
|
||||
"localStorage": {},
|
||||
"sessionStorage": {}
|
||||
},
|
||||
"timing": {
|
||||
"total_ms": 231,
|
||||
"bundle_load_ms": 49,
|
||||
"render_ms": 99
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PHP Integration Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
class JqhtmlSSR {
|
||||
private $socket;
|
||||
|
||||
public function __construct(string $socketPath = '/tmp/jqhtml-ssr.sock') {
|
||||
$this->socket = stream_socket_client("unix://$socketPath", $errno, $errstr, 5);
|
||||
if (!$this->socket) {
|
||||
throw new Exception("SSR connection failed: $errstr");
|
||||
}
|
||||
}
|
||||
|
||||
public function render(string $component, array $args = [], array $bundles = []): array {
|
||||
$request = json_encode([
|
||||
'id' => uniqid('ssr-'),
|
||||
'type' => 'render',
|
||||
'payload' => [
|
||||
'bundles' => $bundles,
|
||||
'component' => $component,
|
||||
'args' => $args,
|
||||
'options' => [
|
||||
'baseUrl' => 'http://localhost',
|
||||
'timeout' => 30000
|
||||
]
|
||||
]
|
||||
]) . "\n";
|
||||
|
||||
fwrite($this->socket, $request);
|
||||
$response = fgets($this->socket);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
public function ping(): bool {
|
||||
$request = json_encode([
|
||||
'id' => 'ping',
|
||||
'type' => 'ping',
|
||||
'payload' => []
|
||||
]) . "\n";
|
||||
|
||||
fwrite($this->socket, $request);
|
||||
$response = json_decode(fgets($this->socket), true);
|
||||
|
||||
return $response['status'] === 'success';
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
$ssr = new JqhtmlSSR();
|
||||
$result = $ssr->render('Dashboard_Index_Action', ['user_id' => 123], $bundles);
|
||||
|
||||
if ($result['status'] === 'success') {
|
||||
echo $result['payload']['html'];
|
||||
}
|
||||
```
|
||||
|
||||
### Node.js Client Example
|
||||
|
||||
```javascript
|
||||
const net = require('net');
|
||||
|
||||
function sendSSRRequest(port, request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = net.createConnection({ port }, () => {
|
||||
client.write(JSON.stringify(request) + '\n');
|
||||
});
|
||||
|
||||
let data = '';
|
||||
client.on('data', (chunk) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\n')) {
|
||||
client.end();
|
||||
resolve(JSON.parse(data.trim()));
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', reject);
|
||||
setTimeout(() => {
|
||||
client.end();
|
||||
reject(new Error('Request timeout'));
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
// Usage
|
||||
const response = await sendSSRRequest(9876, {
|
||||
id: 'render-1',
|
||||
type: 'render',
|
||||
payload: {
|
||||
bundles: [
|
||||
{ id: 'vendor', content: vendorBundleContent },
|
||||
{ id: 'app', content: appBundleContent }
|
||||
],
|
||||
component: 'Dashboard_Index_Action',
|
||||
args: { user_id: 123 },
|
||||
options: { baseUrl: 'http://localhost:3000' }
|
||||
}
|
||||
});
|
||||
|
||||
console.log(response.payload.html);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Types
|
||||
|
||||
### ping
|
||||
|
||||
Health check.
|
||||
|
||||
```json
|
||||
{ "id": "1", "type": "ping", "payload": {} }
|
||||
```
|
||||
|
||||
Response includes `uptime_ms`.
|
||||
|
||||
### render
|
||||
|
||||
Render a component to HTML.
|
||||
|
||||
Required payload fields:
|
||||
- `bundles` - Array of `{ id, content }` objects
|
||||
- `component` - Component name to render
|
||||
- `options.baseUrl` - Base URL for relative fetch/XHR requests
|
||||
|
||||
Optional:
|
||||
- `args` - Component arguments (default: `{}`)
|
||||
- `options.timeout` - Render timeout in ms (default: 30000)
|
||||
|
||||
### flush_cache
|
||||
|
||||
Clear bundle cache.
|
||||
|
||||
```json
|
||||
{ "id": "1", "type": "flush_cache", "payload": {} }
|
||||
```
|
||||
|
||||
Or flush specific bundle:
|
||||
```json
|
||||
{ "id": "1", "type": "flush_cache", "payload": { "bundle_id": "app" } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `PARSE_ERROR` | Invalid JSON or malformed request |
|
||||
| `BUNDLE_ERROR` | JavaScript syntax error in bundle |
|
||||
| `COMPONENT_NOT_FOUND` | Component not registered after loading bundles |
|
||||
| `RENDER_ERROR` | Error during component lifecycle |
|
||||
| `RENDER_TIMEOUT` | Component did not reach ready state in time |
|
||||
| `INTERNAL_ERROR` | Unexpected server error |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SSR Server (long-running Node.js process) │
|
||||
│ │
|
||||
│ 1. Receive request (component name, args, bundles) │
|
||||
│ 2. Load bundles (cached) into isolated jsdom environment │
|
||||
│ 3. Execute component lifecycle (including real fetch()) │
|
||||
│ 4. Wait for on_ready │
|
||||
│ 5. Return HTML + localStorage/sessionStorage cache dump │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key features:**
|
||||
- **Real data fetching** - Components make actual HTTP requests during SSR
|
||||
- **Cache export** - Server exports storage state for instant client hydration
|
||||
- **Bundle caching** - Prepared bundle code cached with LRU eviction
|
||||
- **Request isolation** - Each render gets fresh jsdom environment
|
||||
- **URL rewriting** - Relative URLs resolved against baseUrl
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
aux/ssr/
|
||||
├── SPECIFICATION.md # Complete technical specification
|
||||
├── README.md # This file
|
||||
├── package.json
|
||||
├── bin/
|
||||
│ └── jqhtml-ssr-example.js # Reference CLI example (canonical integration source)
|
||||
├── src/
|
||||
│ ├── index.js # Package exports
|
||||
│ ├── server.js # TCP/Unix socket server (jqhtml-ssr CLI)
|
||||
│ ├── environment.js # jsdom + jQuery environment setup
|
||||
│ ├── bundle-cache.js # Bundle caching with LRU eviction
|
||||
│ ├── http-intercept.js # fetch/XHR URL rewriting
|
||||
│ ├── storage.js # Fake localStorage/sessionStorage
|
||||
│ └── protocol.js # Message parsing/formatting
|
||||
├── test/
|
||||
│ ├── test-protocol.js # Protocol unit tests (16 tests)
|
||||
│ ├── test-storage.js # Storage unit tests (13 tests)
|
||||
│ └── test-server.js # Server integration tests (6 tests)
|
||||
├── test-manual.js # Manual test with real bundles
|
||||
└── test-debug.js # Debug script for troubleshooting
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Unit tests
|
||||
node test/test-protocol.js
|
||||
node test/test-storage.js
|
||||
node test/test-server.js
|
||||
|
||||
# Manual test with real bundles (requires bundles_for_ssr_test/)
|
||||
node test-manual.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Critical Technical Discoveries
|
||||
|
||||
### 1. jQuery Module Load Order (CRITICAL)
|
||||
|
||||
**jQuery MUST be `require()`'d BEFORE `global.window` is set.**
|
||||
|
||||
```javascript
|
||||
// CORRECT - jQuery returns a factory function
|
||||
const jqueryFactory = require('jquery'); // First!
|
||||
global.window = jsdomWindow; // Second
|
||||
const $ = jqueryFactory(jsdomWindow); // Returns working jQuery
|
||||
|
||||
// WRONG - jQuery auto-initializes and returns an object
|
||||
global.window = jsdomWindow; // First (BAD)
|
||||
const jqueryFactory = require('jquery'); // jQuery sees window, auto-binds
|
||||
const $ = jqueryFactory(jsdomWindow); // Returns object, not function!
|
||||
```
|
||||
|
||||
**Why:** jQuery checks for `global.window` at require-time. If it exists, jQuery auto-initializes against that window instead of returning a factory.
|
||||
|
||||
### 2. Use `vm.runInThisContext` Not `vm.createContext`
|
||||
|
||||
Using `vm.createContext` creates VM context boundary issues where function references don't work across contexts. jQuery wrapper functions fail because closures can't access variables across the boundary.
|
||||
|
||||
**Solution:** Use `vm.runInThisContext` which executes in Node's global context.
|
||||
|
||||
### 3. Bundle Loading Order
|
||||
|
||||
Load bundles in dependency order:
|
||||
1. **Vendor bundle** - Contains `@jqhtml/core`, initializes `window.jqhtml`
|
||||
2. **App bundle** - Contains templates and component classes
|
||||
|
||||
### 4. Global Cleanup
|
||||
|
||||
When destroying environments, set globals to `undefined` instead of using `delete`:
|
||||
```javascript
|
||||
global.window = undefined; // Safe
|
||||
// delete global.window; // Can cause issues
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
```json
|
||||
{
|
||||
"jsdom": "^24.0.0",
|
||||
"jquery": "^3.7.1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [SPECIFICATION.md](./SPECIFICATION.md) - Complete technical specification
|
||||
- [/packages/core/src/component.ts](/packages/core/src/component.ts) - Component lifecycle
|
||||
- [/docs/official/18_boot.md](/docs/official/18_boot.md) - Client-side boot/hydration
|
||||
- [jsdom documentation](https://github.com/jsdom/jsdom)
|
||||
- [jQuery in Node.js](https://www.npmjs.com/package/jquery)
|
||||
Reference in New Issue
Block a user