diff --git a/changelog.md b/changelog.md index c86f56d..84f8e21 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2026-01-01 - 3.27.0 - feat(services) +introduce DeesServiceLibLoader to lazy-load heavy client libraries from CDN and update components to use it + +- Add DeesServiceLibLoader singleton (ts_web/services/DeesServiceLibLoader.ts) to lazily load and cache libraries via jsDelivr ESM: xterm, xterm-addon-fit, highlight.js, ApexCharts, and Tiptap. +- Inject xterm CSS dynamically to avoid shipping xterm styles in the initial bundle. +- Expose helper methods preloadAll() and isLoaded(), and typed bundle interfaces (IXtermBundle, IXtermFitAddonBundle, ITiptapBundle). +- Update components to use runtime-loaded modules: dees-chart-area, dees-dataview-codebox, dees-input-richtext, wysiwyg code block, dees-workspace-terminal, terminal-tab-manager, dees-workspace-terminal-preview. +- TerminalTabManager now requires setXtermModules(...) before creating tabs and will throw if not initialized; workspace terminal now initializes and passes the loaded modules. +- Replace direct runtime imports of heavy libs with typed imports and runtime-loaded bundles to reduce initial bundle size and improve load performance. + ## 2026-01-01 - 3.26.1 - fix(dees-actionbar) animate actionbar hide using grid-template-rows and wait for animation before clearing state diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 2901c82..feb5a7f 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.26.1', + version: '3.27.0', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-chart/dees-chart-area/component.ts b/ts_web/elements/00group-chart/dees-chart-area/component.ts index 89d7efd..c0bf479 100644 --- a/ts_web/elements/00group-chart/dees-chart-area/component.ts +++ b/ts_web/elements/00group-chart/dees-chart-area/component.ts @@ -11,7 +11,8 @@ import { demoFunc } from './demo.js'; import { chartAreaStyles } from './styles.js'; import { renderChartArea } from './template.js'; -import ApexCharts from 'apexcharts'; +import type ApexCharts from 'apexcharts'; +import { DeesServiceLibLoader } from '../../../services/index.js'; declare global { interface HTMLElementTagNameMap { @@ -150,7 +151,10 @@ export class DeesChartArea extends DeesElement { public async firstUpdated() { await this.domtoolsPromise; - + + // Load ApexCharts from CDN + const ApexChartsLib = await DeesServiceLibLoader.getInstance().loadApexCharts(); + // Wait for next animation frame to ensure layout is complete await new Promise(resolve => requestAnimationFrame(resolve)); @@ -353,7 +357,7 @@ export class DeesChartArea extends DeesElement { }; try { - this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options); + this.chart = new ApexChartsLib(this.shadowRoot.querySelector('.chartContainer'), options); await this.chart.render(); // Give the chart a moment to fully initialize before resizing diff --git a/ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts b/ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts index e379d28..2644049 100644 --- a/ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts +++ b/ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts @@ -10,12 +10,13 @@ import { } from '@design.estate/dees-element'; import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js'; -import hlight from 'highlight.js'; +import type { HLJSApi } from 'highlight.js'; import * as smartstring from '@push.rocks/smartstring'; import * as domtools from '@design.estate/dees-domtools'; import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js'; +import { DeesServiceLibLoader } from '../../../services/index.js'; declare global { interface HTMLElementTagNameMap { @@ -229,6 +230,7 @@ export class DeesDataviewCodebox extends DeesElement { } private codeToDisplayStore = ''; + private highlightJs: HLJSApi | null = null; public async updated(_changedProperties) { super.updated(_changedProperties); @@ -250,11 +252,17 @@ export class DeesDataviewCodebox extends DeesElement { this.codeToDisplay = this.codeToDisplayStore; } await domtools.plugins.smartdelay.delayFor(0); + + // Load highlight.js from CDN if not already loaded + if (!this.highlightJs) { + this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs(); + } + const localCodeNode = this.shadowRoot.querySelector('code'); - const html = hlight.highlight(this.codeToDisplayStore, { + const highlightedHtml = this.highlightJs.highlight(this.codeToDisplayStore, { language: this.progLang, ignoreIllegals: true, }); - localCodeNode.innerHTML = html.value; + localCodeNode.innerHTML = highlightedHtml.value; } } diff --git a/ts_web/elements/00group-input/dees-input-richtext/component.ts b/ts_web/elements/00group-input/dees-input-richtext/component.ts index a72a8e1..214bb1a 100644 --- a/ts_web/elements/00group-input/dees-input-richtext/component.ts +++ b/ts_web/elements/00group-input/dees-input-richtext/component.ts @@ -14,12 +14,8 @@ import { query, } from '@design.estate/dees-element'; -import { Editor } from '@tiptap/core'; -import StarterKit from '@tiptap/starter-kit'; -import Underline from '@tiptap/extension-underline'; -import TextAlign from '@tiptap/extension-text-align'; -import Link from '@tiptap/extension-link'; -import Typography from '@tiptap/extension-typography'; +import type { Editor } from '@tiptap/core'; +import { DeesServiceLibLoader, type ITiptapBundle } from '../../../services/index.js'; declare global { interface HTMLElementTagNameMap { @@ -63,6 +59,7 @@ export class DeesInputRichtext extends DeesInputBase { private editorElement: HTMLElement; private linkInputElement: HTMLInputElement; + private tiptapBundle: ITiptapBundle | null = null; public editor: Editor; @@ -233,13 +230,19 @@ export class DeesInputRichtext extends DeesInputBase { public async firstUpdated() { await this.updateComplete; + + // Load Tiptap from CDN + this.tiptapBundle = await DeesServiceLibLoader.getInstance().loadTiptap(); + this.editorElement = this.shadowRoot.querySelector('.editor-content'); this.linkInputElement = this.shadowRoot.querySelector('.link-input input'); this.initializeEditor(); } private initializeEditor(): void { - if (this.disabled) return; + if (this.disabled || !this.tiptapBundle) return; + + const { Editor, StarterKit, Underline, TextAlign, Link, Typography } = this.tiptapBundle; this.editor = new Editor({ element: this.editorElement, @@ -249,7 +252,7 @@ export class DeesInputRichtext extends DeesInputBase { levels: [1, 2, 3], }, }), - Underline, + Underline.configure({}), TextAlign.configure({ types: ['heading', 'paragraph'], }), @@ -259,7 +262,7 @@ export class DeesInputRichtext extends DeesInputBase { class: 'editor-link', }, }), - Typography, + Typography.configure({}), ], content: this.value || (this.placeholder ? `

