-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Add support for import defer proposal
#60757
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
Conversation
|
Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page. Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up. |
942385c to
de82ce7
Compare
I guess this goes in favor of using |
src/compiler/factory/nodeFactory.ts
Outdated
|
|
||
| // @api | ||
| function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { | ||
| function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined, phase: ImportPhase): ImportClause { |
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.
Preemptively noting that this is a breaking API change, and would require a deprecation helper in the deprecations project to tack onto our public API something that will set a default for the new parameter.
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.
It may also be the case that phase needs to go after isTypeOnly since AST node builder parameters and properties are intended to be in source order.
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 don't think we want to mix type imports with defer or source. Something like import type source foo from "foo" doesn't make sense if all source imports will have the same type, and import type defer * as foo from "foo" would essentially be the same as import type * as foo from "foo".
If we consider type, defer, and source to be mutually exclusive, then I would suggest we replace the isTypeOnly parameter with something like importModifier: SyntaxKind.TypeKeyword | SyntaxKind.DeferKeyword | SyntaxKind.SourceKeyword | boolean | undefined and have ImportClause be:
export interface ImportClause extends NamedDeclaration {
readonly kind: SyntaxKind.ImportClause;
readonly parent: ImportDeclaration | JSDocImportTag;
/** @deprecated */
readonly isTypeOnly: boolean;
readonly importModifier: SyntaxKind.TypeKeyword | SyntaxKind.DeferKeyword | SyntaxKind.SourceKeyword | undefined;
readonly name?: Identifier; // Default binding
readonly namedBindings?: NamedImportBindings;
}For back-compat purposes, we can set isTypeOnly to true if importModifier is SyntaxKind.TypeKeyword.
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 the issue with that scheme is that it's a little weirder to capture multiple modifiers being used in the cases of error recovery.
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 was originally going with something very similar to Ron's suggestion, and consider "type" just as another phase. I ended up not doing it because of the AST breaking change, but if just keeping the old property for backwards compat is ok then I'd go for it.
I think the issue with that scheme is that it's a little weirder to capture multiple modifiers being used in the cases of error recovery.
Error recovery is going to be tricky anyway, because each modifier is a valid identifier so things like import type defer are a potentially valid start of an import declaration. I wonder how likely it is for people to accidentally write two modifiers in an import.
I'm not an expert per se, but I would expect that these need to be modifiers so that they are nodes that can be walked, since people can stick comments between them and so on... |
Modifiers would precede the But this is close syntactically to |
We have |
|
That said, |
4ad3ba8 to
b1f506b
Compare
|
I updated the PR to go with the suggestion in #60757 (comment), which is what I found the cleanest. Happy to do differently if needed. The API change is now backwards compatible. Probably I should do the same change for I also added a test showing that comments are properly preserved. |
b1f506b to
bdf98f5
Compare
|
Marking as draft until I implement |
|
Oh well, that was simpler than expected. Given that |
808450c to
621eee7
Compare
621eee7 to
e8d76ea
Compare
| const file = getSourceFileOfNode(node); | ||
| Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); | ||
| Debug.assert(node.name.escapedText === "defer" || !!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); | ||
| return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; |
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 import.defer be returning errorType here? Should we also report an error here for import.defer outside of a call?
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 now moved the import.defer check to the checkMetaProperty, which also already calls checkGrammarMetaProperty to check if import.defer is outside of a call. I also changes isExpressionNode() to return false for the import.defer in import.defer(...).
So now this branch is only reached if we have a "standalone" import.defer, for which checkGrammarMetaProperty reports an error, and so returning errorType is correct.
| ns.foo(); | ||
| }); | ||
|
|
||
| import("./a.js"); // TODO: Without this the import.defer cannot resolve ./a |
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'm going to debug this next week, but if you have any pointer as to why I would be getting an error about ./a.js not being resolved in import.defer(...) without this I'd appreciate it :)
|
Sorry for bother ,i have some quesions:
I am currently working on supporting this syntax for the biome parser, so I would like to see if TS has any support for this syntax in its parser. |
|
|
|
||
| if (node.keywordToken === SyntaxKind.ImportKeyword) { | ||
| if (node.name.escapedText === "defer") { | ||
| Debug.assert(!isCallExpression(node.parent) || node.parent.expression !== node, "Trying to get the type of `import.defer` in `import.defer(...)`"); |
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 assertion seems like it could result in an exception in an editor as you type import.defer. I'd rather we not crash here.
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 don't think this can ever cause a crash. These are two cases:
- we have
import.defer(...) - we have
import.deferoutside of a call
In the first case, we are handling it in the CallExpression visitor and we do not recourse into the import.defer node. This assertions is verifying this.
In the second case, !isCallExpression(node.parent) || node.parent.expression !== node is true so the assertion does not fail.
5d38296 to
84c71d5
Compare
|
Note: this proposal reached stage 3 at the last TC39 meeting. |
|
@weswigham Actually, reading https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution, should
The latest Node.js version does not support this proposal yet. |
84c71d5 to
1886e24
Compare
|
@weswigham All your suggestions have been applied, except for the I ended up dropping support for I also added support for the new syntax when |
weswigham
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.
We'll need to remember that when we add support for these to node module modes to make support format-dependent and/or downlevel it, but for now, this is probably fine.
|
Unfortunately this broke code like: import defer = require("./defer"); |
|
PR coming leter today! |
This proposal is currently at Stage 3.
This PR only needs to add parsing support for the proposal:
import defer * as nsis meant to "look like" the one created byimport * as nsAs you'll see from the code, whether a module is deferred or not is not a boolean but an enum. That's because of the
import sourceproposal: there is no PR for it yet, but once it will be implemented it should be done by adding one more possible "import phase" to this enum. A question I have is if that should be an enum, or just an union ofundefined | DeferKeyword(which will one day becomeundefined | DeferKeyword | SourceKeyword).Fixes #59391
This patch was originally written by @ryzokuken