-
Notifications
You must be signed in to change notification settings - Fork 938
Add API Extractor for Firestore + API Pruning #3790
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
|
Binary Size ReportAffected SDKs
Test Logs |
@@ -1,544 +1,557 @@ | |||
/** |
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 this file be not committed since it's generated now?
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 prefer not deleting it yet as it makes the changes to this tool and the API in general much more visible. A next step is to use the new d.ts file as input to API Explorer. I think we can remove it once that is done.
}, | ||
{ | ||
// Copy into generated source files to support API Extractor | ||
src: 'src/protos/firestore_proto_api.d.ts', |
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.
You can change firestore_proto_api.d.ts
to a ts file even though it only has types, so TS will automatically generate the d.ts file for you in the dist folder.
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 is L5 level genius.
input: { | ||
type: 'string', | ||
demandOption: true, | ||
desc: 'The location of the index.ts 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.
s/index.ts/index.d.ts
* - Constructors are made private or protected if marked with | ||
* `@hideconstructor`/`@hideconstructor protected`. | ||
* | ||
* This function is meant to operate on DTS files generated by API explorer |
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.
s/explorer/extractor
* export class PublicFoo {} | ||
* export function doFoo(foo: PublicFoo); | ||
*/ | ||
function extractPublicName( |
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 would we use a private type in a public API? This feels like an issue that should be fixed in the source code. In case of multiple public classes extending PrivateFoo
, we use the first one which seems arbitrary and is probably wrong - considering PublicFoo1
and PublicFoo2
, both of which extends PrivateFoo
. doFoo
should accept either of them, but after this change, it will only accept PublicFoo1
.
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 is definitely an issue in our source code, but one that saves us 1000+ lines of code. The Lite SDK and the Exp SDK use the same Collection/Document/Query types and APIs, even though the actual instance of FirebaseFirestore/FirebaseDataConverter is different. If I don't perform this hack, then two versions of FirebaseDataConverter exist and all APIs that seemingly depend on the Lite type leak types that are invisible.
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.
Can you show me an example in a snippet? Don't lite and exp have separate d.ts files, why would there be 2 versions of FirebaseDataConverter? and what do you mean by leaking invisible types?
exportedTypes.push( | ||
ts.updateExpressionWithTypeArguments( | ||
type, | ||
type.typeArguments, |
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.
What if the public class/interface takes different numbers of type arguments? Will it create broken typings for this example?
class A<T> {
data: T;
}
class B extends A<string> {
}
ts.updateExpressionWithTypeArguments( | ||
type, | ||
type.typeArguments, | ||
ts.createIdentifier(publicName) |
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.
IIUC, this can potentially make the class/interface has more members than it really has. Considering this:
class A {
data: string;
}
export class C extends A {
additionalData: number;
}
export class B extends A {
}
will become
export class C {
data: string;
additionalData: number;
}
export class B extends C {
}
Now B
has additionalData
it didn't have before.
I think the safest strategy is always copy the members from the private symbols instead of replacing it with a public one that extends it.
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 becomes:
export declare class B2 {
data?: string;
}
export declare class C2 extends B2 {
additionalData?: number;
}
That seems correct to me.
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 confused. Is B2 B and C2 C from my example? Or are you giving a different example?
Nit: I saw some usage of |
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.
Here it is again.
Sorry, the commit history is a bit messy, but I wanted to keep it in case I need to go back to an earlier revision.
I addressed your main two points as follows:
- If a private API has different generic arguments from a public API, I will no longer simply substitute their names. Instead, I merge the declarations of the private API into the public type.
- To merge the declarations, I now use TypeScript's
codefix
package. At first, I tried to just port the code that we need, but it quickly got out of hand. So for now, this script relies on the same private API that VS Code uses to auto-fill missing imports. If this private API ever breaks, we can move the required parts into our repository (a hacky attempt is here: ffc5ba1#diff-6db0153ee9f6f29fa6fc10782448b200R204, but it still has some issues)
@@ -1,544 +1,557 @@ | |||
/** |
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 prefer not deleting it yet as it makes the changes to this tool and the API in general much more visible. A next step is to use the new d.ts file as input to API Explorer. I think we can remove it once that is done.
}, | ||
{ | ||
// Copy into generated source files to support API Extractor | ||
src: 'src/protos/firestore_proto_api.d.ts', |
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 is L5 level genius.
* export class PublicFoo {} | ||
* export function doFoo(foo: PublicFoo); | ||
*/ | ||
function extractPublicName( |
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 is definitely an issue in our source code, but one that saves us 1000+ lines of code. The Lite SDK and the Exp SDK use the same Collection/Document/Query types and APIs, even though the actual instance of FirebaseFirestore/FirebaseDataConverter is different. If I don't perform this hack, then two versions of FirebaseDataConverter exist and all APIs that seemingly depend on the Lite type leak types that are invisible.
ts.updateExpressionWithTypeArguments( | ||
type, | ||
type.typeArguments, | ||
ts.createIdentifier(publicName) |
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 becomes:
export declare class B2 {
data?: string;
}
export declare class C2 extends B2 {
additionalData?: number;
}
That seems correct to me.
if ( | ||
publicSymbol && | ||
publicSymbol.name !== currentName && | ||
verifyMatchingTypeArguments( |
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 not sure it's the sufficient condition for a safe replacement. Considering:
declare class A<T> {
aData: T;
}
export declare class B<T> extends A<string> {
bData: T;
}
export declare class C<T> extends A<T> {
}
Will it become?
export declare class B<T> {
aData: string;
bData: T;
}
export declare class C<T> extends B<T> { // it is incorrect, because the type of `aData` becomes `string` which was `T` in the original definition.
}
// can explore adding a slimmed down version of this package to our repository | ||
// if this dependency should ever break. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(ts as any).codefix.createMissingMemberNodes( |
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.
Does it handle generics with different names? For example:
declare class A<T> {
aData: T;
}
export declare class B<K> extends A<K> {
bData:K;
}
Does it become:
export declare class B<K> {
aData: K; // instead of aData: T
bData: K;
}
* export class PublicFoo {} | ||
* export function doFoo(foo: PublicFoo); | ||
*/ | ||
function extractPublicName( |
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.
Can you show me an example in a snippet? Don't lite and exp have separate d.ts files, why would there be 2 versions of FirebaseDataConverter? and what do you mean by leaking invisible types?
@Feiyang1 - Can you take another look at this? |
@schmidt-sebastian I don't see any new changes since my last review. Did you mean to push something, but didn't? |
This adds support for API extractor to Firestore Exp and Firestore Lite and a script to prune its output to match our previous manual API layer.
I manually verified that the new and the old type files are equivalent (which was quite painful, but very necessary).
There are some things we could still improve:
export
instead ofexport declare