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:
268
node_modules/@jqhtml/ssr/src/environment.js
generated
vendored
Normal file
268
node_modules/@jqhtml/ssr/src/environment.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* JQHTML SSR Environment
|
||||
*
|
||||
* Creates isolated jsdom environments for SSR rendering.
|
||||
* Each render request gets a fresh environment to ensure isolation.
|
||||
*/
|
||||
|
||||
const { JSDOM } = require('jsdom');
|
||||
const vm = require('vm');
|
||||
const { createStoragePair } = require('./storage.js');
|
||||
const { installHttpIntercept } = require('./http-intercept.js');
|
||||
|
||||
// CRITICAL: Require jQuery BEFORE any global.window is set
|
||||
// This ensures jQuery returns a factory function, not an auto-bound object
|
||||
const jqueryFactory = require('jquery');
|
||||
|
||||
/**
|
||||
* SSR Environment - Isolated rendering context
|
||||
*/
|
||||
class SSREnvironment {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
baseUrl: options.baseUrl || 'http://localhost',
|
||||
timeout: options.timeout || 30000
|
||||
};
|
||||
|
||||
this.dom = null;
|
||||
this.window = null;
|
||||
this.$ = null;
|
||||
this.storage = null;
|
||||
this.initialized = false;
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the jsdom environment with jQuery and all required globals
|
||||
*/
|
||||
init() {
|
||||
// Create jsdom instance
|
||||
this.dom = new JSDOM(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>SSR</title></head>
|
||||
<body></body>
|
||||
</html>
|
||||
`, {
|
||||
url: this.options.baseUrl,
|
||||
pretendToBeVisual: true,
|
||||
runScripts: 'outside-only'
|
||||
});
|
||||
|
||||
this.window = this.dom.window;
|
||||
|
||||
// Create fake storage
|
||||
this.storage = createStoragePair();
|
||||
|
||||
// Set up global references that bundles expect
|
||||
global.window = this.window;
|
||||
global.document = this.window.document;
|
||||
global.HTMLElement = this.window.HTMLElement;
|
||||
global.Element = this.window.Element;
|
||||
global.Node = this.window.Node;
|
||||
global.NodeList = this.window.NodeList;
|
||||
global.DOMParser = this.window.DOMParser;
|
||||
global.Text = this.window.Text;
|
||||
global.DocumentFragment = this.window.DocumentFragment;
|
||||
global.Event = this.window.Event;
|
||||
global.CustomEvent = this.window.CustomEvent;
|
||||
global.MutationObserver = this.window.MutationObserver;
|
||||
|
||||
// Window-like globals
|
||||
global.self = this.window;
|
||||
global.top = this.window;
|
||||
global.parent = this.window;
|
||||
global.frames = this.window;
|
||||
global.location = this.window.location;
|
||||
global.history = this.window.history;
|
||||
global.screen = this.window.screen;
|
||||
// NOTE: Don't set global.performance - causes issues between jsdom instances
|
||||
// The window.performance is available for code that accesses it via window
|
||||
global.requestAnimationFrame = (cb) => setTimeout(cb, 16);
|
||||
global.cancelAnimationFrame = (id) => clearTimeout(id);
|
||||
global.getComputedStyle = this.window.getComputedStyle.bind(this.window);
|
||||
|
||||
// Install fake storage
|
||||
global.localStorage = this.storage.localStorage;
|
||||
global.sessionStorage = this.storage.sessionStorage;
|
||||
this.window.localStorage = this.storage.localStorage;
|
||||
this.window.sessionStorage = this.storage.sessionStorage;
|
||||
|
||||
// Mark as SSR environment
|
||||
global.__SSR__ = true;
|
||||
global.__JQHTML_SSR_MODE__ = true;
|
||||
this.window.__SSR__ = true;
|
||||
this.window.__JQHTML_SSR_MODE__ = true;
|
||||
|
||||
// Install HTTP interception (fetch + XHR URL rewriting)
|
||||
installHttpIntercept(this.window, this.options.baseUrl);
|
||||
|
||||
// Create jQuery bound to jsdom window
|
||||
this.$ = jqueryFactory(this.window);
|
||||
|
||||
// Make jQuery available globally
|
||||
global.$ = this.$;
|
||||
global.jQuery = this.$;
|
||||
this.window.$ = this.$;
|
||||
this.window.jQuery = this.$;
|
||||
|
||||
// Stub console_debug if bundles use it
|
||||
this.window.console_debug = function() {};
|
||||
global.console_debug = function() {};
|
||||
|
||||
// rsxapp config object (some bundles expect this)
|
||||
this.window.rsxapp = this.window.rsxapp || {};
|
||||
global.rsxapp = this.window.rsxapp;
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute JavaScript code in the environment
|
||||
* @param {string} code - JavaScript code to execute
|
||||
* @param {string} filename - Filename for error reporting
|
||||
*/
|
||||
execute(code, filename = 'bundle.js') {
|
||||
if (!this.initialized) {
|
||||
throw new Error('SSR environment not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
// Remove sourcemap comments to avoid errors
|
||||
const cleanCode = code.replace(/\/\/# sourceMappingURL=.*/g, '');
|
||||
|
||||
// Execute in Node's global context
|
||||
vm.runInThisContext(cleanCode, {
|
||||
filename,
|
||||
displayErrors: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a component to HTML
|
||||
* @param {string} componentName - Component name to render
|
||||
* @param {object} args - Arguments to pass to component
|
||||
* @returns {Promise<{ html: string, cache: object }>}
|
||||
*/
|
||||
async render(componentName, args = {}) {
|
||||
if (!this.initialized) {
|
||||
throw new Error('SSR environment not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
const $ = global.$;
|
||||
|
||||
// Create container
|
||||
const $container = $('<div>');
|
||||
|
||||
// Create component
|
||||
$container.component(componentName, args);
|
||||
|
||||
const component = $container.component();
|
||||
if (!component) {
|
||||
throw new Error(`Failed to create component: ${componentName}`);
|
||||
}
|
||||
|
||||
// Wait for ready with timeout
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`Component render exceeded ${this.options.timeout}ms timeout`));
|
||||
}, this.options.timeout);
|
||||
});
|
||||
|
||||
await Promise.race([
|
||||
component.ready(),
|
||||
timeoutPromise
|
||||
]);
|
||||
|
||||
// Get rendered HTML
|
||||
const html = component.$.prop('outerHTML');
|
||||
|
||||
// Export cache state
|
||||
const cache = this.storage.exportAll();
|
||||
|
||||
return { html, cache };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if jqhtml runtime is available
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isJqhtmlReady() {
|
||||
return !!(global.window && global.window.jqhtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of registered templates
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getRegisteredTemplates() {
|
||||
if (global.window.jqhtml) {
|
||||
// Real jqhtml API
|
||||
if (typeof global.window.jqhtml.get_registered_templates === 'function') {
|
||||
return global.window.jqhtml.get_registered_templates();
|
||||
}
|
||||
// Mock bundle API (for testing)
|
||||
if (global.window.jqhtml._templates) {
|
||||
return Array.from(global.window.jqhtml._templates.keys());
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the environment and clean up globals
|
||||
*
|
||||
* NOTE: We set globals to undefined instead of deleting them,
|
||||
* because jsdom expects certain globals to exist (even if undefined)
|
||||
* when creating new windows.
|
||||
*/
|
||||
destroy() {
|
||||
// Close jsdom window first
|
||||
if (this.window) {
|
||||
this.window.close();
|
||||
}
|
||||
|
||||
// Clean up global references by setting to undefined
|
||||
// This is safer than delete because some libraries check for existence
|
||||
global.window = undefined;
|
||||
global.document = undefined;
|
||||
global.HTMLElement = undefined;
|
||||
global.Element = undefined;
|
||||
global.Node = undefined;
|
||||
global.NodeList = undefined;
|
||||
global.DOMParser = undefined;
|
||||
global.Text = undefined;
|
||||
global.DocumentFragment = undefined;
|
||||
global.Event = undefined;
|
||||
global.CustomEvent = undefined;
|
||||
global.MutationObserver = undefined;
|
||||
global.self = undefined;
|
||||
global.top = undefined;
|
||||
global.parent = undefined;
|
||||
global.frames = undefined;
|
||||
global.location = undefined;
|
||||
global.history = undefined;
|
||||
global.screen = undefined;
|
||||
// Don't touch performance - jsdom needs it
|
||||
global.requestAnimationFrame = undefined;
|
||||
global.cancelAnimationFrame = undefined;
|
||||
global.getComputedStyle = undefined;
|
||||
global.localStorage = undefined;
|
||||
global.sessionStorage = undefined;
|
||||
global.$ = undefined;
|
||||
global.jQuery = undefined;
|
||||
global.__SSR__ = undefined;
|
||||
global.__JQHTML_SSR_MODE__ = undefined;
|
||||
global.console_debug = undefined;
|
||||
global.rsxapp = undefined;
|
||||
global.fetch = undefined;
|
||||
global.XMLHttpRequest = undefined;
|
||||
|
||||
this.dom = null;
|
||||
this.window = null;
|
||||
this.$ = null;
|
||||
this.storage = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SSREnvironment };
|
||||
Reference in New Issue
Block a user