Skip to content

Add tooling to verify dependency chain #3033

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

Merged
merged 12 commits into from
May 14, 2020
Merged

Conversation

schmidt-sebastian
Copy link
Contributor

This adds tooling to verify top-level dependencies of tree-shakeable APIs. It is hooked up a a very basic version of a tree-shakeable API.

The goal of this API is to let us verify the dependency chain. One example where I used this is here: #3024

I suspect this might be helpful for other APIs and as such I added it to /scripts.

@@ -0,0 +1,15 @@
{
Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian May 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This file is generated by the Node CLI tool and then used in the unit test as the source of truth. It is checked in and as such provides a nice diff as we make changes.

"sizeInBytes" is for reference only.

@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/extractdeps branch from 9891d6e to f21776b Compare May 7, 2020 04:47
"dependencies": [
"bar"
],
"sizeInBytes": 39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is per function? seems like a pain to always update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is auto-updated via the script, but the script is not run automatically. I'll send this to @Feiyang1 and we can ponder how we can automate this step.

Copy link
Contributor

@avolkovi avolkovi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks cool, excited to see how it works in practice. Should we consider adding a pre submit hook that calls this to regenerate the .json?

@avolkovi avolkovi removed their assignment May 7, 2020
Copy link
Member

@Feiyang1 Feiyang1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool stuff. We should definitely automate it and generate a nicely formatted report that we can review for every PR.

For some reason, I kept getting Error: ENOENT: no such file or directory, open '/Users/feiyangc/Projects/firebase-js-sdk/packages/firestore/dist/lib/src/util/error.d.ts' and couldn't compile the project and test the scripts. Any clue?

Comment on lines 30 to 31
dependencies.sort();
expect(extractedDependencies).to.have.members(dependencies);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dependencies.sort(); is not necessary because order doesn't matter in .members().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

return JSON.stringify(result, null, 4);
}

const publicApi = extractFunctionsAndClasses(path.resolve(argv.types));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't handle reexports, e.g. export * from 'src/impl'. We can generate a rollup d.ts file to sidestep the problem, or we need to traverse the AST to get all exports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that more like the behavior we want though? We bundle all of our own sources into a single script. If one of our dependencies changes, we wouldn't want our tests to break.

Or are you planning to no longer ship a single bundled source file? That might break Firestore and other SDKs as we have a ton of transitive dependencies. Bundling works around that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are extracting APIs from the d.ts file, not the source file, right? Currently we don't bundle d.ts files into a single file, so there can be reexports from other d.ts files.

As we plan to have multiple entry points in firestore next, it will be easier to not have a single bundled source file so it's easier to share code between different entry points. Why would transitive dependencies be a problem though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public APIs are extracted from a handcrafted index.d.ts. Are we moving away from hand-crafted types? Note that this is not meant to work for all of Firebase, but only for each specific SDK.

FWIW, as more SDKs adopt this, we probably have to greatly expand the feature set of this tooling. I wonder if it makes sense to wait until there is a need, which can then happen together with CI integration?

Copy link
Member

@Feiyang1 Feiyang1 May 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I leave that decision to teams. But generally we don't need to handcraft d.ts anymore in the modular world, because we control what gets exported in the source code, and we can generate d.ts files with only public APIs from it automatically.

I generate index.d.ts from source code and use it for @firebase/app-exp. It allows me to keep documentation in the source code as well. I expect most of our SDKs to use automatically generated d.ts files in the modular world.

Sure. that sounds reasonable to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be interesting to see if we can hide SDK-public internals from the public typings without splitting up all Firestore classes into abstract Public-API-only base classes and implementation classes. I will keep an eye on this as we rewrite the client.

Comment on lines 103 to 108
ts.forEachChild(sourceFile, node => {
if (ts.isFunctionDeclaration(node)) {
exports.push(node.name!.text);
} else if (ts.isClassDeclaration(node)) {
exports.push(node.name!.text);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be interesting to see variable declarations too, e.g. the error message map.
It'd be nice to keep the types of the declarations and show them in the report, but it can be done in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be pretty easy to do as part of this PR. I will report back in my conversation with myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done.

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why you suddenly got the build failure. It seems unrelated to this PR. I was able to repro using a clean checkout though and fixed it. I hope there is no adverse size impact, but I am waiting for GitHub Actions to come back to confirm.

Comment on lines 30 to 31
dependencies.sort();
expect(extractedDependencies).to.have.members(dependencies);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Comment on lines 103 to 108
ts.forEachChild(sourceFile, node => {
if (ts.isFunctionDeclaration(node)) {
exports.push(node.name!.text);
} else if (ts.isClassDeclaration(node)) {
exports.push(node.name!.text);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be pretty easy to do as part of this PR. I will report back in my conversation with myself.

Comment on lines 103 to 108
ts.forEachChild(sourceFile, node => {
if (ts.isFunctionDeclaration(node)) {
exports.push(node.name!.text);
} else if (ts.isClassDeclaration(node)) {
exports.push(node.name!.text);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done.

return JSON.stringify(result, null, 4);
}

const publicApi = extractFunctionsAndClasses(path.resolve(argv.types));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that more like the behavior we want though? We bundle all of our own sources into a single script. If one of our dependencies changes, we wouldn't want our tests to break.

Or are you planning to no longer ship a single bundled source file? That might break Firestore and other SDKs as we have a ton of transitive dependencies. Bundling works around that.

@google-oss-bot
Copy link
Contributor

google-oss-bot commented May 12, 2020

Binary Size Report

Affected SDKs

No changes between base commit (ab2e73d) and head commit (b8ab8d9).

Test Logs

@schmidt-sebastian
Copy link
Contributor Author

@Feiyang1 The quick and dirty fix for your build issue increased the size of the SDK. Instead, I updated extract-api.ts to perform a precompile step.

@@ -40,7 +40,7 @@ export abstract class Emulator {

download(): Promise<void> {
return new Promise<void>((resolve, reject) => {
tmp.dir((err: Error, dir: string) => {
tmp.dir((err: Error|null, dir: string) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed to fix the CI.

@schmidt-sebastian schmidt-sebastian merged commit 94ee69a into master May 14, 2020
@firebase firebase locked and limited conversation to collaborators Jun 14, 2020
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/extractdeps branch July 9, 2020 17:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants