Angular Package Format
Angular Package Format
0
authors: iminar@ & jasonaden@, last update: 2019-02-20, status: implemented
doc url: h
ttps://goo.gl/jB3GVv, edit/comment url: https://goo.gl/NGBfjF
previous version archive: v4, v5, v6
This document describes the structure and format of the Angular framework packages
currently available on npm. This format applies to packages distributing Angular
components (like Angular Material) as well as the core framework packages published
under the @angular namespace, such as @ angular/core and @angular/forms.
The format described here uses a distinct file layout and metadata configuration that
enables a package to work seamlessly under most common scenarios where Angular is
used, and makes it compatible with the tooling offered by the Angular team and the
community itself. For that reason, third-party library developers are also strongly
encouraged to follow the same structure.
The versioning of the format is aligned with the versioning of Angular itself and we expect
the format to evolve in forward-compatible way to support the needs of the Angular
component and tooling ecosystem.
File layout
This is an abbreviated version of the @angular/core package with explanation of the
purpose of various files.
Note: in APF v6 and before, each entry point would have a `src` directory next to the .d.ts entry
point. This is still allowed in v8, but we now prefer to run the .d.ts bundler tool from
https://api-extractor.com/ so that the entire API appears in a single file. This avoids users finding
deep-import paths offered by their editor and accidentally importing private symbols from them.
This package layout allows us to support the following usage-scenarios and environments:
Build / Bundler / Module Primary Entry Point resolves Secondary Entry Points resolves
Consumer Format to to
Plunker / Fiddle / UMD Requires manual resolution by Requires manual resolution by the
ES5 / script tag the developer to: developer to:
README.md
The readme file is used to display marked up description of a package on npm and github.
Example readme content of @angular/core package:
Angular
=======
License: MIT
Primary Entry-point
Primary entry point of the package is the module with module id matching the name of the
package (e.g. for "@angular/core" package, the import from the primary entry-point looks
like: import {Component, ...} from '@angular/core').
The primary entry-point is configured primarily via the package.json in the package root via
the following properties:
{
"name": "@angular/core",
"module": "./fesm5/core.js",
"es2015": "./fesm2015/core.js",
"esm5": "./esm5/core.js",
"esm2015": "./esm2015/core.js",
"fesm5": "./fesm5/core.js",
"fesm2015": "./fesm2015/core.js",
"main": "bundles/core.umd.js",
"typings": "core.d.ts",
"sideEffects": false
}
Property Name Purpose
main Node.js
sideEffects webpack v4+ specific flag to enable advanced optimizations and code
splitting (see note)
Secondary Entry-point
Besides the primary entry point, a package can contain zero or more secondary entry
points (e.g. @angular/core/testing). These contain symbols that we don't want to group
together with the symbols in the main entry point for two reasons:
1. users typically perceive them as distinct from the main group of symbols, and if they
were pertinent to the main group of symbols they would have already been there.
2. the symbols in the secondary group are typically only used in a certain scenario (e.g.
when writing and running tests). My not including these symbols in the main
entry-point we reduce the chance of them being accidentally used incorrectly (e.g.
using testing mocks in production code).
Module ID of an import for a secondary entry point directs a module loader to a directory
by the secondary entry point’s name. For instance, “@angular/core/testing” resolves to a
directory by the same name, “@angular/core/testing”. This directory contains a
package.json file that directs the loader to the correct location for what it’s looking for. This
allows us to create multiple entry points within a single package.
The contents of the package.json for the secondary entry point can be as simple as:
{
"name": "@angular/core/testing",
"module": "../fesm5/testing.js",
"es2015": "../fesm2015/testing.js",
"esm5": "../esm5/testing.js",
"esm2015": "../esm2015/testing.js",
"fesm5": "../fesm5/testing.js",
"fesm2015": "../fesm2015/testing.js",
"main": "../bundles/core-testing.umd.js",
"typings": "./testing.d.ts",
"sideEffects": false
}
This is an example of @angular/core/testing/package.json that simply redirects
@angular/core/testing imports to the appropriate bundle, flat module, or typings.
ESM5:
{
"compilerOptions": {
...
"declaration": true,
"module": "es2015",
"target": "es5"
},
"angularCompilerOptions": {
"strictMetadataEmit": true,
"skipTemplateCodegen": true,
"flatModuleOutFile": "my-ui-lib.js",
"flatModuleId": "my-ui-lib",
"annotateForClosureCompiler": true
}
}
ESM2015:
{
"compilerOptions": {
...
"declaration": true,
"module": "es2015",
"target": "es2015"
},
"angularCompilerOptions": {
"strictMetadataEmit": true,
"skipTemplateCodegen": true,
"flatModuleOutFile": "my-ui-lib.js",
"flatModuleId": "my-ui-lib",
"annotateForClosureCompiler": true
}
}
Optimizations
Flattening of ES Modules
We strongly recommend that you optimize the build artifacts before publishing your build
artifacts to npm, by flattening the ES Modules. This significantly reduces the build time of
Angular applications as well as download and parse time of the final application bundle.
Please check out the excellent post "The cost of small modules" by Nolan Lawson.
Angular compiler has support for generating index ES module files that can then be used to
flattened module using tools like Rollup, resulting in a file format we call Flattened ES
Module or FESM.
FESM is a file format created by flattening all ES Modules accessible from an entry-point
into a single ES Module. It’s formed by following all imports from a package and copying
that code into a single file while preserving all public ES exports and removing all private
imports.
The shortened name “FESM” (pronounced “phesom”) can have a number after it such as
“FESM5” or “FESM2015”. The number refers to the language level of the JavaScript inside
the module. So a FESM5 file would be ESM+ES5 (import/export statements and ES5 source
code).
To generate a flattened ES Module index file, use the following configuration options in
your t sconfig.json file:
{
"compilerOptions": {
...
"module": "es2015",
"target": "es5", // for FESM5, or "es2015" for FESM15
...
},
"angularCompilerOptions": {
...
"flatModuleOutFile": "my-ui-lib.js",
"flatModuleId": "my-ui-lib"
}
}
Once the index file (e.g. my-ui-lib.js) is generated by ngc, bundlers and optimizers like
Rollup can be used to to produce the flattened ESM file.
Component libraries are typically implemented using stylesheets and html templates
stored in separate files. While it's not required, we suggest that component authors inline
the templates and stylesheets into their FESM files as well as * .metadata.json files by
replacing the s tyleUrls and templateUrl with styles and template metadata properties
respectively. This simplifies consumption of the components by application developers.
webpack v4 "sideEffects": false
As of webpack v4, packages that contain a special property called "sideEffects" set to
false in their package.json, will be processed by webpack more aggressively than those that
don't. The end result of these optimizations should be smaller bundle size and better code
distribution in bundle chunks after code-splitting. This optimization can break your code if
it contains non-local side-effects - this is however not common in Angular applications and
it's a usually a sign of bad design. Our recommendation is for all packages to claim the
side-effect free status by setting the s ideEffects property to false, and that developers
follow the Angular Style Guide which naturally results in code without non-local
side-effects.
More info: w ebpack docs on side-effects
ES2015 Language Level (experimental)
While the most commonly used build tools today (e.g. WebPack and Uglify) expect the input
coming from n ode_modules/ to be transpiled down to ES5. Now that all evergreen
browsers natively support ES2015 (ES6), we encourage that the whole front-end ecosystem
transitions over to distributing JS code at the ES2015 language level. Doing so will simplify
the build process and will enable new optimizations resulting in better optimizations during
packaging and minification. For this reason we encourage component authors to publish
their libraries in a way where both ES5 and ES2015 language levels are supported.
The "es2015" property in package.json can be used to point tools with ES2015 support to
the right file in your package.
Examples
● @angular/core package
● @angular/material package
● jasonaden/simple-ui-lib
● filipesilva/angular-quickstart-lib
Definition of Terms
The following terms are used throughout this document very intentionally. In this section
we define all of them to provide additional clarity.
Package - the smallest set of files that are published to NPM and installed together, for
example @angular/core. This package includes a manifest called package.json, compiled
source code, typescript definition files, source maps, metadata, etc. The package is installed
with npm install @angular/core.
Symbol - a class, function, constant or variable contained in a module and optionally made
visible to the external world via a module export.
Module - Short for ECMAScript Modules. A file containing statements that import and
export symbols. This is identical to the definition of modules in the ECMAScript spec.
ESM - short for ECMAScript Modules (see above).
FESM - short for Flattened ES Modules and consists of a file format created by flattening all
ES Modules accessible from an entry-point into a single ES Module.
Module ID - the identifier of a module used in the import statements, e.g. "@angular/core".
The ID often maps directly to a path on the filesystem, but this is not always the case due to
various module resolution strategies.
Module Resolution Strategy - algorithm used to convert Module IDs to paths on the
filesystem. Node.js has one that is well specified a nd widely used, TypeScript supports
several module resolution strategies, Closure has yet another strategy.
Module Format - specification of the module syntax that covers at minimum the syntax for
the importing and exporting from a file. Common module formats are CommonJS (CJS,
typically used for Node.js applications) or ECMAScript Modules (ESM). The module format
indicates only the packaging of the individual modules, but not the JavaScript language
features used to make up the module content. Because of this, the Angular team often
uses the language level specifier as a suffix to the module format, e.g. ESM+ES5 specifies
that the module is in ESM format and contains code down-leveled to ES5. Other commonly
used combos: ESM+ES2015, CJS+ES5, and CJS+ES2015.
Bundle - an artifact in the form of a single JS file, produced by a build tool, e.g. WebPack or
Rollup, that contains symbols originating in one or more modules. Bundles are a
browser-specific workaround that reduce network strain that would be caused if browsers
were to start downloading hundreds if not tens of thousands of files. Node.js typically
doesn't use bundles. Common bundle formats are UMD and System.register.
Language Level - T he language of the code (ES5 or ES2015). Independent of the module
format.
Entry Point - a module intended to be imported by the user. It is referenced by a unique
module ID and exports the public API referenced by that module ID. An example is
@angular/core or @angular/core/testing. Both entry points exist in the @angular/core
package, but they export different symbols. A package can have many entry points.
Deep Import - p rocess of retrieving symbols from modules that are not Entry Points. These
module IDs are usually considered to be private APIs that can change over the lifetime of
the project or while the bundle for the given package is being created.
Top-Level Import - an import coming from an entry point. The available top-level imports
are what define the public API and are exposed in “@angular/name” modules, such as
@angular/core or @angular/common.
Tree-shaking - process of identifying and removing code not used by an application - also
known as dead code elimination. This is a global optimization performed at the application
level using tools like Rollup, Closure Compiler, or Uglify.
AOT Compiler - the A head of Time Compiler for Angular.
Flattened Type Definitions - the bundled TypeScript definitions generated from
api-extractor.