Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose important pieces of vs folder on API #5283

Open
jerch opened this issue Jan 7, 2025 · 7 comments
Open

Expose important pieces of vs folder on API #5283

jerch opened this issue Jan 7, 2025 · 7 comments
Labels
area/api area/performance type/debt Technical debt that could slow us down in the long run type/enhancement Features or improvements to existing features

Comments

@jerch
Copy link
Member

jerch commented Jan 7, 2025

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.

@jerch jerch added type/enhancement Features or improvements to existing features type/debt Technical debt that could slow us down in the long run area/performance area/api labels Jan 7, 2025
@Tyriar
Copy link
Member

Tyriar commented Jan 7, 2025

Good idea for common things like Event👍

@jerch
Copy link
Member Author

jerch commented Jan 7, 2025

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

  • image addon
  • search addon
  • webgl addon

@jerch
Copy link
Member Author

jerch commented Jan 8, 2025

@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
Copy link
Member

Tyriar commented Jan 8, 2025

@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
Copy link
Member Author

jerch commented Jan 8, 2025

@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
Copy link
Member

Tyriar commented Jan 8, 2025

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
Copy link
Member Author

jerch commented Jan 8, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/api area/performance type/debt Technical debt that could slow us down in the long run type/enhancement Features or improvements to existing features
Projects
None yet
Development

No branches or pull requests

2 participants