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
Rewrite afterLayoutFlush
as a generic TS function.
#32926
Conversation
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser: Sections (~720 bytes added 📈)
Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to. Async-loaded Components (~168 bytes added 📈)
React components that are loaded lazily, when a certain part of UI is displayed for the first time. Legend What is parsed and gzip size?Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory. Generated by performance advisor bot at iscalypsofastyet.com. |
This PR exposes an interesting problem: it appears that |
@@ -12,18 +14,23 @@ | |||
* https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers | |||
* | |||
* @param {Function} func The function to be invoked after the layout flush | |||
* @returns {Function} Returns the new delayed function | |||
* @returns {Function} The new delayed function |
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've been removing the types from the JSDoc comments when adding them in the code. To me it makes sense not to keep two copies of that information next to each other when one will be tied to the code and the other manually updated. Already we have a divergence here because the JSDoc doesn't mention anything about being cancelable.
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 actually did that at first, but it produces linting errors with our current config:
<path>/wp-calypso/client/lib/after-layout-flush/index.ts
5:1 error Missing JSDoc return type valid-jsdoc
16:4 error Missing JSDoc parameter type for 'func' valid-jsdoc
Should we remove JSDoc linting from TS files if we expect types to be omitted in the doc (which makes a lot of sense)?
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.
that's odd. I thought I removed those lint lines. In #32872 I added requireParamType: false
to our eslint
config but it hasn't merged yet…
https://github.com/Automattic/wp-calypso/blob/master/packages/eslint-config-wpcalypso/index.js#L113
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 suppose I could remove the JSDoc altogether and convert this to a standard comment, but that seems less than ideal, as I suspect IDEs won't pick it up.
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.
Actually, I'll add a comment for now, to disable the valid-jsdoc
rule throughout the file.
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.
feel free to add that property to the eslint
rule - I'm pretty sure we'll want it generally going forward, or create a new PR to add it, or wait and I'll try to make a PR to add it
export default function afterLayoutFlush( func ) { | ||
let rafHandle = undefined; | ||
let timeoutHandle = undefined; | ||
export default function afterLayoutFlush< T extends ( ...args: any[] ) => any >( func: T ) { |
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.
this may be the equivalent of <T extends Function>
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 thought so too, but switching to that produces the following error further down in the scheduleRAF
and scheduleTimeout
return
s:
Conversion of type '(this: any, ...args: any[]) => void' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
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.
sorry for the delay - it's something we'd have to coordinate below.
also, can you help me understand the purpose of lastThis
and the like? what are those there fore and why are we using them? are we having trouble with this
binding and maintaining context?
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.
afterLayoutFlush
is meant to be as bullet-proof as possible with regards to this
usage, similar to defer
and friends in Lodash. Because of that, we store and restore the this
we receive, so that whatever the context the function is used in, everything will still work.
}; | ||
|
||
// if RAF is not supported (in Node.js test environment), the wrapped function | ||
// will only set a timeout. | ||
let wrappedFunc = scheduleTimeout( func ); | ||
let wrappedFunc: T = scheduleTimeout( func ); |
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 do we always create the timeout function even when requestAnimationFrame()
is available?
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 correct flow is to go through both. The article linked to in the file explains why, exactly, but it basically boils down to browser rendering timing quirks.
When requestAnimationFrame
isn't available, we do a best effort of running through just scheduleTimeout
, which is better than nothing.
Does it seem like this could be easier if we collapsed the two halves of this function into one, whereby we only track one "scheduler handle" instead of two, and whereby we only have one wrapping function instead of two? I'm sure I'm missing something, but why does the function work different in |
Doodling… const [ schedule, clearSchedule ] = typeof requestAnimationFrame === 'function'
? [ requestAnimationFrame, clearAnimationFrame ]
: [ setTimeout, clearTimeout ];
export default afterLayoutFlush = f => {
let handle: TimerHandle | undefined;
const wrapped = ( ...args ) => {
if ( handle ) {
return;
}
handle = schedule( () => ( handle = undefined, f( ...args ) ) );
}
wrapped.cancel = () => ( clearSchedule( handle ), handle = undefined );
return wrapped;
} We impose the constraint that I didn't verify, but when I see, for example, any thoughts? |
Again, the correct flow is to apply both.
The raf code doesn't need to do as much, because the timeout code will still run (and always run, even if the raf code doesn't). These are stages, not alternative paths. Only on the final stage do we need to ensure that the saved attributes and |
} | ||
|
||
// NodeJS and the browser have different return types for `setTimeout`. | ||
type TimeoutHandle = ReturnType< typeof setTimeout >; |
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.
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 problem, I'll pull in your changes there 👍
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.
Done!
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.
@sgomes thanks for doing this. putting this out there should be good and even if we refine the types I think that getting these changes out first will help
I've requested that you move TimeoutHandle
into client/types.ts
but you can ignore that. if you do ignore it would you mind adding a @TODO
that I can find when I consolidate those global types later?
26a77b8
to
542fbd3
Compare
@dmsnell Thank you for the review! Do you think this is ready for a merge despite the |
We usually don't break |
I figured as much, but wasn't sure, since it's common for the tests to break. I'll keep this PR open until TS support gets added to |
It took me a day here to think about it and yeah I can't recommend breaking desktop. Initially I overlooked that part. Is it broken already with the iframe bridge? Maybe we can all try and figure it out - get it building fine this week. |
@sgomes I've created Automattic/wp-desktop#598 to try and get on top of the desktop app. not sure if what I'm seeing over there is even an existing problem with my setup but I think some of my changes at least are essential in any case |
I started poking at updating the desktop build in Automattic/wp-desktop#599. It's going to take some effort, help appreciated. |
542fbd3
to
7d4f190
Compare
I've got a fix for TypeScript in wp-desktop: Automattic/wp-desktop#604 The tests are passing here because I rebased the desktop test branch on that PR ☝️. This still shouldn't be merged until that lands. |
Woohoo, thanks, @sirreal ! I'll wait until Automattic/wp-desktop#604 is merged :) |
We'll probably need a rebase for Desktop to be really happy to get #33230. Related: should Desktop (electron) use the evergreen build? |
No. This is how I set it up initially, but I ended up changing it later on. Electron does not necessarily offer all the features that the evergreen build may expect to be present, particularly since I don't think we update the Electron dependency in |
Automattic/wp-desktop#604 has landed. Let the TypeScript flow 😎 |
Woohoo, thanks @sirreal ! I'm rebasing this on |
It now takes a function and returns a cancelable function of the same type. This is a stricter contract that helps avoid typing errors in consumers of this library.
7d4f190
to
884ae27
Compare
Seems to be working fine 🎉 Want to give this a final look-over, @sirreal ? |
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.
afterLayoutFlush
now takes a function and returns a cancelable function of the same type. This is a stricter contract that helps avoid typing errors in consumers of this library.This library seemed like a good opportunity to learn and test out more advanced TS concepts like generics, intersection types and union types.
Changes proposed in this Pull Request
Testing instructions
There are very few actual code changes, with most of this PR being about types. The unit tests should be enough to ensure that the library continues working normally.