Building reusable Angular NPM packages
by Magnus Stuhr — Principal Engineer
My previous blog post argued that one of the most important principles of programming is to avoid code-duplication, and rather reuse existing code for the same functionality — it aimed at providing insights into how anyone could implement their own code-sharing strategy. This blog post will further elaborate on this by showing a concrete example of how you in no time can develop and publish your own Angular NPM packages that can be reused across your different applications.
The first thing we need to do when publishing our own NPM packages, is to make sure we have an NPM registry to push our packages to. If you want to push your package to a public repository, you do not need to set up any authentication. However, if you have a private NPM registry of some sort, you need to follow the authentication setup instructions for that repository, as we will not go into details about that in this post. Either way, you should end up with a .npmrc file in the root folder of your project, containing a list of the registries your application should retrieve packages from:
.npmrc
@<your_registry_name>:registry=<your_registry_url>
always-auth=true
Building and publishing NPM packages
This section includes a step-by-step guide on how to build and publish your own NPM packages.
First of all, when developing NPM packages we usually want to include peer-dependencies and dev-dependencies instead of the regular direct dependencies. Peer-dependencies are the packages required for including our self-made NPM package, and dev-dependencies are the dependencies used for developing our NPM package. We should include regular dependencies for libraries that are only required in our NPM package, and that do not need to be referred to outside the package-scope. See the package.json dependencies for the full documentation.
1 All components and services should have their own “index.ts” file
When coding NPM packages, make sure to export your code correctly, in order for it to be used properly from the outside. All of our components and services should have their own index.ts file that export the functionality.
<component/service>/index.ts
export * from '<your_component/service_name.component/service/module>';
Example:
export * from './dialog-provider.module';
export * from './dialog-provider.service';
2 Your app module should have an “index.ts” file
Finally we need to create an entrypoint where the functionality of all our modules are included:
index.ts
export * from '<your_component/service_paths>'; (no file extension declaration after path)
Example:
export * from './dialog-provider';
3 Add Gulp-integration for proper building
When building our package, we need to inline the HTML and CSS into Angular component decorators.
3.1 Install the Gulp plugin
npm install gulp gulp-inline-ng2-template --save-dev
3.2 Add a new Gulp task
Make sure to include your own relative paths instead of the paths below, if your project structure is different.
const inlineNg2Template = require('gulp-inline-ng2-template');
gulp.task('inline-build-templates', () => {
return gulp.src(['./src/app/**/*.ts', '!./src/app/**/**.spec.ts', '!./src/app/app.*'])
.pipe(inlineNg2Template({
target: 'es5',
useRelativePaths: true,
}))
.pipe(gulp.dest('./build'));
});
4 Add build steps in the “scripts” object in your package.json
"scripts": {
...
"inline-build-templates": "gulp inline-build-templates",
"ngc-build": "ngc -p ./tsconfig.lib.json
...
}
5 Install typings
We need to install the typings necessary, in order for the package to build without errors.
npm install --save-dev @types/core-js
5.1 Make sure you have a “tsconfig.lib.json” file in your root folder
tsconfig.lib.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"declaration": true,
"outDir": "./lib",
"stripInternal": true,
"lib": [
"es6",
"dom"
]
},
"include": [
"./build/*"
],
"files": [
"./node_modules/@types/core-js/index.d.ts"
],
"angularCompilerOptions": {
"skipTemplateCodegen": true
}
}
5.2 Add TypeScript typings and entrypoint for our package
package.json
...
"main": "./lib/index.js",
"typings": "./lib/index.d.ts"
...
6 Add the “lib” folder to the “files” array in your package.json
The “lib” folder is the raw output of our built package.
"files": [
...
"lib"
...
]
7 Add rimraf integration
Add rimraf-integration in order to cleardown existing files for each build.
7.1 Install rimraf
We want to make sure no old files linger for our newer builds.
npm install rimraf --save-dev
7.2 Add the rimraf build command to the “scripts” object in your package.json file
"scripts": {
...
"cleardown": "rimraf build lib",
"package": "npm run cleardown && npm run inline-build-templates && npm run ngc-build"
...
}
8 Build your package
npm run package
9 Push your package to the NPM registry
For your final release (i.e. v1.0.0):
npm publish
For beta-releases (i.e. v1.0.0-beta1):
npm publish --tag beta
Congratulations, your package should now have been pushed to the NPM registry!
Magnus Stuhr
Principal engineer / head of the Data Science community in Computas, and organizer for the Semantic Technologies group on meetup.com. Above all loves to code.