feat: support async plugin's pre/post#16862
Conversation
| await wait(50); | ||
| this.file.ast.program.body[0].value = "success" |
There was a problem hiding this comment.
checking that promise is actually awaited
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/58233 |
nicolo-ribaudo
left a comment
There was a problem hiding this comment.
This looks good -- can you also ad a test with an async pre, which transforms the AST in such a way that makes sure that it's being awaited before running the visitors?
|
@nicolo-ribaudo there is one, it reassigns variable in the closure. If pre would not be awaited, visitor will print "failure" instead of "success". I also have question regarding typings. Should we update typings of pre/post to reflect that they are supporting Async result? |
I tried changing them in Where we do function chainMaybeAsync<Args extends any[]>(
a: undefined | ((...args: Args) => void | Promise<void>),
b: undefined | ((...args: Args) => void | Promise<void>),
) {
if (!a) return b;
if (!b) return a;
return function (this: unknown, ...args: Args) {
const res = a.apply(this, args);
if (res != null && typeof (res as Promise<void>).then === "function") {
return (res as Promise<void>).then(() => b.apply(this, args));
}
return b.apply(this, args);
};
}You can add a test with a plugin A that has plugin B in its |
9637819 to
295a9d7
Compare
|
@nicolo-ribaudo i changed types for pre/post and fixed chaining. Also added two more tests for pre/post with inheritance. The pipelines are failing for unknown reason. Could you check what's wrong? |
3049bd1 to
a448bbf
Compare
nicolo-ribaudo
left a comment
There was a problem hiding this comment.
It just needed a rebase.
I left a minor comment, but this looks good now!
| return { | ||
| inherits: pluginC, | ||
| async post() { | ||
| await wait(50); |
There was a problem hiding this comment.
Could you make pluginC async and keep pluginB sync? So we have async-sync-async, testing both combinations :)
There was a problem hiding this comment.
Done, test for pre uses sync-async-async and test for post uses async-sync-async.
a448bbf to
3601be9
Compare
|
@nicolo-ribaudo what about passing pre?: (this: S, file: File, ctx: {isAsync: boolean}) => void | Promise<void>;
post?: (this: S, file: File, ctx: {isAsync: boolean}) => void | Promise<void>;Or might be as a property on a PluginPass object? I'm afraid it would be a bit tricky to implement no-breaking logic for the plugins without that flag. |
|
Whops sorry, I missed the question. I would prefer to expose it on |
3601be9 to
166c7ff
Compare
|
@nicolo-ribaudo i've added a new property |
| /** | ||
| * The working directory that Babel's programmatic options are loaded | ||
| * relative to. | ||
| */ | ||
| cwd: string; | ||
|
|
||
| // The absolute path of the file being compiled. | ||
| /** The absolute path of the file being compiled. */ | ||
| filename: string | void; |
There was a problem hiding this comment.
With /** style of comment it's a jsdoc description which could be used as quick documentation in IDE.
| file: File, | ||
| key?: string | null, | ||
| options?: Options, | ||
| isAsync?: boolean, |
There was a problem hiding this comment.
i've added isAsync as optional argument to the last position, so it shouldn't be a breaking change to anyone who uses PluginPass constructor directly. However, i would like to add it as required argument after the file. Let me know if that change is acceptable.
There was a problem hiding this comment.
We only export PluginPass as a type and not as a constructor (see packages/babel-core/src/index.ts), so it should be fine to add it as a required property here.
There was a problem hiding this comment.
Actually, you can just remove the ?s from the other arguments since they are always always passed. It's probably a leftover from when we converted the repo from Flow to TS.
|
|
||
| describe("misc", () => { | ||
| it("unknown preset in config file does not trigget unhandledRejection if caught", async () => { | ||
| it("unknown preset in config file does not trigger unhandledRejection if caught", async () => { |
There was a problem hiding this comment.
fixed a typo
| fn.apply(this, args); | ||
| function chainMaybeAsync<Args extends any[], R extends void | Promise<void>>( | ||
| a: undefined | ((...args: Args) => R), | ||
| b: undefined | ((...args: Args) => R), |
There was a problem hiding this comment.
Here we assume a and b share the same return type, however, it is also possible that a is sync while b is async. I suggest we remove the R constraint and type them as (...args: Args) => void | Promise<void>. We can probably get rid of the final type cast as well.
For situations where both a and b are sync, we can add a type overload so that the chained function is still sync.
There was a problem hiding this comment.
I think the current typing is fine:
- if
aandbare sync/undefined,Risvoidand thuschainMaybeAsyncreturns(...args: Args) => void. - if
ais sync/undefined andbis async/undefined,Risvoid | Promise<void>and thuschainMaybeAsyncreturns(...args: Args) => void | Promise<void>. Note that thevoid |is correct, because ifbis undefined thenchainMaybeAsyncreturnsawhich returnsvoid. - if
aandbare async/undefined,RisPromise<void>and thuschainMaybeAsyncreturns(...args: Args) => Promise<void>.
Also note that this function is only called when a and b are either both sync, or both void | Promise<void>. We don't use it in mixed-type cases.
Also, a problem with a type overload is that every function is assignable to () => void, so it would also cover the case returning a promise:
https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzAAsotUBZKATwCMQBBAZ0vQB46YBzB+EADwxCpg3KKkoBtALoA+ABQAoePCgAueGlCJSIYPAA+8WbIB0p2FzXsuASngBeafABuOLMGsAaRfGpqNILVQdfUMTM04GSwjbB2dXdy9rNTDjc0j4KwYYxxc3AG55UEhYBBR0bDwCYlIKGnomVkyefkFhZTEpOW9VdSEA7V0DI1NUiKibexz4kIAFGBwAWywGEBZc4GlPb19ezQGQ4fCLDOjJuLdZ+aWVtfjNxOSRtPGss-XLxeXV9ekC+ULwNA4PAICAMEgAIzJbLnQbwOafG4-P6g8Fwbh2KokchUWiMZhgWSICEeSHWArwAD0lIAegB+IA
There was a problem hiding this comment.
On the mixed-type cases, for example, if inherits.pre is a sync function and plugin.pre is an async function, then the merged pre will be an async function.
Like you said, every function is assignable to () => void, so the type checking can't help very much here. I am fine with leaving it as-is.
|
Thanks for your reviews and helps on this PR. |
Enable support of async pre/post functions in plugins + update tests. As discussed here #16860
I'm also wondering of adding a
isAsyncflag for pre/post functions arguments so plugins could adjust it's own logic based on that. However i don't know how exactly to shape this signature, my proposition is:Let me know what you think, maybe there is a better place for that.