- Notifications
You must be signed in to change notification settings - Fork1.8k
Expose key helpers on the API for addons#5292
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:master
Are you sure you want to change the base?
Conversation
jerch commentedJan 10, 2025
@Tyriar Ah well, still not there - tried to simplify the imports/exports, which again led to pulling vs stuff into addon 😊 Need to fix that again... |
jerch commentedJan 10, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Well treeshaking is not working with webpack, a 3rd party addon would still include the whole xterm.js lib. Needs a separate shared.js output in the final xterm.js (not gonna fix that today...) |
Tyriar commentedJan 10, 2025
I wouldn't worry about 3rd party libs using our helper. They will have access to Emitter and Event via the API so they can make the tiny wrappers on their end if they need them. Not worth it imo. |
Uh oh!
There was an error while loading.Please reload this page.
typings/xterm.d.ts Outdated
| /** | ||
| * Get Emitter constructor. | ||
| */ | ||
| exportconstemitterCtor:EmitterCtorType; | ||
| /** | ||
| * Get DisposableStore contructor. | ||
| */ | ||
| exportconstdisposableStoreCtor:DisposableStoreCtorType; | ||
| /** | ||
| * Turn a function into a Disposable. | ||
| */ | ||
| exportconsttoDisposable:(fn:()=>void)=>IDisposable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think you should be able toexport class here, similar toexport class Terminal. That way we don't eed to deal with thisCtorType stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Isn't that pulling types from internal sources into the public API? My idea here was to decouple that with minimal stub types, so linter / type inspection doesn't rely on internal stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Isn't that pulling types from internal sources into the public API?
Everything in the API needs to be standalone, so we'd duplicate it there.
Also, we depend the other way for Terminal here to ensure our implementation matches the API:
| exportclassTerminalextendsDisposableimplementsITerminalApi{ |
So we could do the same forDisposableStore/Emitter/etc. by depending onxterm.d.ts frompublic/Terminal.ts again.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
| private_onChange:IEmitter<IProgressState>; | ||
| publiconChange:IEvent<IProgressState>; | ||
| constructor(protectedreadonly_emitterCtor:EmitterCtorType){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Maybe it's nicest to just pass in the whole xterm API object to some addons? I think we can do this without error:
importtype*asXtermApifrom'@xterm/xterm';importtype{Terminal,ITerminalAddon,IDisposable}from'@xterm/xterm';
That way it would be nicer from the embedder side:
newProgressAddon(xterm)newWebglAddon(xterm)etc.
Looks better than this imo:
newProgressAddon(AddonDisposable)newWebglAddon(AddonDisposable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yes, thats a good idea. The dispoable + emitter ctors, if both are needed, already make this cumbersome and looking ugly. With the whole exports as one arguments it gets much nicer and easier to comprehend on caller side.
I even wonder if we should make that the first default argument on all addon ctors, this way ppl wont get it wrong on certain addons, just apply it on all (well thats a major API shift).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Well the full module export type interface stubbing felt kinda wrong. My next approach looks like that:
on addon impl side:
import{ ...,ISharedExports}from'@xterm/xterm';exportclassAddonXYimplements ...{constructor(sharedExports:ISharedExports){// do something with things exposed under sharedExports}
on embedding side:
importtype{Terminal,sharedExports}from'@xterm/xterm';import{AddonXY}from'@term/addon-xy';constterm=newTerminal(...);constaddonXY=newAddonXY(sharedExports);
| private_onChange:IEmitter<IProgressState>; | ||
| publiconChange:IEvent<IProgressState>; | ||
| constructor(protectedreadonly_emitterCtor:EmitterCtorType){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
In retrospect it's obvious this would be a breaking change, but that's fine and worth it considering the wins we get in bundle size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
About breaking change - as I wrote above, I wonder whether to make the xterm exports the first ctor argument for all addons, e.g.:
import*asXtermApifrom'@xterm/xterm';import{AddonXY}from'@term/addon-xy';constterm=XtermApi.Terminal(...);constaddonXY=newAddonXY(XtermApi);
Then an addon ctor is free to use the exported ctors there or to ignore that argument:
importtype*asXtermApifrom'@xterm/xterm';classAddonXYextends ...{contructor(xterm:XtermApi,other_args){// if addon needs an event:this._onWhatever=xterm.Emitter<Whatever>(); ...// else: just ignore xterm argument}}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Or if thats too "globalish" looking, we could also aggregate the extra exports under ashared API endpoint:
import{Terminal,shared}from'@xterm/xterm';import{AddonXY}from'@term/addon-xy';constterm=newTerminal(...);constaddonXY=newAddonXY(shared);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
And last but not least - we could also keepTerminal the only exported impl endpoint and put the shared stuff on the terminal class instead:
import{Terminal}from'@xterm/xterm';import{AddonXY}from'@term/addon-xy';constterm=newTerminal(...);constaddonXY=newAddonXY(Terminal.shared);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The latter has a few advantages, like keeping stuff under the Terminal umbrella and automatically gaining access to those symbols even on a terminal instance.
Edit: Tbh the ctor argument idea raises in fact the question, why not to load addons this way in the first place with a terminal instance as first argument. Do you remember, why we have theloadAddon mechanics on the terminal the way it is implemented currently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
why not to load addons this way in the first place with a terminal instance as first argument
Terminal has always been the only thing exposed on the API, but it makes a lot of sense to pass in the full API when we start adding new things. This sort of breaking change is more impactful though as we can't just expect all addon ctors to have it as the first arg.
Do you remember, why we have the loadAddon mechanics on the terminal the way it is implemented currently?
Was a long time ago, but one of the big things we get isloadAddon lets the Terminal take ownership of it. So disposing of a terminal means the addon will be destroyed. That's pretty much allAddonManager does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yes it def. smells like quite the big API change would be needed, so idk exactly how to proceed. Maybe we should go back to conceptual structuring before inventing a square wheel here? Gonna try to do a write up of what we have currently vs. what could be done about it in#5283.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Posted long replies in#5283 (comment)
jerch commentedJan 11, 2025
True. So lets treat the shared stuff as internal helpers only, and maybe place some recipes for event or dispoable usage in the docs for external folks. Both can be done easily with composition after we added their ctors to the API. |
jerch commentedJan 12, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@Tyriar Results so far:
in total: from 415 kB down to 192 kB |
Tyriar left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Skimmed the PR and it looks good, some small comments. The next release is major so no problems with this breaking change especially considering the space savings.
| import{IDisposable,IDisposableStore,ISharedExports}from'@xterm/xterm'; | ||
| exportclassAddonDisposableimplementsIDisposable{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Should we also exportDisposable onISharedExports instead of this?
| publicreadonlyonRemoveTextureAtlasCanvas:IEvent<HTMLCanvasElement>; | ||
| constructor( | ||
| _sharedExports:ISharedExports, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
No_ since it's not a member
| // FIXME:@Tyriar - plz have a look at the following interfaces and | ||
| // to what degree those should be exposed or get stripped down |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
These look good as a starting set, can always expand it later
Uh oh!
There was an error while loading.Please reload this page.
Attempt tofix#5283.
@Tyriar
The addon stubs under
/sharedonly add ~500 bytes on the xtermjs package, and also on addons that derive their addon class from it. Only inconvenience so far is the fact, how we synthesize the final xtermjs package, which leads to an import switch:import { ... } from 'shared/shared';Currently I have only one testbed implemented in
ProgressAddon.tsusing theEmitterAddonclass. Should be the same withDisposableAddon, but I didnt want to change too much in code before getting some feedback from you. So plz have a look at the idea and whether it goes into the right direction. Also we kinda need to cut type ropes for the vs objects, so plz also see the added types in d.ts - we prolly should keep them as small as possible while still being useful.And last but not least the question, whether we should put these additions under a separate name in the API (maybe
shared.*?)