From 5ee876041858136fab972eaff1aaefdc2c2f6d3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 00:31:02 +0000 Subject: [PATCH 1/7] Initial plan From 3c2b91143960a202e2410bff557e4eab7bd5c255 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 00:40:40 +0000 Subject: [PATCH 2/7] Set up code splitting for ESM build Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- bin/esbuild.mjs | 21 ++++++++++++++++----- demo/index.html | 2 +- demo/test.html | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index 5e0a370cad..c40b85fe60 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -26,7 +26,10 @@ const commonOptions = { target: 'es2021', sourcemap: true, treeShaking: true, + splitting: true, + chunkNames: 'chunks/[name]-[hash]', logLevel: 'warning', + outExtension: { '.js': '.mjs' }, }; /** @type {esbuild.BuildOptions} */ @@ -103,7 +106,8 @@ if (config.addon) { bundleConfig = { ...bundleConfig, entryPoints: [`addons/addon-${config.addon}/src/${getAddonEntryPoint(config.addon)}.ts`], - outfile: `addons/addon-${config.addon}/lib/addon-${config.addon}.mjs`, + outdir: `addons/addon-${config.addon}/lib`, + entryNames: `addon-${config.addon}`, }; outConfig = { ...outConfig, @@ -128,11 +132,12 @@ if (config.addon) { bundleConfig = { ...bundleConfig, entryPoints: [`demo/client/client.ts`], - outfile: 'demo/dist/client-bundle.js', + outdir: 'demo/dist', + entryNames: 'client-bundle', external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'], alias: { // Library ESM imports - "@xterm/xterm": ".", + "@xterm/xterm": "./lib/xterm.mjs", "@xterm/addon-attach": "./addons/addon-attach/lib/addon-attach.mjs", "@xterm/addon-clipboard": "./addons/addon-clipboard/lib/addon-clipboard.mjs", "@xterm/addon-fit": "./addons/addon-fit/lib/addon-fit.mjs", @@ -164,6 +169,10 @@ if (config.addon) { format: 'cjs', platform: 'node', external: ['node-pty'], + // Splitting is not compatible with cjs format + splitting: false, + // Override the .mjs extension from commonOptions + outExtension: {}, } skipOut = true; skipOutTest = true; @@ -171,7 +180,8 @@ if (config.addon) { bundleConfig = { ...bundleConfig, entryPoints: [`src/headless/public/Terminal.ts`], - outfile: `headless/lib-headless/xterm-headless.mjs` + outdir: `headless/lib-headless`, + entryNames: 'xterm-headless', }; outConfig = { ...outConfig, @@ -183,7 +193,8 @@ if (config.addon) { bundleConfig = { ...bundleConfig, entryPoints: [`src/browser/public/Terminal.ts`], - outfile: `lib/xterm.mjs` + outdir: `lib`, + entryNames: 'xterm', }; outConfig = { ...outConfig, diff --git a/demo/index.html b/demo/index.html index 9c8eec666a..56a6c2a497 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,6 +23,6 @@ - + diff --git a/demo/test.html b/demo/test.html index d0e1e1f18f..3532aed08c 100644 --- a/demo/test.html +++ b/demo/test.html @@ -18,6 +18,6 @@
- + From 8c71bfd65fcdd9d93fee307483f8672eaf3ce803 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 00:41:57 +0000 Subject: [PATCH 3/7] Revert @xterm/xterm alias to use package.json resolution Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- bin/esbuild.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index c40b85fe60..b84cfd1cdc 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -137,7 +137,7 @@ if (config.addon) { external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'], alias: { // Library ESM imports - "@xterm/xterm": "./lib/xterm.mjs", + "@xterm/xterm": ".", "@xterm/addon-attach": "./addons/addon-attach/lib/addon-attach.mjs", "@xterm/addon-clipboard": "./addons/addon-clipboard/lib/addon-clipboard.mjs", "@xterm/addon-fit": "./addons/addon-fit/lib/addon-fit.mjs", From 7a7abddc45e1c75778b0471238b1232dc5d7ae8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:12:01 +0000 Subject: [PATCH 4/7] Use dynamic imports for custom glyphs in WebGL addon Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- addons/addon-webgl/src/TextureAtlas.ts | 6 +++- .../src/customGlyphs/CustomGlyphRasterizer.ts | 35 +++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/addons/addon-webgl/src/TextureAtlas.ts b/addons/addon-webgl/src/TextureAtlas.ts index 182dda59e5..8b180ac759 100644 --- a/addons/addon-webgl/src/TextureAtlas.ts +++ b/addons/addon-webgl/src/TextureAtlas.ts @@ -5,7 +5,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from './Constants'; -import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer'; +import { loadCustomGlyphDefinitions, tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer'; import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types'; import { NULL_COLOR, channels, color, rgba } from 'common/Color'; @@ -100,6 +100,10 @@ export class TextureAtlas implements ITextureAtlas { alpha: this._config.allowTransparency, willReadFrequently: true })); + // Start loading custom glyph definitions asynchronously if custom glyphs are enabled + if (this._config.customGlyphs !== false) { + loadCustomGlyphDefinitions(); + } } public dispose(): void { diff --git a/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts b/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts index 6302757fc1..9d32ac3e6f 100644 --- a/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts +++ b/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts @@ -4,8 +4,34 @@ */ import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { customGlyphDefinitions } from './CustomGlyphDefinitions'; -import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types'; +import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types'; + +/** + * Cached custom glyph definitions. This is loaded dynamically to support code splitting. + */ +let customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefinition | undefined } | undefined; + +/** + * Promise that resolves when custom glyph definitions are loaded. + */ +let loadingPromise: Promise | undefined; + +/** + * Begins loading the custom glyph definitions. This should be called as early as possible + * to ensure the definitions are ready when needed. + * @returns A promise that resolves when the definitions are loaded. + */ +export function loadCustomGlyphDefinitions(): Promise { + if (customGlyphDefinitions) { + return Promise.resolve(); + } + if (!loadingPromise) { + loadingPromise = import('./CustomGlyphDefinitions').then(module => { + customGlyphDefinitions = module.customGlyphDefinitions; + }); + } + return loadingPromise; +} /** * Try drawing a custom block element or box drawing character, returning whether it was @@ -24,6 +50,11 @@ export function tryDrawCustomGlyph( devicePixelRatio: number, backgroundColor?: string ): boolean { + // If definitions are not loaded yet, return false to fall back to font rendering + if (!customGlyphDefinitions) { + return false; + } + const unifiedCharDefinition = customGlyphDefinitions[c]; if (unifiedCharDefinition) { // Normalize to array for uniform handling From c2890d8a4da4b5ce1cc30133f4501ecd4f100262 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:14:01 +0000 Subject: [PATCH 5/7] Add error handling for dynamic import in loadCustomGlyphDefinitions Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts b/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts index 9d32ac3e6f..eb73117e51 100644 --- a/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts +++ b/addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts @@ -28,6 +28,10 @@ export function loadCustomGlyphDefinitions(): Promise { if (!loadingPromise) { loadingPromise = import('./CustomGlyphDefinitions').then(module => { customGlyphDefinitions = module.customGlyphDefinitions; + }).catch(err => { + // Reset the loading promise on failure to allow retry + loadingPromise = undefined; + throw err; }); } return loadingPromise; From a5422d1b11e16cfb6e0db6ab29709b7dd639a4b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:56:13 +0000 Subject: [PATCH 6/7] Add dynamic imports for AccessibilityManager and OverviewRulerRenderer Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- src/browser/CoreBrowserTerminal.ts | 51 ++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index c26e670515..8d9c4935d8 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -28,7 +28,7 @@ import { OscLinkProvider } from 'browser/OscLinkProvider'; import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types'; import { Viewport } from 'browser/Viewport'; import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer'; -import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer'; +import type { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { IRenderer } from 'browser/renderer/shared/Types'; @@ -53,12 +53,34 @@ import { toRgbString } from 'common/input/XParseColor'; import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; import { WindowsOptionsReportType } from '../common/InputHandler'; -import { AccessibilityManager } from './AccessibilityManager'; +import type { AccessibilityManager } from './AccessibilityManager'; import { Linkifier } from './Linkifier'; import { Emitter, Event } from 'vs/base/common/event'; import { addDisposableListener } from 'vs/base/browser/dom'; import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +/** + * Lazily loaded AccessibilityManager module. + */ +let accessibilityManagerModule: Promise | undefined; +function loadAccessibilityManager(): Promise { + if (!accessibilityManagerModule) { + accessibilityManagerModule = import('./AccessibilityManager'); + } + return accessibilityManagerModule; +} + +/** + * Lazily loaded OverviewRulerRenderer module. + */ +let overviewRulerRendererModule: Promise | undefined; +function loadOverviewRulerRenderer(): Promise { + if (!overviewRulerRendererModule) { + overviewRulerRendererModule = import('browser/decorations/OverviewRulerRenderer'); + } + return overviewRulerRendererModule; +} + export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { public textarea: HTMLTextAreaElement | undefined; public element: HTMLElement | undefined; @@ -277,7 +299,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { private _handleScreenReaderModeOptionChange(value: boolean): void { if (value) { if (!this._accessibilityManager.value && this._renderService) { - this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); + loadAccessibilityManager().then(module => { + // Check again after async load in case option changed + if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) { + this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this); + } + }); } } else { this._accessibilityManager.clear(); @@ -588,16 +615,28 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); + loadAccessibilityManager().then(module => { + if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) { + this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this); + } + }); } this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); if (this.options.overviewRuler.width) { - this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); + loadOverviewRulerRenderer().then(module => { + if (this.options.overviewRuler.width && !this._overviewRulerRenderer && this._viewportElement && this.screenElement) { + this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement)); + } + }); } this.optionsService.onSpecificOptionChange('overviewRuler', value => { if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) { - this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); + loadOverviewRulerRenderer().then(module => { + if (!this._overviewRulerRenderer && this.options.overviewRuler.width && this._viewportElement && this.screenElement) { + this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement)); + } + }); } }); // Measure the character size From 369ca9b52507255f36ebd6d39a04151e25aa4229 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:58:45 +0000 Subject: [PATCH 7/7] Add error handling for dynamic imports Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com> --- src/browser/CoreBrowserTerminal.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 8d9c4935d8..e5515e90d4 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -304,6 +304,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) { this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this); } + }).catch(() => { + // Failed to load accessibility manager module }); } } else { @@ -619,6 +621,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) { this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this); } + }).catch(() => { + // Failed to load accessibility manager module }); } this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); @@ -628,6 +632,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (this.options.overviewRuler.width && !this._overviewRulerRenderer && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement)); } + }).catch(() => { + // Failed to load overview ruler renderer module }); } this.optionsService.onSpecificOptionChange('overviewRuler', value => { @@ -636,6 +642,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (!this._overviewRulerRenderer && this.options.overviewRuler.width && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement)); } + }).catch(() => { + // Failed to load overview ruler renderer module }); } });