import { CDN_BASE, CDN_VERSIONS } from './versions.js'; // Type imports (no runtime overhead) import type { Terminal, ITerminalOptions } from 'xterm'; import type { FitAddon } from 'xterm-addon-fit'; import type { HLJSApi } from 'highlight.js'; import type ApexChartsType from 'apexcharts'; import type { Editor, EditorOptions } from '@tiptap/core'; import type { StarterKitOptions } from '@tiptap/starter-kit'; import type { UnderlineOptions } from '@tiptap/extension-underline'; import type { TextAlignOptions } from '@tiptap/extension-text-align'; import type { LinkOptions } from '@tiptap/extension-link'; /** * Bundle type for xterm and its addons */ export interface IXtermBundle { Terminal: typeof Terminal; } /** * Bundle type for xterm-addon-fit */ export interface IXtermFitAddonBundle { FitAddon: typeof FitAddon; } /** * Bundle type for Tiptap editor and extensions */ export interface ITiptapBundle { Editor: typeof Editor; StarterKit: { configure: (options?: Partial) => any }; Underline: { configure: (options?: Partial) => any }; TextAlign: { configure: (options?: Partial) => any }; Link: { configure: (options?: Partial) => any }; Typography: { configure: (options?: any) => any }; } /** * Singleton service for lazy-loading heavy libraries from CDN. * * This reduces initial bundle size by loading libraries only when needed. * Libraries are cached after first load to avoid duplicate fetches. * * @example * ```typescript * const libLoader = DeesServiceLibLoader.getInstance(); * const xterm = await libLoader.loadXterm(); * const terminal = new xterm.Terminal({ ... }); * ``` */ export class DeesServiceLibLoader { private static instance: DeesServiceLibLoader; // Cached library references private xtermLib: IXtermBundle | null = null; private xtermFitAddonLib: IXtermFitAddonBundle | null = null; private highlightJsLib: HLJSApi | null = null; private apexChartsLib: typeof ApexChartsType | null = null; private tiptapLib: ITiptapBundle | null = null; // Loading promises to prevent duplicate concurrent loads private xtermLoadingPromise: Promise | null = null; private xtermFitAddonLoadingPromise: Promise | null = null; private highlightJsLoadingPromise: Promise | null = null; private apexChartsLoadingPromise: Promise | null = null; private tiptapLoadingPromise: Promise | null = null; private constructor() {} /** * Get the singleton instance of DeesServiceLibLoader */ public static getInstance(): DeesServiceLibLoader { if (!DeesServiceLibLoader.instance) { DeesServiceLibLoader.instance = new DeesServiceLibLoader(); } return DeesServiceLibLoader.instance; } /** * Load xterm terminal emulator from CDN * @returns Promise resolving to xterm module with Terminal class */ public async loadXterm(): Promise { if (this.xtermLib) { return this.xtermLib; } if (this.xtermLoadingPromise) { return this.xtermLoadingPromise; } this.xtermLoadingPromise = (async () => { const url = `${CDN_BASE}/xterm@${CDN_VERSIONS.xterm}/+esm`; const module = await import(/* @vite-ignore */ url); // Also load and inject xterm CSS await this.injectXtermStyles(); this.xtermLib = { Terminal: module.Terminal, }; return this.xtermLib; })(); return this.xtermLoadingPromise; } /** * Load xterm-addon-fit from CDN * @returns Promise resolving to FitAddon class */ public async loadXtermFitAddon(): Promise { if (this.xtermFitAddonLib) { return this.xtermFitAddonLib; } if (this.xtermFitAddonLoadingPromise) { return this.xtermFitAddonLoadingPromise; } this.xtermFitAddonLoadingPromise = (async () => { const url = `${CDN_BASE}/xterm-addon-fit@${CDN_VERSIONS.xtermAddonFit}/+esm`; const module = await import(/* @vite-ignore */ url); this.xtermFitAddonLib = { FitAddon: module.FitAddon, }; return this.xtermFitAddonLib; })(); return this.xtermFitAddonLoadingPromise; } /** * Inject xterm CSS styles into the document head */ private async injectXtermStyles(): Promise { const styleId = 'xterm-cdn-styles'; if (document.getElementById(styleId)) { return; // Already injected } const cssUrl = `${CDN_BASE}/xterm@${CDN_VERSIONS.xterm}/css/xterm.css`; const response = await fetch(cssUrl); const cssText = await response.text(); const style = document.createElement('style'); style.id = styleId; style.textContent = cssText; document.head.appendChild(style); } /** * Load highlight.js syntax highlighter from CDN * @returns Promise resolving to highlight.js API */ public async loadHighlightJs(): Promise { if (this.highlightJsLib) { return this.highlightJsLib; } if (this.highlightJsLoadingPromise) { return this.highlightJsLoadingPromise; } this.highlightJsLoadingPromise = (async () => { const url = `${CDN_BASE}/highlight.js@${CDN_VERSIONS.highlightJs}/+esm`; const module = await import(/* @vite-ignore */ url); this.highlightJsLib = module.default; return this.highlightJsLib; })(); return this.highlightJsLoadingPromise; } /** * Load ApexCharts charting library from CDN * @returns Promise resolving to ApexCharts constructor */ public async loadApexCharts(): Promise { if (this.apexChartsLib) { return this.apexChartsLib; } if (this.apexChartsLoadingPromise) { return this.apexChartsLoadingPromise; } this.apexChartsLoadingPromise = (async () => { const url = `${CDN_BASE}/apexcharts@${CDN_VERSIONS.apexcharts}/+esm`; const module = await import(/* @vite-ignore */ url); this.apexChartsLib = module.default; return this.apexChartsLib; })(); return this.apexChartsLoadingPromise; } /** * Load Tiptap rich text editor and extensions from CDN * @returns Promise resolving to Tiptap bundle with Editor and extensions */ public async loadTiptap(): Promise { if (this.tiptapLib) { return this.tiptapLib; } if (this.tiptapLoadingPromise) { return this.tiptapLoadingPromise; } this.tiptapLoadingPromise = (async () => { const version = CDN_VERSIONS.tiptap; // Load all Tiptap modules in parallel const [ coreModule, starterKitModule, underlineModule, textAlignModule, linkModule, typographyModule, ] = await Promise.all([ import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/core@${version}/+esm`), import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/starter-kit@${version}/+esm`), import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-underline@${version}/+esm`), import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-text-align@${version}/+esm`), import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-link@${version}/+esm`), import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-typography@${version}/+esm`), ]); this.tiptapLib = { Editor: coreModule.Editor, StarterKit: starterKitModule.default || starterKitModule.StarterKit, Underline: underlineModule.default || underlineModule.Underline, TextAlign: textAlignModule.default || textAlignModule.TextAlign, Link: linkModule.default || linkModule.Link, Typography: typographyModule.default || typographyModule.Typography, }; return this.tiptapLib; })(); return this.tiptapLoadingPromise; } /** * Preload multiple libraries in parallel * Useful for warming the cache before components are rendered */ public async preloadAll(): Promise { await Promise.all([ this.loadXterm(), this.loadXtermFitAddon(), this.loadHighlightJs(), this.loadApexCharts(), this.loadTiptap(), ]); } /** * Check if a specific library is already loaded */ public isLoaded(library: 'xterm' | 'xtermFitAddon' | 'highlightJs' | 'apexCharts' | 'tiptap'): boolean { switch (library) { case 'xterm': return this.xtermLib !== null; case 'xtermFitAddon': return this.xtermFitAddonLib !== null; case 'highlightJs': return this.highlightJsLib !== null; case 'apexCharts': return this.apexChartsLib !== null; case 'tiptap': return this.tiptapLib !== null; default: return false; } } }