Skip to content
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

firebase doesn't play nicely with yarn install --flat #546

Closed
zevdg opened this issue Mar 4, 2018 · 6 comments · Fixed by #547
Closed

firebase doesn't play nicely with yarn install --flat #546

zevdg opened this issue Mar 4, 2018 · 6 comments · Fixed by #547

Comments

@zevdg
Copy link
Contributor

zevdg commented Mar 4, 2018

[REQUIRED] Describe your environment

  • Operating System version: Linux 4.15.7 (Solus 3)
  • Firebase SDK version: npm [email protected]
  • Firebase Product: firestore

[REQUIRED] Describe the problem

I was trying to follow best practices with my web app and ran yarn install --prod --flat to make sure that I wasn't using delivering multiple copies of the same libraries to my clients. I found several libraries with conflicts and they were all being pulled in by firebase. Specifically: minimist, readable-stream, isarray, process-nextick-args, string_decoder, assert-plus, extsprintf, glob, through2, vinyl, clone, clone-stats, replace-ext, glob-parent, is-glob, is-extglob, kind-of, and is-number.

Some further debugging...

$ npm ls --prod minimist readable-stream isarray process-nextick-args string_decoder assert-plus extsprintf glob through2 vinyl clone clone-stats replace-ext glob-parent is-glob is-extglob kind-of is-number
[email protected] /home/user/workspace/site
└─┬ [email protected]
  ├─┬ @firebase/[email protected]
  │ └─┬ [email protected]
  │   ├─┬ [email protected]
  │   │ ├─┬ [email protected]
  │   │ │ └── [email protected]
  │   │ ├─┬ [email protected]
  │   │ │ └─┬ [email protected]
  │   │ │   └── [email protected]  deduped
  │   │ ├─┬ [email protected]
  │   │ │ └── [email protected]
  │   │ ├─┬ [email protected]
  │   │ │ └─┬ [email protected]
  │   │ │   ├── [email protected]
  │   │ │   ├─┬ [email protected]
  │   │ │   │ ├── [email protected]
  │   │ │   │ ├── [email protected]
  │   │ │   │ └─┬ [email protected]
  │   │ │   │   ├── [email protected]
  │   │ │   │   └── [email protected]  deduped
  │   │ │   └─┬ [email protected]
  │   │ │     ├── [email protected]
  │   │ │     ├─┬ [email protected]
  │   │ │     │ └── [email protected]
  │   │ │     └─┬ [email protected]
  │   │ │       └── [email protected]
  │   │ ├─┬ [email protected]
  │   │ │ └── [email protected]
  │   │ └─┬ [email protected]
  │   │   └─┬ [email protected]
  │   │     ├── [email protected]
  │   │     ├── [email protected]
  │   │     └── [email protected]
  │   └─┬ [email protected]
  │     └── [email protected]
  └─┬ @firebase/[email protected]
    └─┬ [email protected]
      ├─┬ [email protected]
      │ └─┬ [email protected]
      │   ├── [email protected]
      │   ├── [email protected]
      │   └── [email protected]
      ├─┬ [email protected]
      │ ├── [email protected]
      │ ├── [email protected]
      │ ├─┬ [email protected]
      │ │ ├── [email protected]
      │ │ └── [email protected]  deduped
      │ └── [email protected]
      └─┬ [email protected]
        ├─┬ [email protected]
        │ └── [email protected]  deduped
        ├─┬ [email protected]
        │ ├── [email protected]
        │ ├─┬ [email protected]
        │ │ └─┬ [email protected]
        │ │   └── [email protected]
        │ ├─┬ [email protected]
        │ │ ├─┬ [email protected]
        │ │ │ └─┬ [email protected]
        │ │ │   └─┬ [email protected]
        │ │ │     ├─┬ [email protected]
        │ │ │     │ └── [email protected]  deduped
        │ │ │     ├─┬ [email protected]
        │ │ │     │ └── [email protected]  deduped
        │ │ │     └─┬ [email protected]
        │ │ │       ├─┬ [email protected]
        │ │ │       │ └── [email protected]
        │ │ │       └── [email protected]
        │ │ ├─┬ [email protected]
        │ │ │ └── [email protected]
        │ │ ├── [email protected]
        │ │ ├─┬ [email protected]
        │ │ │ └── [email protected]  deduped
        │ │ ├── [email protected]
        │ │ └─┬ [email protected]
        │ │   ├─┬ [email protected]
        │ │   │ ├─┬ [email protected]
        │ │   │ │ └── [email protected]  deduped
        │ │   │ └─┬ [email protected]
        │ │   │   └── [email protected]
        │ │   ├── [email protected]
        │ │   └─┬ [email protected]
        │ │     └── [email protected]  deduped
        │ ├─┬ [email protected]
        │ │ └── [email protected]  deduped
        │ └─┬ [email protected]
        │   └─┬ [email protected]
        │     ├── [email protected]
        │     └── [email protected]
        ├─┬ [email protected]
        │ ├── [email protected]  deduped
        │ └─┬ [email protected]
        │   ├── [email protected]
        │   ├── [email protected]
        │   └── [email protected]
        ├─┬ [email protected]
        │ └── [email protected]  deduped
        ├─┬ [email protected]
        │ └── [email protected]  deduped
        ├─┬ [email protected]
        │ └── [email protected]
        ├── [email protected]  deduped
        ├── [email protected]  deduped
        ├─┬ [email protected]
        │ └── [email protected]  deduped
        └─┬ [email protected]
          ├── [email protected]
          ├── [email protected]
          └── [email protected]

