I'm trying to create a mono repository to host multiple small packages that I plan to deploy on npm. I use Rollup for the bundling part.
After many hours watching what others do, searching on the internet and experimenting, I've reached a point where I'm stuck and little push in the right direction would be very much appreciated.
I've created a minimalist demo project that I've hosted on GitHub so it's easy to experiment with. You can find it here: https://github.com/Stnaire/my-lib
In the repository you'll find 3 packages in the packages directory:
config: contains a SharedConfiguration servicecontainer: a wrapper aroundInversifyto have a statically accessible container with helper methodsstorage: a package containing a StorageService (normally for writing in the local storage, cookies, etc) and a VarHolder helper which is just a memory storage.
In each package there is a package.json (defining the npm package parameters) and a tsconfig.json for the build.
What I'm trying to do is the have a npm package for each of the packages, each allowing for the following types of usages:
- With a bundler in a TypeScript environment
import { SharedConfiguration } from '@my-lib/config';
// or ideally:
import { SharedConfiguration } from '@my-lib/config/shared-configuration';
// this way only this dependency is included, not the whole `config` pacakge
- With a bundler in a JavaScript environment
var SharedConfiguration = require('@my-lib/config');
// Or like above, ideally:
var SharedConfiguration = require('@my-lib/config/shared-configuration');
- By including the output JS file in the browser
<script src="my-lib-config.umd.js"></script>
<script>
MyLibConfig.SharedConfiguration.get(...);
</script>
What I tried
I've created two branches in the demo repository, corresponding to two strategies.
First strategy: create aliases (branch detailed-modules-resolution)
In the tsconfig.json, I do:
{
"paths": {
"@my-lib/config/*": ["packages/config/src/*"],
"@my-lib/container/*": ["packages/container/src/*"],
"@my-lib/storage/*": ["packages/storage/src/*"]
}
}
This way I can import precisely what I need:
import { SharedConfiguration } from '@my-lib/config/shared-configuration';
And because the aliases also correspond to the folder structure in node_modules, it should work for the end user with a bundler as well.
But this way I get warnings when building as UMD:
No name was provided for external module '@my-lib/config/shared-configuration' in output.globals – guessing 'sharedConfiguration'
No name was provided for external module '@my-lib/container/container' in output.globals – guessing 'container'
Creating a global alias for each import is out of question, thus the second strategy.
Second strategy: centralize all public exports (branch centralized-imports)
So the idea is simply to export everthing the package wants to expose to other packages in the index.ts:
// packages/config/index.ts
export * from './shared-configuration';
Then in the build script, I do this:
// scripts/build/builds.js
/**
* Define the mapping of externals.
*/
export const Globals = {
'inversify': 'Inversify'
};
for (const build of Object.keys(Builds)) {
Globals[`@my-lib/${Builds[build].package}`] = Builds[build].moduleName;
}
Wihch creates the following object:
{
'inversify': 'Inversify',
'@my-lib/config': 'MyLibConfig',
'@my-lib/container': 'MyLibContainer',
'@my-lib/storage': 'MyLibStorage',
}
This way umd builds are working.
But there is two main drawbacks:
You have very little control on what you import. You import a whole package or nothing. And if the package depends on other packages you can quickly import thousands of lines for a little service.
This creates circular dependencies, as in my example project.
SharedConfigurationusesVarHolderwhich is in the @my-lib/storage. But this package also contain aStorageServicewhich usesSharedConfiguration, creating a circular dependency because all the imports are based on theindex.ts: @my-lib/config => @my-lib/storage => @my-lib/config.
I thought about using one strategy or the other depending if I build in umd or not, but it feels wrong.
It must be simplier way to handle all of this.
Thank you very much for reading all this.