So, I recently just solved this issue at the company I work for using custom npm packages and the solution itself is actually very elegant and clean… it just takes time to get there.
We built our shared component library using ng-packagr and use it across multiple projects as well as using semantic versioning to control what updates get applied to projects (version locking). I really highly recommend it as it has been the best development experience that I’ve had so far with npm packages. We initially tried to go the npm link route but as you pointed out above, it doesn’t work very well and has quite a few issues.
I’ll try to give a quick write up of what I did and how I did it and got it working and hopefully it will help you get up and running quickly. Keep in mind this is how we set it up to work for us, so feel free to tweak it for your own personal needs.
ng new my-component-library
Then in the package.json
file, change the name of the package to be whatever you want. We used npm scope for our name to keep it specific to our company, ie. @company
or @company/ionic
but you can use whatever you want to, scope or not.
Next, cd into your project directory and install the following packages.
npm install ng-packagr --save-dev
npm install standard-version --save-dev
I recommend standard-version for version control, but you don’t need to use it or could use something else of your choice.
Update package.json to add the following lines to the scripts
section.
"packagr": "ng-packagr -p ng-package.json",
"release": "standard-version",
"publish:latest": "npm run release && npm run packagr && cd dist && npm pack && cd .. && npm publish dist && git push --follow-tags origin master",
as well, add a new key to the package.json
with the following code:
"standard-version": {},
and finally remove "private": true
from the file if you plan on publishing to a registry.
Next you’ll need to create two files at the root directory,
ng-package.json
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"dest": "dist",
"workingDirectory": ".ng_build",
"lib": {
"entryFile": "public_api.ts"
}
}
public_api.ts
export { MyLibraryModule } from './library/public_api';
You’ll notice that the public api file is exporting from a file that doesn’t exist. We’ll want to create that file. The reason why we developed our library this way is to support multiple entry points. Similar to how with angular projects, you can import @angular/core
or @angular/common
but both are in the same project, with this structure you can have @company/http
or @company/forms
. The project structure should look like this (shortened):
library/
|- src/
| |- library.module.ts
|- public_api.ts
|- package.json
|- README.md
src/
|- Angular project files
ng-package.json
package.json
public_api.ts
README.md
The library/package.json
file should look like this:
{
"ngPackage": {
"lib": {
"entryFile": "./public_api.ts"
}
}
}
The library/public_api.ts
file should export anything you want to make available to import in other projects.
The angular project files src directory can be used like a normal Angular project that can import the different component modules from the library for testing and development purposes.
At this point, you can actually be done. Run npm run packagr
which will generate the dist folder, cd into the dist folder and then run npm pack
to create a tarball (.tgz
). This tarball can then be distributed and anyone can install it into any project using npm install ./path/to/tarball-0.0.0.tgz
. then to use it, you would import files from company/library
. For every folder that you create at the top level that matches the structure of the library
directory, it should create an entry point that you can use.
If you want to continue with publishing to a registry, you can use the npm run publish:stable
command, or modify that to fit your needs. If you don’t want to publish to the public registry, then you can either use npm private registries, or host your own (we use verdaccio to host our own private registry that only our projects have access to).
Hopefully this helps and gets you up and running with a simpler solution for sharing modules between projects.
Edit: One thing I just remembered, best practice is to list dependencies for your library as peerDependencies
in your package.json
file rather than as dependencies
so that you avoid the node_modules conflict that you mentioned above. This means that it is up to the project installing the library to provide the needed dependencies, like @angular/core or ionic-angular.