‹div›RIOTS logo
TODO

TODO


Inheriting another workspace’s dependencies

We talked a bit about phantom dependencies - pnpm has this good practice in mind that forces you to explicitly list the dependencies you want to use in your source code.

However we ran into some problems because we have the following structure in our monorepo:

.
├── ...
├── packages
│   ├── ...
│   ├── functions
│   ├── shared
│   ├── ...
├── ...

shared is a project where we put code we want to use both in functions, a Firebase functions project, and in some other projects. functions declares a dependency to shared:

> cat packages/functions/package.json
{
  ...
  dependencies: {
    ...
    "shared": "workspace:1.0.0",
    ...
  }
  ...

In functions, we bundle the source code of both functions and shared using esbuild, and we bundle it inside packages/​functions/​dist/​index.js. We however do not bundle shared dependencies, and even if the functions declares a dependency to shared, its node_modules ends up being

> tree -L 1 packages/functions/node_modules
packages/functions/node_modules/
├── ...
├── shared -> ../../../shared
├── ...

So none of shared’s dependencies can be found by the bundle located in packages/​functions/​dist/​index.js

This kind of goes against pnpm’s explicit dependency, but we just did not want to have to keep shared and functions dependencies up-to-date. Fortunately, there’s a way to fix it: .pnpmfile.cjs or more specifically the readPackage hook.

What we do there, is simply to copy all of shared’s dependencies into functions when it’s evaluated:

const fs = require('fs');
const path = require('path');

const sharedManifest = JSON.parse(
  fs.readFileSync(
    path.resolve(__dirname, path.join('packages/shared', 'package.json')),
    'utf-8'
  )
);

function readPackage(pkg, context) {
  if (pkg.name === 'functions') {
    Object.entries(sharedManifest.dependencies).forEach(([k, v]) => {
      if (pkg.dependencies[k])
        context.log(
          `Dependency ${k} is defined in ${pkg.name} but it was already defined in shared`
        );
      pkg.dependencies[k] = v;
    });
  }
}

module.exports = {
  hooks: {
    readPackage,
  },
};

This effectively copies all the direct dependencies of shared into functions, printing a message in case a dependency was already there 🎁