I have developed a solution and wrote an article about this. Below is an
excerpt, but here is the full
article
The problem with Firebase
When deploying to Firebase it wants to upload a folder just like a
traditional single package repository, containing the source files
together with a manifest file declaring its external dependencies.
After receiving the files in its cloud deployment pipeline, it then
detects the package manager and runs an install and build.
In a monorepo, and especially a private one, your Firebase code
typically depend on one or more shared packages from the same
repository, for which you have no desire to publish them anywhere.
Once Firebase tries to look up those dependencies in the cloud they
can not be found and deployment fails.
Hacking your way out
Using a bundler
In order to solve this you could try to use a bundler like Webpack to
combine your Firebase code with the shared packages code and then
remove those packages from the package.json manifest that is being
sent to Firebase, so it doesn’t know these packages even existed.
Unfortunately, this strategy quickly becomes problematic…
If the shared packages themselves do not bundle all of their
dependencies in their output, Firebase doesn’t know what the shared
code depends on, because you are not including or installing those
manifests.
You could try to bundle everything then, but if your shared package
depends on things your Firebase package also depends on, you now have
one part of your code running an internally bundled copy of a
dependency and the other part using that same dependency from a
different location installed by the package manager.
Also, some libraries really don’t like to be bundled, and in my
experience that includes the Firebase and Google client libraries. You
will quickly find yourself trying to externalize things via the
bundler settings in order get thing to work.
And even if you managed to make all this work, you are probably
creating large bundles which could then lead to problems with the
cold-start times of your cloud functions.
Not exactly a reliable or scalable solution.
Packing and linking local dependencies
An arguably more elegant approach involves packing the local
dependencies into a tarball (similar to how a package would be
published to NPM), and copying the results to the build output before
linking them in an altered manifest file.
This could work quite nicely, as it basically resembles how your
Firebase code would have worked if these packages were installed from
an external domain.
Whether you’re doing this manually, or write a shell script to handle
things, it still feels very cumbersome and fragile to me, but I think
it is a viable workaround if your local dependencies are simple.
However, this approach quickly becomes hairy once you have shared
packages depending on other shared packages, because then you’ll have
have multiple levels of things to pack and adapt.
My solution
I have created
isolate-package. The name
is generic because it doesn’t contain anything specific to Firebase,
although I currently don’t know of any other use-cases for isolated
output.
It takes a similar approach to what is described earlier in packing
and linking dependencies, but does so in a more sophisticated way. It
is designed to handle different setups and package managers and it
completely hides the complexity from the user.
The isolate binary it exposes can simply be added to the Firebase
predeploy hook, and that’s pretty much it!
This also allows you to deploy to Firebase from multiple different
packages and keep the configuration co-located instead of littering
the monorepo root directory.
It should be zero-config for the vast majority of use-cases, and is
designed to be compatible with all package managers.