angular-package-builder
v2.0.0
Published
Packages your Angular library based on the Angular Package Format.
Downloads
6
Maintainers
Readme
angular-package-builder
Packages your Angular 4+ library based on the Angular Package Format.
What it does
These days, setting up build chains for frontend projects requires lots of knowledge and time. When working with Angular, in particular, there is a fair amount of things to do in order to get an Angular library published just right.
The Angular Package Builder is here to help! Once set up, this NodeJS-based command line tool will build your Angular libraries with a single command, allowing developers to focus on the important things - developing!
Features include:
- :pushpin: Support for primary and (multiple) secondary entry points
- :gift: Support for multiple libraries (e.g. in a monorepo)
- :page_facing_up: Inlining of external resources, such as templates (HTML) and styles (CSS, SASS)
- :hammer: Custom configurations (Angular compiler options, TypeScipt compiler options, external dependencies)
The result is a package, following the official Angular Package Format:
- :green_book: JavaScript build (ES2015, ES5)
- :orange_book: JavaScript bundles (flat ES2015, flat ES5, UMD)
- :blue_book: TypeScript type definition files
- :closed_book: Angular AoT metadata file
- :notebook_with_decorative_cover: package.json file with references to entry files
How to install
You can get the angular-package-builder via npm by adding it as a new devDependency to your package.json
file and running
npm install
. Alternatively, run the following command:
npm install angular-package-builder --save-dev
Angular compatibility
The following lists the Angular versions supported by the Angular Package Builder. The table also mentions the TypeScript and RxJS versions which are officially supported by each Angular version. Diverging from this matrix is surely possible yet might lead to unexpected issues. The last column defines the minimal required NodeJS version.
| Angular | TypeScript | RxJS | NodeJS |
| --------------------------------------- | ----------------------- | ----- | ---------- |
| 4.0.x
4.1.x
4.2.x
4.3.x
4.4.x
| 2.1.x
2.2.x
2.3.x
| 5.x
| >= 7.6.0
|
| 5.0.x
| 2.4.x
| 5.x
| >= 7.6.0
|
| 5.1.x
| 2.4.x
2.5.x
| 5.x
| >= 7.6.0
|
| 5.2.x
| 2.4.x
2.5.x
2.6.x
| 5.x
| >= 7.6.0
|
| 6.0.x
| 2.7.x
| 6.x
| >= 8.0.0
|
Angular 2 is not supported. Angular versions newer than
6.0.x
might work, yet have not not been tested.
How to use
In most cases, integrating angular-package-builder into a project is very straightforward.
The Angular Package Builder only builds libraries from an Angular / JavaScript perspective. It's possible that you might have to setup a few extra build steps, for instance in order to compile global SASS, or copy assets / other files.
Step 1: Create .angular-package.json
file
Now, every library requires a .angular-package.json
file to be present, placed directly next to the package.json
file of that library.
Within that .angular-package.json
file, you can place the build onfiguration for your library.
A minimal configuration looks like the following:
{
"$schema": "./node_modules/angular-package-builder/angular-package.schema.json",
"entryFile": "./index.ts",
"outDir": "./dist"
}
The two options seen above are also the only required ones:
entryFile
is the relative path to the primary entry file- Usually, entry files are named
index.ts
- All further files of the library must be within the same folder, or some place deeper in the directory
- Usually, entry files are named
outDir
is the relative path to the build output folder- Usually, the build output folder is named
dist
- Don't forget to add the
outDir
path to your.gitignore
file
- Usually, the build output folder is named
Directory structure
The following directory structure is recommended:
── dist/ // Output
└── ...
── src/ // Source
└── ...
── .angular-package.json // Build config
── index.ts // Entry file
── package.json // Package
Note: The build process will create additional files at the root level (where the entry files is placed). Thus, it's highly recommended to place all other files in a subfolder - usually that's the
src
folder.
Secondary entry points
Angular, for instance, has packages with multiple entry points: @angular/core
as the primary, and @angular/core/testing
as the (here
only) secondary. Within the .angular-package.json
file, you can define any number of secondary entry points using the secondaryEntries
option. For instance:
{
"$schema": "./node_modules/angular-package-builder/angular-package.schema.json",
"entryFile": "./index.ts",
"outDir": "./dist",
"secondaryEntries": [
{
"entryFile": "./testing/index.ts"
}
]
}
Step 2: Add build script to package.json
Now, run angular-package-builder within one of your package.json
scripts. The command accepts an unordered list of paths to
.angular-package.json
files as parameters. For instance:
{
"scripts": {
"build": "angular-package-builder ./my-library/.angular-package.json"
}
}
Multiple libraries
Angular, again, consists of multiple packages, all united in a single Git repository (called monorepo). The Angular Package Builder is
able to build multiple libraries using a single command. Building more libraries means adding more .angular-package.json
files to the
corresponding npm script. For example:
{
"scripts": {
"build": "angular-package-builder ./lib-one/.angular-package.json ./lib-two/.angular-package.json"
}
}
The order of the parameters does not matter as the Angular Package Builder will derive the build order independently.
Advanced configuration
Usually, configuring the entryFile
and outDir
should be sufficient for most libraries. For more advanced use cases or requirements, you
can extend the build configuration in your .angular-package.json
file(s).
TypeScript compiler options
One of the things you might want to configure specifically for your project is TypeScript. Popular options include strictNullChecks
,
skipLibCheck
and allowSyntheticDefaultImports
. For instance:
{
"typescriptCompilerOptions": {
"strictNullChecks": true
}
}
See the TypeScript Compiler Options Documentation for the full list of available options.
The following options cannot be changed:
declaration
,emitDecoratorMetadata
,experimentalDecorators
,module
,moduleResolution
,newLine
,outDir
,rootDir
,sourceRoot
andtarget
Angular compiler options
Furthermore, you might also decide to configure the Angular compiler differently. Common options are annotateForClosureCompiler
,
preserveWhitespaces
and strictMetadataEmit
. For instance:
{
"angularCompilerOptions": {
"annotateForClosureCompiler": false
}
}
The following options cannot be changed:
flatModuleId
,flatModuleOutFile
andskipTemplateCodegen
Dependencies
By default, the Angular Package Builder will identify your libraries' dependencies automatically. If, for some reason, a dependency is
missing or you want to overwrite a dependency definition, you can declare them in the form of package -> global constant
. For instance:
{
"dependencies": {
"@ngx-translate/core": "ngxTranslate.core"
}
}
Known pitfalls with solutions
There are quite a few pitfalls when packaging an Angular library. Most of them are all but obvious, and the fix is not always clear. The following is a collection of known pitfally, plus tips on how to solve them.
Feel free to extend this list by creating an issue!
Caution with barrels
Usually, libraries are built in a way that allows us to import them from a single source (normally the module name). This is achieved by
re-exporting the implementation (spread accross multiple files) with a so-called
Barrel (normally index.ts
).
Now, issues might occur when - somewhere within the library - two barrels meet each other. Funnily enough, should such a constellation lead to any issues, it won't be appearant right away: Chances are good that the Angular Package Builder will succeed, and the compilation output might also look correct. At the latest, when trying to import the library into an Angular application, an error will be thrown (something like "injected dependencies cannot be resolved").
Solution
We recommend to only use a single barrel / index.ts
file at the root of you library, re-exporting all public functionality from that
single place.
Forbidden JSDoc tags
The usage of type-related JSDoc tags / information within JSdoc tags is disallowed, reason being that the TypeScript syntax already exposes this kind of information. Forbidden are (amongst other things):
- type information in parameter tags (e.g.
@param {string} myOption
) - type-related tags on variables, functions, classes (e.g.
@type
,@constructor
,@function
,@class
) - tags regarding visibility (e.g.
@private
,@public
) - further redundant tags such as
@static
,@extends
,@implements
The full list of allowed / disallowed JSDoc tags can be found in the tsickle source.
If any of those tags are being used anyway, the Angular Compiler (tsickle
to be specific) will complain:
Solution
Preferably, remove all redundant JSDoc tags until the Angular Compiler is happy. As an alternative, one could also set the
annotateForClosureCompiler
option in the angularCompilerOptions
to false
- but it's not recommended. Read the Angular annotateForClosureCompiler documentation for further information.
Angular metadata generation errors
Especially when writing custom factories for NgModules
, one might run into Angular metadata generation issues, usually resulting in errors
like Lambda not supported
or Reference to a non-exported function
.
Solution
This issue can be solved by extracing the mentioned arrow function into a separate function, and making sure that it's exported.
Also see this Angular issue on GitHub.
Angular metadata validation errors
Rarely, and only when using arrow functions within static classes and / or methods, an error like Function call not supported
might occur.
Solution
This issue can be solved in two ways:
- Prefered: Add the
@dynamic
JSdoc tag to the comment describing the static method (or, if this should not work, the class containing the static method). Then, the Angular Compiler will make an exception for this piece of code when validating the generated metadata. - Alternative: Set the
strictMetadataEmit
option in theangularCompilerOptions
object tofalse
. Then, however, other metadata validation issues will no longer be visible. Read the Angular strictMetadataEmit documentation for further information.
Also see this Angular issue on GitHub.
Synthetic imports
Often, we integrate long-existing libraries into our Angular projects. Moment.js, for instance, is one of the libraries used when working with dates. Due to its age, however, it's still published as a single-entry ES5 module - which means people usually write the following TypeScript code to import the library:
import * as moment from 'moment';
When trying to package an Angular library using the import statement above, an error will be thrown:
Solution
The solution to this problem is called synthetic default imports, a technique which does allow TypeScript to make default import from modules that come without a default export.
First, enable synthetic default import support in the TypeScript configuration by adding the following line to the
typescriptCompilerOptions
within your .angular-package.json
file:
"typescriptCompilerOptions": {
"allowSyntheticDefaultImports": true
}
Then, change the affected import statements to default import statements. For instance:
import moment from 'moment';
Alternatively, you could also consider Moment ES6 - it wraps around Moment.js and exports it in an ES6-compatible (and thus TypeScript-compatible) way.
Also see this Moment.js issue on GitHub.
Creator
Dominique Müller
- E-Mail: [email protected]
- Website: www.devdom.io
- Twitter: @itsdevdom