${this.placeholder}

` : ''), onUpdate: ({ editor }) => { diff --git a/ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/code.block.ts b/ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/code.block.ts index 7f2e27d..84933b0 100644 --- a/ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/code.block.ts +++ b/ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/code.block.ts @@ -2,9 +2,10 @@ import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js'; import type { IBlock } from '../../wysiwyg.types.js'; import { cssManager } from '@design.estate/dees-element'; import { WysiwygSelection } from '../../wysiwyg.selection.js'; -import hlight from 'highlight.js'; +import type { HLJSApi } from 'highlight.js'; import { cssGeistFontFamily, cssMonoFontFamily } from '../../../../00fonts.js'; import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js'; +import { DeesServiceLibLoader } from '../../../../../services/index.js'; /** * CodeBlockHandler with improved architecture @@ -18,8 +19,9 @@ import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js'; */ export class CodeBlockHandler extends BaseBlockHandler { type = 'code'; - + private highlightTimer: any = null; + private highlightJs: HLJSApi | null = null; render(block: IBlock, isSelected: boolean): string { const language = block.metadata?.language || 'typescript'; @@ -306,28 +308,33 @@ export class CodeBlockHandler extends BaseBlockHandler { return linesBeforeCursor.length - 1; // 0-indexed } - private applyHighlighting(element: HTMLElement, block: IBlock): void { + private async applyHighlighting(element: HTMLElement, block: IBlock): Promise { const editor = element.querySelector('.code-editor') as HTMLElement; if (!editor) return; - + + // Load highlight.js from CDN if not already loaded + if (!this.highlightJs) { + this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs(); + } + // Store cursor position const cursorPos = this.getCursorPosition(element); - + // Get plain text content const content = editor.textContent || ''; const language = block.metadata?.language || 'typescript'; - + // Apply highlighting try { - const result = hlight.highlight(content, { + const result = this.highlightJs.highlight(content, { language: language, - ignoreIllegals: true + ignoreIllegals: true, }); - + // Only update if we have valid highlighted content if (result.value) { editor.innerHTML = result.value; - + // Restore cursor position if editor is focused if (document.activeElement === editor && cursorPos !== null) { requestAnimationFrame(() => { diff --git a/ts_web/elements/00group-workspace/dees-workspace-terminal-preview/dees-workspace-terminal-preview.ts b/ts_web/elements/00group-workspace/dees-workspace-terminal-preview/dees-workspace-terminal-preview.ts index d6cf62d..403ab6d 100644 --- a/ts_web/elements/00group-workspace/dees-workspace-terminal-preview/dees-workspace-terminal-preview.ts +++ b/ts_web/elements/00group-workspace/dees-workspace-terminal-preview/dees-workspace-terminal-preview.ts @@ -7,9 +7,10 @@ import { css, cssManager, } from '@design.estate/dees-element'; -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import type { Terminal } from 'xterm'; +import type { FitAddon } from 'xterm-addon-fit'; import { themeDefaultStyles } from '../../00theme.js'; +import { DeesServiceLibLoader } from '../../../services/index.js'; declare global { interface HTMLElementTagNameMap { @@ -305,8 +306,15 @@ export class DeesWorkspaceTerminalPreview extends DeesElement { const domtoolsInstance = await this.domtoolsPromise; const isBright = domtoolsInstance.themeManager.goBrightBoolean; - // Create xterm terminal in read-only mode - this.terminal = new Terminal({ + // Load xterm from CDN + const libLoader = DeesServiceLibLoader.getInstance(); + const [xtermBundle, fitAddonBundle] = await Promise.all([ + libLoader.loadXterm(), + libLoader.loadXtermFitAddon(), + ]); + + // Create xterm terminal in read-only mode using CDN-loaded module + this.terminal = new xtermBundle.Terminal({ convertEol: true, cursorBlink: false, disableStdin: true, @@ -323,7 +331,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement { } }); - this.fitAddon = new FitAddon(); + this.fitAddon = new fitAddonBundle.FitAddon(); this.terminal.loadAddon(this.fitAddon); this.terminal.open(container); this.fitAddon.fit(); diff --git a/ts_web/elements/00group-workspace/dees-workspace-terminal/dees-workspace-terminal.ts b/ts_web/elements/00group-workspace/dees-workspace-terminal/dees-workspace-terminal.ts index 5637997..1b8adfc 100644 --- a/ts_web/elements/00group-workspace/dees-workspace-terminal/dees-workspace-terminal.ts +++ b/ts_web/elements/00group-workspace/dees-workspace-terminal/dees-workspace-terminal.ts @@ -10,8 +10,7 @@ import { } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import type { Terminal } from 'xterm'; import { themeDefaultStyles } from '../../00theme.js'; import type { IExecutionEnvironment } from '../../00group-runtime/index.js'; import { WebContainerEnvironment } from '../../00group-runtime/index.js'; @@ -24,6 +23,7 @@ import type { ICreateTerminalTabOptions, TTerminalTabType, } from './interfaces.js'; +import { DeesServiceLibLoader } from '../../../services/index.js'; declare global { interface HTMLElementTagNameMap { @@ -495,6 +495,16 @@ export class DeesWorkspaceTerminal extends DeesElement { } ); + // Load xterm from CDN + const libLoader = DeesServiceLibLoader.getInstance(); + const [xtermBundle, fitAddonBundle] = await Promise.all([ + libLoader.loadXterm(), + libLoader.loadXtermFitAddon(), + ]); + + // Initialize tab manager with loaded modules + this.tabManager.setXtermModules(xtermBundle, fitAddonBundle); + // Create default shell tab await this.createShellTab(); } diff --git a/ts_web/elements/00group-workspace/dees-workspace-terminal/terminal-tab-manager.ts b/ts_web/elements/00group-workspace/dees-workspace-terminal/terminal-tab-manager.ts index 3bc77be..ac7f2dd 100644 --- a/ts_web/elements/00group-workspace/dees-workspace-terminal/terminal-tab-manager.ts +++ b/ts_web/elements/00group-workspace/dees-workspace-terminal/terminal-tab-manager.ts @@ -1,6 +1,7 @@ -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import type { Terminal, ITerminalOptions } from 'xterm'; +import type { FitAddon } from 'xterm-addon-fit'; import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from './interfaces.js'; +import type { IXtermBundle, IXtermFitAddonBundle } from '../../../services/index.js'; /** * Manages terminal tabs lifecycle and state @@ -8,6 +9,17 @@ import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from ' export class TerminalTabManager { private tabs: Map = new Map(); private tabCounter: number = 0; + private xtermBundle: IXtermBundle | null = null; + private xtermFitAddonBundle: IXtermFitAddonBundle | null = null; + + /** + * Initialize the manager with loaded xterm modules. + * Must be called before creating tabs. + */ + public setXtermModules(xtermBundle: IXtermBundle, fitAddonBundle: IXtermFitAddonBundle): void { + this.xtermBundle = xtermBundle; + this.xtermFitAddonBundle = fitAddonBundle; + } /** * Generate unique tab ID @@ -96,11 +108,15 @@ export class TerminalTabManager { * Create a new tab instance */ createTab(options: ICreateTerminalTabOptions, isBright: boolean): ITerminalTab { + if (!this.xtermBundle || !this.xtermFitAddonBundle) { + throw new Error('TerminalTabManager: xterm modules not initialized. Call setXtermModules() first.'); + } + const id = this.generateTabId(); const type = options.type; - // Create xterm.js Terminal instance - const terminal = new Terminal({ + // Create xterm.js Terminal instance using CDN-loaded module + const terminal = new this.xtermBundle.Terminal({ convertEol: true, cursorBlink: true, theme: this.getTerminalTheme(isBright), @@ -109,8 +125,8 @@ export class TerminalTabManager { lineHeight: 1.2, }); - // Create FitAddon - const fitAddon = new FitAddon(); + // Create FitAddon using CDN-loaded module + const fitAddon = new this.xtermFitAddonBundle.FitAddon(); terminal.loadAddon(fitAddon); const tab: ITerminalTab = { diff --git a/ts_web/services/DeesServiceLibLoader.ts b/ts_web/services/DeesServiceLibLoader.ts new file mode 100644 index 0000000..f16e498 --- /dev/null +++ b/ts_web/services/DeesServiceLibLoader.ts @@ -0,0 +1,285 @@ +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; + } + } +} diff --git a/ts_web/services/index.ts b/ts_web/services/index.ts new file mode 100644 index 0000000..63ea0ed --- /dev/null +++ b/ts_web/services/index.ts @@ -0,0 +1,3 @@ +export { DeesServiceLibLoader } from './DeesServiceLibLoader.js'; +export type { IXtermBundle, IXtermFitAddonBundle, ITiptapBundle } from './DeesServiceLibLoader.js'; +export { CDN_BASE, CDN_VERSIONS } from './versions.js'; diff --git a/ts_web/services/versions.ts b/ts_web/services/versions.ts new file mode 100644 index 0000000..e92ecf9 --- /dev/null +++ b/ts_web/services/versions.ts @@ -0,0 +1,17 @@ +/** + * CDN versions for lazy-loaded libraries. + * Keep these in sync with package.json for type compatibility. + */ +export const CDN_VERSIONS = { + xterm: '5.3.0', + xtermAddonFit: '0.8.0', + highlightJs: '11.11.1', + apexcharts: '5.3.6', + tiptap: '2.23.0', + fontawesome: '7.1.0', +} as const; + +/** + * Base CDN URL for jsdelivr ESM imports + */ +export const CDN_BASE = 'https://cdn.jsdelivr.net/npm';