import { EvtRt } from '../EvtRt.js'; import { MountConfig, MountContext } from '../types/mount-observer/types.js'; /** * Handler for exposing module exports from script elements via element.export. * * Solves two key problems: * * 1. ES Module Export Access: * - Use nomodule attribute to prevent browser from loading the module separately * - Handler imports it and exposes exports via element.export * - Avoids double-loading the module in memory * - Example: * - Access: document.getElementById('cfg').export.myValue * * 2. JSON/Data Import: * - Use type attribute for JSON and other data formats * - Browser ignores non-standard script types, so no double-loading issue * - Handler imports with appropriate assertion and exposes via element.export * - Example: * - Access: document.getElementById('data').export.default * * Supported type values: * - type="json" - JSON data * - type="application/json" - JSON with full MIME type * - type="application/ld+json" - JSON-LD linked data * - Any type containing "json" triggers JSON import assertion */ export class ScriptExportHandler extends EvtRt { // Match script elements with src attribute static matching = 'script[src]'; static whereInstanceOf = HTMLScriptElement; async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): Promise { this.abort(); // Clean up event listeners (one-time operation) const scriptElement = mountedElement as HTMLScriptElement; // Optimization 3: Skip if already processed (export property exists) if ((scriptElement as any).export !== undefined) { return; } // Skip if this is a module script (browser handles these) const typeAttr = scriptElement.getAttribute('type'); if (typeAttr === 'module') { return; } // Only process if: // 1. Has nomodule attribute (for ES modules), OR // 2. Has type attribute containing "json" (for JSON data) const hasNoModule = scriptElement.hasAttribute('nomodule'); const isJsonType = typeAttr && typeAttr.toLowerCase().includes('json'); if (!hasNoModule && !isJsonType) { return; } // Read src attribute const srcAttr = scriptElement.getAttribute('src'); if (!srcAttr) { throw new Error('Script element must have a src attribute'); } // Resolve the src relative to the document's base URL const resolvedUrl = new URL(srcAttr, document.baseURI).href; // Determine import assertion type const importType = isJsonType ? 'json' : null; // Perform import let module; try { if (importType) { module = await import(resolvedUrl, { with: { type: importType } } as any); } else { module = await import(resolvedUrl); } } catch (error) { throw new Error(`Failed to import module from '${srcAttr}': ${error instanceof Error ? error.message : String(error)}`); } // Store result on element (scriptElement as any).export = module; // Dispatch resolved event const { ResolvedEvent } = await import('../Events.js'); scriptElement.dispatchEvent(new ResolvedEvent(module)); } } // Register built-in handler import { MountObserver } from '../MountObserver.js'; export const scriptExport = 'builtIns.scriptExport'; MountObserver.define(scriptExport, ScriptExportHandler);