As you can see, all of these conflicts are getting pulled in due to grpc getting pulled in by @firebase/firestore and lcov-result-merger getting pulled in by @firebase/messaging.

The grpc package ultimately gets tree-shaken out on non-node platforms, so it really isn't a problem. However, since it's listed as a normal dependency, yarn can't know that it will ultimately get excluded from the build and so it gets included in the --flat resolution. I'm not sure what the best solution to this problem is, but I suspect the easiest thing would be to make it an optional dependency.

The lcov-result-merger package appears to have been added as part of this commit by mistake. It's only used as a cli tool during development and it's already included as a dev dependency at the root of the project. I removed it from @firebase/messaging and all the tests still pass.

Steps to reproduce:

#vue isn't necessary to reproduce this.  This was just one of the simplest demos I could find that actually imported firebase as an npm package 
git clone [email protected]:Q42/vuejs-firestore-demo.git 
cd vuejs-firestore-demo
npm upgrade firebase

# then, to see the issue before we get yarn involved
npm ls --prod minimist readable-stream isarray process-nextick-args string_decoder assert-plus extsprintf glob through2 vinyl clone clone-stats replace-ext glob-parent is-glob is-extglob kind-of is-number

# to recreate the issue itself, run
yarn install --prod --flat

Relevant Code:

see reproduction steps

@mikelehen
Copy link
Contributor

Making grpc an optionalDependency doesn't seem like a good solution to me, since it may result in the client being broken on node. We might be able to exclude it from the browser with something like this in package.json:

"browser": {
  ...
  "grpc": false
}

But I don't know if that helps the yarn flat issue or not...

Else, perhaps the correct solution is to work with grpc and other dependencies to update their dependencies to compatible versions... though I don't know how big of an undertaking that is.

@zevdg
Copy link
Contributor Author

zevdg commented Mar 5, 2018

Unfortunatly, browser isn't a standard field. Neither npm or yarn support it https://github.com/stereobooster/package.json#browser

Getting grpc to clean up it's dependencies would be nice, but expecting server-side modules like grpc to do work that really only benefits client-side projects is a hard sell. They would most likely respond with, why are you pulling grpc into a client-side dependency tree in the first place? Stop that! And I would agree with them. Server-side and client-side javascript are 2 totally different beasts and the real issue here is firestore trying to force them both into the same project.

Even if grpc agreed to do this work, there already are pull requests for grpc dependencies like https://github.com/substack/node-mkdirp/pull/124 that have been ignored for years.

