-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Project compiles OK but hangs when --watch is added #25023
Comments
This is potentially related to #25018 |
In the attached repro, the declaration emit takes forever (which is used in --w mode to determine set of files to emit). This seems to repro as well on doing |
@sheetalkamat If it's not too much trouble to explain (or if there is somewhere you could point me I'd really appreciate it), how were you able to see that? Are you running the compiler in a debugger / are there flags I can enable that dump out compiler phases / the files being processed. |
@benjaminjackman i just ran your code with |
I'm not sure it's so much as hanging as taking (close to) forever because of the number of types it needs to print structurally with import types (mixin pattern + inferred conditional types = nonlocal aliases for all the things). I cut the sample down in size and it actually completes (after an inordinate amount of time). A telltale sign is the fact that the memory usage doesn't really go up (or it does, but incredibly slowly) - meaning neither stack space nor heap are in heavy overuse (and breaking randomly, you're usually never more than ~5 types into an anonymous type stack, at most). I'm guessing that |
While caching is probably still a good idea, given how often I was breaking into that code, on further inspection that isn't the root problem, sadly - just an aggravating factor. T.T |
Most of it is repeitions of the same structural type, unfortunately: {
good: import("h-mst/src/BetterMobxStateTreeTs").ModelReferenceFactory<string | number, import("h-mst/src/BetterMobxStateTreeTs").ModelInstanceForAllIn<{
id: import("h-mst/src/BetterMobxStateTreeTs").IndentifierFactory<string>;
color: string;
tags: import("h-mst/src/BetterMobxStateTreeTs").OptionalFactory<string[] | import("mobx/lib/types/observablearray").IObservableArray<string>, string[], import("mobx/lib/types/observablearray").IObservableArray<string> & import("h-mst/src/BetterMobxStateTreeTs").IToJson<string> & import("h-mst/src/BetterMobxStateTreeTs").IStateTreeNode>;
transient: import("h-mst/src/BetterMobxStateTreeTs").OptionalFactory<boolean, boolean, boolean>;
}> & import("h-mst/src/BetterMobxStateTreeTs").IToJson<import("h-mst/src/BetterMobxStateTreeTs").ModelSnapshotForAllIn<{
id: import("h-mst/src/BetterMobxStateTreeTs").IndentifierFactory<string>;
color: string;
tags: import("h-mst/src/BetterMobxStateTreeTs").OptionalFactory<string[] | import("mobx/lib/types/observablearray").IObservableArray<string>, string[], import("mobx/lib/types/observablearray").IObservableArray<string> & import("h-mst/src/BetterMobxStateTreeTs").IToJson<string> & import("h-mst/src/BetterMobxStateTreeTs").IStateTreeNode>;
transient: import("h-mst/src/BetterMobxStateTreeTs").OptionalFactory<boolean, boolean, boolean>;
}>> & import("h-mst/src/BetterMobxStateTreeTs").IStateTreeNode & {
readonly abbrev: string;
readonly isFood: boolean;
}>;
amount: import("h-mst/src/BetterMobxStateTreeTs").NumberFactory;
} @benjaminjackman is there some kind of alias we should be striving to use instead of these verbose structural decompositions? |
If anyone is curious, it seems like |
On a slightly different tack: |
@weswigham So basically I am trying to structurally type mobx-state-tree using a .d.ts. file, which is as you are seeing fairly complex. Basically mobx-state-tree wraps objects into enhanced versions (called models) that add a lot of features (they can be transformed to and from json / plain old javascript object literals, they may be referred to by other objects by an string/numeric identifier, and can emit patches when things are changed, and on and on). So for each model (what would be a class in normal typescript land) I need to calculare 3 separate types, which are each generated by walking the properties of the model: one for what type is legal to use in their construction. E.g. some fields have default values, so they do not need to be specified in this Additionally, methods can be added onto the model with .views(self => {...}) and .actions(self => {...}) enhancers (that basically & their own return value with the calling type to produce an enhanced type). When a model references or nests another model a seemingly big branch factor happens because how a model is created for example depends on the creation type of all it's children (which themselves can be models.) I could keep going but .. phew! That's enough I am sure you get the point... I'll go over some hacks I have used to speed things up but I don't have a good enough handle on how the typescript compiler is caching stuff to know if it's actually a valid approach or I am just making random noise against a complex space. |
Not at all.
Maybe it gets used as a cache by watch to prevent recompiling? If it's not needed for that then I'd rather turn it off. I am able to hack together decent compile times by just running tsc without watch (using something like nodemon -x to trigger compiles on file changes). However that obviously seems silly. |
Ah, I see, right, because the hash of the declaration output is used for checking up-to-dateness, so you get them built even if you yourself aren't using them. |
I hope this doesn't come across as super annoying but having a couple of flags that would cause the compiler to just dump out what it is doing as it is doing it and what file it's processing (optionally with timestamps) would be extremely helpful for folks that don't know the compiler internals at all to atleast get a handle on which of there files is causing problems. I toyed around with --traceResolution (which atleast sort of lets me know if the compiler is making progress) because whittling my codebase down to this example took a bit of time of just erasing stuff and waiting iteratively until I reduced it to what I uploaded here. Getting something that even just said what phase the compiler was in would have been very useful (even if it wasn't at a file by file level) for that process. There is Also it would be a nice feature to have for attempting to work around the issue, for example now that I know those files are causing problems (which I have suspected) I can atleast try to do more 'caching' there or cut them down if possible. BTW I used to have the mobx-state-tree version of a union type ... which is basically unusable due to the compile time explosion it induces. |
So I have a couple hacks that makes thing a bit better:
export const Player = types.model({
id : types.identifier(types.string),
inventory: Inventory,
progress : ProgressModel,
popPool : PopPool,
laborOrders : LaborOrders,
})
type PlayerTypeOf = typeof Player.Type
//Use this type elsewhere
export interface Player extends PlayerTypeOf {} It seems to improve performance somewhat. Also I have noticed that using a class instead of type literal in calls to model seems to improve things as well, for example writing Player like this export const Player = types.model(new class Player {
id = types.identifier(types.string)
inventory= Inventory
progress = ProgressModel
popPool = PopPool
laborOrders = LaborOrders
}) when I do that perfomance seems to improve when the models start to have a decent amount of nesting. |
Hm, I can make |
@weswigham Sounds like we are working down similar paths. Can you by chance show me what it means to make the name of the internal class type visible? I was just about to post that I am able to get watch to work on a really simplified example by basically throwing the kitchen sink at things for the compiler to cache on then making sure those are used externally. I made a snippet that basically does the Here is what I am talking about: const TimesGoodAmountsModel = augment(
xtypes
.clsModel(
class TimesGoodAmounts {
times = types.number
goodAmounts = GoodAmounts
},
)
.views(self => {
return {
withTimes(times: number) {
return {
...self.toJSON!(),
times,
}
},
get totalGoodAmounts(): GoodAmounts {
if (self.times == 1) {
return GAS(self.goodAmounts)
} else {
return TIMES_GAS(self.times, self.goodAmounts)
}
},
}
}),
self => {
return {
empty() {
return {
times: 0,
goodAmounts: [],
}
},
}
},
)
type TimesGoodAmountsModelTypeOf = typeof TimesGoodAmountsModel
type TimesGoodAmountsTypeTypeOf = typeof TimesGoodAmountsModel.Type
export interface TimesGoodAmountsType extends TimesGoodAmountsTypeTypeOf {}
type TimesGoodAmountsSnapshotTypeTypeOf = typeof TimesGoodAmountsModel.SnapshotType
export interface TimesGoodAmountsSnapshotType extends TimesGoodAmountsSnapshotTypeTypeOf {}
type TimesGoodAmountsCreationTypeTypeOf = typeof TimesGoodAmountsModel.CreationType
export interface TimesGoodAmountsCreationType extends TimesGoodAmountsCreationTypeTypeOf {}
export interface TimesGoodAmounts extends TimesGoodAmountsModelTypeOf {
Type: TimesGoodAmountsType
CreationType: TimesGoodAmountsCreationType
SnapshotType: TimesGoodAmountsSnapshotType
}
export const TimesGoodAmounts: TimesGoodAmounts = TimesGoodAmountsModel Thanks for pointing me in the right direction in terms of the .d.ts size explosions. I have feeling it's a good thing to check for other times I have seen slowness from the compiler when I have been doing a lot of branching with nested types. One suggestion that would cut this approach down in half, maybe typescript should allow interfaces to extend typeof X since it appears to work ok otherwise. I will try just exporting types = typeof next without using the interface extends trick since it will cut down a lot of on the boilerplate, though i think the interface hack had other side benefits in terms of what the compiler was able to cache. I have heard that it can cache an interface but not a type declaration (hence the desire to have an interface extend the type before exporting). |
And it is definitely ultimately the structural decomposition of classes that ends up as the issue here (aggravated by the combinatoric expansion masked via the conditionals) - I experimentally disabled that feature in our declaration emitter (it is just one of the flags set at the top), and while it introduced some errors (as some types became unprintable), it also let it build pretty much instantly with |
Aliases are cached in a best effort kinda way for intersections and unions; so |
And writing out the type annotation like that allows the compiler to name the type without decomposing it, conveniently working around the expansion issue. I'm hoping there's some way to generalize it though, so the compiler can apply this kind of optimization itself when generating declarations; the issue I see though, is that even if I'm free to introduce new local names, if it ends up used in another class in another file, that local name won't be available (since it's in a different scope), and it'll be back to square one; meaning I'd have to export all the generated names, but that'd affect the file's API, which is undesirable (though it is what you've done in the example you wrote above). 😡 |
@weswigham ok that makes sense for what it's worth this is basically the simplest snippet-table boilerplate version I have come up with //Able to reduce .d.ts emits considerably
//-------------
export type TimesGoodAmountsType = typeof TimesGoodAmountsModel.Type
export type TimesGoodAmountsSnapshotType = typeof TimesGoodAmountsModel.SnapshotType
export type TimesGoodAmountsCreationType = typeof TimesGoodAmountsModel.CreationType
export type TimesGoodAmounts = typeof TimesGoodAmountsModel & {
Type: TimesGoodAmountsType
SnapshotType: TimesGoodAmountsSnapshotType
CreationType: TimesGoodAmountsCreationType
}
export const TimesGoodAmounts: TimesGoodAmounts = TimesGoodAmountsModel this did not work and still is causing the massive emits //Does NOT reduce .d.ts emits significantly enough
//-------------
export type TimesGoodAmountsType = typeof TimesGoodAmountsModel.Type
export type TimesGoodAmountsSnapshotType = typeof TimesGoodAmountsModel.SnapshotType
export type TimesGoodAmountsCreationType = typeof TimesGoodAmountsModel.CreationType
export type TimesGoodAmounts = typeof TimesGoodAmountsModel
export const TimesGoodAmounts: TimesGoodAmounts = TimesGoodAmountsModel because those internal types need to be cacheable by the compiler as well I am guessing. edit I realize in example 2 it's kind of silly to expect those types above would help, but typically they are used by other mobx-state-tree classes as aliases to avoid having to type out typeof type ... (being able to nest types within types would be a nice addition here) e.g. const X = {
type S : string
}
type S = X.S instead of having to do const X = {
//Dummy value don't use!
S : string = null as never
}
type S = typeof X.S |
At present - not really. (Other than annotate all your complex types which would otherwise cause the combinatoric explosion in type output or don't use the watch or declaration flags) This is really complex, because fixing it without disabling some declaration emit features which are important for making, eg, mixins work likely requires new syntax in the language itself. So I'm looking into it myself, but it'll probably be a bit, unfortunately. |
@weswigham I'm sure I don't understand the subtleties of the problem, but this seems like a pretty bad defect. In my project a build from scratch is taking 15 seconds, whereas an incremental build is taking ~4 minutes. Until this is fixed I'm probably going to have to avoid |
@weswigham I had a couple of ideas for mitigating this in the meantime:
The way I am combating this in practice is to write code, observe that watch stops showing errors (which can be a pretty frustrating process), stopping what I am doing and emitting .d.ts files / trial and error guessing where the explosions are coming from. It just stinks because if this was a smooth, then the |
I made a gist that describes how to use chokidar to call tsc --noEmit whenever a file changes in case anyone stumbles into this ticket: |
Self-note: Like @benjaminjackman proposed, the best way to provide a short term fix for this is to provide a flag which tells watch mode to skip checking declaration emit. |
@weswigham You would need to handle the builder correctly in that scenario because builder works on declaration files being generated to decide which files to emit. |
Hey @benjaminjackman @pelotom Something that really helped with spinning up watch is to mask MST models behind interfaces, i didn't check how its effect decelerations emits, A quick and minimal example: export type ForDirectExtend<T> = T;
const MyAModelInferred = types.model("MyAModel", {
items: types.array(types.string)
});
interface MyAModelType extends ForDirectExtend<typeof MyAModelInferred> {}
// now MyAModel is fast!
const MyAModel: MyAModelType = MyAModelInferred;
// Now even MyBModelInferred is not very slow, becouse its referencing the "fast" MyAModel and not the inferred thing
const MyBModelInferred = types.model("MyBModel", {
somethingsomething: types.map(MyAModel)
});
interface MyBModelType extends ForDirectExtend<typeof MyBModelInferred> {}
const MyBModel: MyBModelType = MyBModelInferred; And if we could hint typescript to create intermediate type names instead all of it, that would be great! |
I'm also experiencing this issue with Steps to reproduce:
I'm on |
Issue still persists in version 4.1.2. |
I believe "smarter type alias preservation" feature |
bumping issue. |
TypeScript Version: 3.0.0-dev.20180616 and 2.9.2
Search Terms: typescript watch --watch hangs stalls slow
Hi I have minimized one of my projects to an example source (which I have attached [1]) that:
To compile the example:
yarn install
to add the project's deps.3a. run
yarn tsc-ok
for the example where it does compile3b. run
yarn tsc-bad
for the example where it doesn'tBTW: Is there some way for me to tell the compiler to dump out the source files it is processing as it processes them? It would be extremely helpful for tracking this down to the specific issue.
1: Source Code: tsc-watch-hangs.zip
The text was updated successfully, but these errors were encountered: