286 lines
8.6 KiB
TypeScript
286 lines
8.6 KiB
TypeScript
|
|
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<StarterKitOptions>) => any };
|
||
|
|
Underline: { configure: (options?: Partial<UnderlineOptions>) => any };
|
||
|
|
TextAlign: { configure: (options?: Partial<TextAlignOptions>) => any };
|
||
|
|
Link: { configure: (options?: Partial<LinkOptions>) => 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<IXtermBundle> | null = null;
|
||
|
|
private xtermFitAddonLoadingPromise: Promise<IXtermFitAddonBundle> | null = null;
|
||
|
|
private highlightJsLoadingPromise: Promise<HLJSApi> | null = null;
|
||
|
|
private apexChartsLoadingPromise: Promise<typeof ApexChartsType> | null = null;
|
||
|
|
private tiptapLoadingPromise: Promise<ITiptapBundle> | 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<IXtermBundle> {
|
||
|
|
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<IXtermFitAddonBundle> {
|
||
|
|
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<void> {
|
||
|
|
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<HLJSApi> {
|
||
|
|
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<typeof ApexChartsType> {
|
||
|
|
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<ITiptapBundle> {
|
||
|
|
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<void> {
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|