Skip to content

Expose important pieces of vs folder on API #5283

@jerch

Description

@jerch
Member

Coming from #5251 (comment)

Currently using Event, Disposable etc. from the vs/* path in addons adds ~50kB on addons. We should investigate, if and how we can save some binary size on addons by exposing crucial parts on the API, so the impls dont have to be copied over for every single addon.

Activity

Tyriar

Tyriar commented on Jan 7, 2025

@Tyriar
Member

Good idea for common things like Event👍

jerch

jerch commented on Jan 7, 2025

@jerch
MemberAuthor

Looking through the list of addons, these would benefit from exposing event/emitter stuff on the API:

  • image addon
  • search addon
  • webgl addon
jerch

jerch commented on Jan 8, 2025

@jerch
MemberAuthor

@Tyriar Did some further digging around Event/Emitter and Disposable:

  • Event/Emitter:
    • pulls ~40kB into an addon
    • easy to APIfy, since its usage narrows down to a new Emitter<T>() call
    • call should be moved from ctor to activate, where the addon gets a hold of the terminal object
  • Disposable:
    • pulls ~25kB into an addon
    • hard to APIfy, as it is an abstract base class meant to be used with class extends Disposable
    • Disposable methods & ctor have a relative low footprint and only call into statics in vs/base/common/lifecycles (no further inheritance chaining)
    • --> might be possible to provide the same logic as a ctor mixin via API to be constructed at runtime (not sure yet...)
  • Disposable + Event/Emitter:
    • pulls ~50kB into an addon (Disposable + Emitter share a lot of code)

Given the list of addons using vs/* imports those are the potential savings on the minified bundles:

  • image addon: uses Disposable, APIfied would save ~25kB (current ~80kB)
  • search addon: uses Emitter, APIfied would save ~40kB (current ~50kB)
  • webgl addon: uses Disposable + Emitter, APIfied would save ~50kB (current ~240kB)
Tyriar

Tyriar commented on Jan 8, 2025

@Tyriar
Member

@jerch if you don't extend the typical thing is to use DisposableStore as a property instead. That's less convenient sometimes but if it'll save a bunch we can use that approach exclusively in addons instead of extends? Example:

https://github.com/microsoft/vscode/blob/decaa08e9945fad06912b5aafbd8f0f0e88e11c2/src/vs/workbench/contrib/terminal/browser/terminalView.ts#L69

Did you want to take a stab at a PR for this or want me to, since I introduced the problem? 😉

jerch

jerch commented on Jan 8, 2025

@jerch
MemberAuthor

@jerch if you don't extend the typical thing is to use DisposableStore as a property instead.

Oh ic, yeah that might be the easier pattern then. Although I found a pattern with runtime extends leading to the same inheritance chain, schematically:

/////////////////
// before
// in AddonXY.ts
import { Disposable } from 'vs/base/common/lifecycle';
export class AddonXY extends Disposable { ... }

/////////////////
// with runtime extends
// in core terminal (quickly hacked into core)
  private helperCls = class extends Disposable {}
  public disposableCtor(): {new(): Disposable} {
    return this.helperCls;
  }

// in AddonXY.ts
import type { Disposable } from 'vs/base/common/lifecycle';
export function AddonXY(terminal: Terminal, ...) {
  const DisposableBase = (terminal as any)._core.disposableCtor() as {new(): Disposable};
  const AddonXY = class extends DisposableBase { ... };
  return new AddonXY(...);
}

It works basically the same as before, but now needs a terminal arg at the fake ctor already to derive the Disposable impl from there. Its not nicely abstracted yet, the helperCls looks like a code smell but is needed as the base class is abstract. Beside that it fully maintains the correct inheritance chain with a working super(). It is somewhat a class mixin pattern, but also relies on ES5 new behavior. Maybe the class factory aspect could be stated more clearly by separating the concerns, whatever if preferrable.

Did you want to take a stab at a PR for this or want me to, since I introduced the problem? 😉

Up to you. I can try to formalize that idea, though I am not 100% sure yet, if this works properly in all circumstances. At the moment this is a full drop-in replacement with only the terminal argument as difference.

Edit: To get instanceof properly working the class factory part should be separated from the instantiation for sure.

Tyriar

Tyriar commented on Jan 8, 2025

@Tyriar
Member

Runtime extends looks pretty confusing to me. The composition approach isn't quite as convenient but it's super clear how it works. We could also have a special AddonDisposable helper in shared/ that just wraps the API object?

// shared/
export class AddonDisposable {
  constructor(storeCtor: DisposableStoreCtor) {
    // init protected readonly _store
  }
  dispose() {
    this._store.dispose();
  }
}
// addon
export class WebglAddon extends AddonDisposable {
  constructor() {
    super(xterm.DisposableStore);
  }
}

That way only the class in shared/ will be pulled into the addon bundle

jerch

jerch commented on Jan 8, 2025

@jerch
MemberAuthor

Yepp, +1 to composition. It also needs less awkward APi additions.

jerch

jerch commented on Jan 9, 2025

@jerch
MemberAuthor

@Tyriar Would we need a separate xterm-shared for external addon creators, or will installing the xterm package with extended API be enough in the end to build an external addon? To me it seems quite involved if the full xterm source would be needed just to build a tiny addon xy.

Tyriar

Tyriar commented on Jan 9, 2025

@Tyriar
Member

@jerch I don't think so, it'll be a very small wrapper and they'll only be gaining things by having new stuff exposed on the API.

linked a pull request that will close this issue on Jan 10, 2025

7 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiarea/performancetype/debtTechnical debt that could slow us down in the long runtype/enhancementFeatures or improvements to existing features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @Tyriar@jerch

      Issue actions

        Expose important pieces of vs folder on API · Issue #5283 · xtermjs/xterm.js