node.jstypescriptnpmnode-modulesyarn-workspaces

Creating an NPM package from a yarn workspace monorepo with internal dependency


I'm using yarn workspace to develop a library that will be published on NPM. The library is dependent on a private core package in the same workspace. My understanding is that the workspace should bundle the dependency from the workspace into the library. The core package is listed as a devDependency of the library

I think I have up the workspace and packages correctly according to the yarn workspaces docs

I've recreated my issue as an example. Here is my repo, and here's the package on the NPM registry

When I install the library in an application I get Error: Module not found: Error: Can't resolve 'jakelo123-core' in '/Users/jakeloew/chalk/fusionauth/angular-quickstart/complete-application/node_modules/jakelo123-user-lib/dist' -- the module containing the internal dependency is not found.


Solution

  • My understanding is that the workspace should bundle the dependency from the workspace into the library.

    This is not correct. "bundling" is not provided by either TS or the package manager. If you need it, it is something you need to include and configure.

    Workspaces are just a nice way of developing across & orchestrating multiple packages in a source tree without needing to manually symlink things and without awkward dependency management. They aren't a compiler of any kind.

    The core package is listed as a devDependency of the library

    Since there is no bundling as per above, and the core dep is in devDependencies, the core dep is not fetched when the one that depends on it is installed. So it is not available.

    Usually, such bundling is a concern of the library consumer. That means you'd build and publish both of your packages. The core one would be declared under dependencies and not devDependencies. The consumer will usually have app-level bundling via Vite/Webpack/Rollup or whatever they are using. Or they might not, that's up to them.

    However, you mentioned this module is "private", which is of interest. It's quite normal for projects to be made of many published packages from the same author, even though many of them are unlikely to be used directly and may not even have package-level documentation. This is common, and often simply not a worry. Often in this scenario, they are published under a common package scope, which helps identify them as related.

    However, if you really do not wish for that package to be published, you would need a bundling step during build time (before publishing) on your side in the top-level module. You could use Rollup, esbuild or one of the many others.

    Note, this would by default bundle every other third-party dep as well, which is not so great for the consumer (duplicate deps, large bundle size, hard-to-fix security issues). But you can configure this by using the bundler "externals" features (esbuild, Rollup).

    Another thing you should think about is if this package is private, is it truly a reusable unit? Presumably, it is if you opted to separate it. If you end up in future with more than one module that you want to publish that relies on this core module, and you bundle in all of those places, any consuming package manager is unable to deduplicate that core dep if they are using more than one of those top level packages. And you'd have to publish all of them every time the core changes, which is bothersome and a lot of work for what is quite possibly a worse overall result.

    This is why you'd usually simply publish the core module as well, even if it is an internal detail. But you could have a very good reason for wanting the bundling despite this (and there are some), so the question remains legitimate. But you should think hard about what you want to achieve with it being "truly" private.