IMO, large conditional dependencies like this are a code smell. Dependency management is hard enough for tooling to handle without libraries pulling in tons of code that a downstream developer knows that it will never use. In cases like this where the condition is always known in advance, simply having 2 separate packages that each only depend on what they actually need (in this case, firestore-web and firestore-node or something like that) is the simplest solution and the best one IMO. When that's not possible, the next best option is to require the calling code to inject the conditional dependency during initialization if they need it.

However, both of those would be an API change so I assume that ship has already sailed. 😞
Technically firestore is still beta, so maybe not? 🤞

Assuming the conditional grpc dep cannot be refactored out, and without a well supported "browser" build tag, optionalDependency appears to be the only other option. AFAICT, optional dependencies are still installed by default by both yarn and npm, so it this would only affect people who are using firestore on the server side AND are already using the --no-optional flag in their build for other reasons. They would just need to add grpc as a direct dependency of their project to resolve the issue. I wouldn't expect enough people to fall into this category for it to be a serious problem. The only other thing that optionalDependency does is to prevent the build from failing if grpc couldn't be installed for a more legitimate reason (like a network error). This isn't great, but it's still the lesser of 2 evils.

@mikelehen
Copy link
Contributor

The only other thing that optionalDependency does is to prevent the build from failing if grpc couldn't be installed for a more legitimate reason (like a network error)

That was my main concern. grpc can fail to install for a variety of reasons since it's based on a C core that must be compiled if there isn't a prebuilt binary for your platform. We've seen it fail before and if yarn / npm silently ignored this, it could lead to broken users and support headaches.

I'll let @jshcrowthe weigh in with his thoughts. My objection is largely in principle (grpc is not optional and node code will break if it's not installed). If we think the actual user experience is acceptable, then this change might be okay.

@zevdg
Copy link
Contributor Author

zevdg commented Mar 5, 2018

Eww, I see how that would be a problem. Frankly, I'm not particularly happy with the optional deps solution for other reasons I didn't go into.

I do hope the package splitting or dependency injection solutions are viable alternatives since yarn doesn't seem open to adding support for the browser field.

@jshcrowthe
Copy link
Contributor

jshcrowthe commented Mar 5, 2018

Hey @zevdg thanks a bunch for the PR here and trying to help us figure this out!

I agree with @mikelehen that moving grpc to an optionalDependency is probably not the right solution. It's important to note that grpc is not an optional dependency, it is a runtime dependency for Node and w/o it this package won't function in Node environments.

While trying to install the dependencies using the --flat flag will allow you to only include a single version of a given dependency, you assume the risk that any given resolution could break your dependencies. Just doing yarn install --prod will still give you much of the behavior you need (i.e. install deps, resolve all compatible version ranges to the same instance of a dependency), w/o the risk of breaking your dependencies by changing the versions out from under the authors intention.

For Node.js, multiple instances of a dep isn't a huge concern, as the dependency size isn't nearly as large of a factor (though, admittedly, it still has an impact). I think this is really more of a "the JS community ships isomorphic dependencies (with different assumptions about dependencies) to the Node Package Manager" problem, than a Firebase problem. An alternate solution could be to work w/ the yarn team to allow you to ignore specific dependencies (and their transitive deps) in --flat resolution. Or just ignore those dependencies entirely, if you know that the package is shipping some things that aren't needed for your target environment.

All that said, I think your best bet is to probably to avoid passing the --flat flag until yarn supports ignoring env specific deps, or until the community standardizes around a better solution for delineating between isomorphic/browser/node dependencies.

@zevdg
Copy link
Contributor Author

zevdg commented Mar 5, 2018

Thanks! I'd agree that yarn could certainly provide some additional fine tuning knobs around --flat.

As a side note, the polymer 3 preview currently requires the use of this --flat flag. So until this is resolved one way or another, firestore and polymer3 cannot be used together ☹️. The discussion about how tooling should handle polymer3 and flattening is ongoing over at package-community/discussions#2 so you might want to keep an eye on that. It'd be a shame if 2 of my favorite Google projects couldn't be used together.

@firebase firebase locked and limited conversation to collaborators Oct 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants