website

This document describes the structure and format of the currently available Angular framework packages on NPM. This format works for packages that distribute Angular components, such as Angular Material, as well as core framework packages that are published under the @angular namespace, such as @angular/core and @angular/forms.

The format described here uses a unique file layout and metadata configuration to make the package work seamlessly in the most common scenarios of using Angular and to make it compatible with the tools provided by the Angular team and the community itself. For this reason, third-party library developers are also strongly encouraged to follow the same structure.

Versioning of the format is consistent with Angular’s own versioning, and we want the format to evolve in a forward-compatible manner to support the needs of the Angular component and tools ecosystem.

Purpose of Package Format

In today’s JavaScript environment, developers will use packages in many different ways. For example, some might use SystemJS, others might use Webpack. However, others may use packages as UMD packages in Node or a browser or access them through global variables.

Angular distributions support all common development tools and workflows and emphasize optimization to reduce application payload size or speed up development iteration (build time).

Library File layout

Libraries should generally use the same layout, but have features that differ from the Angular framework.

Typically, libraries are split at the component or function level. Let’s take the Angular Material project as an example.

Angular Material publishes sets of components, such as a Button (a single component), Tabs (a group of components that work together), and so on. The common denominator is the NgModules that bind these functional areas together. Button has one NgModule, Tabs another, and so on.

The general rule of Angular Package Format is to generate FESM files for the smallest logically connected code set. For example, the Angular package has a FESM for @angular/core. When developers use the Component symbol from @angular/core, they will most likely also use symbols such as Injectable, Directive, NgModule, etc., directly or indirectly. Therefore, all of these parts should be bundled together to form a FESM. For most library cases, a single logical group should be grouped into an NgModule, and all these files should be bundled together as a single FESM file in the package, representing a single entry point in the NPM package.

Here’s an example of how an Angular Material project might look in this format:

Now look at the output from Spartacus Core Build:

Primary Entry point

The main entry point for a package is a module whose module ID matches the package name (for example, for the “@angular/core” package, the import from the main entry point is: import {Component,… } from ‘@ Angle/core ‘).

Secondary Entry point

In addition to the main entry point, a package can contain zero or more secondary entry points (for example, @angular/common/ HTTP). These contain symbols that we do not want to combine with the symbols in the main entry point for two reasons:

(1) The user usually thinks they are different from the main symbol group, and if they are related to the main symbol group, then they are already there.

(2) Symbols in secondary groups are usually only used in specific scenarios (for example, when writing and running tests). These symbols may not be included in the main entry point, so we reduce the chance that they will be accidentally used incorrectly (for example, using test emulation in production code used in @angular/core/testing).

The module ID imported by the secondary entry point directs the module loader to the directory of the secondary entry point name. For example, “@angular/core/testing” resolves to a directory with the same name, “@angular/core/testing.” This directory contains a package.json file that directs the loader to the correct location it is looking for. This allows us to create multiple entry points in a single package.

Compilation and transpilation

To generate all necessary build artifacts, we strongly recommend that you use the Angular compiler (NGC) to compile your code using the following Settings in tsconfig.json:

{
  "compilerOptions": {
    ...
    "declaration": true,
    "module": "es2015",
    "target": "es2015"
  },
  "angularCompilerOptions": {
    "strictMetadataEmit": true,
    "skipTemplateCodegen": true,
    "flatModuleOutFile": "my-ui-lib.js",
    "flatModuleId": "my-ui-lib",
  }
}

Optimization of the related

Flattening of ES Modules

We strongly recommend that you optimize build artifacts by flattening ES modules before publishing them to NPM. This significantly reduces the build time of Angular applications and the download and parsing time of the final application package.

The Angular compiler supports generating indexed ES module files, which can then be used to generate flat modules using tools such as Rollup to generate what we call a flat ES module or FESM file format.

FESM is a file format that flattens all ES modules accessible from the entry point into a single ES module. It is formed by tracing all imports in a package and copying the code into a single file, while preserving all public ES exports and removing all private imports.

The abbreviated name “FESM” (pronounced “phesom”) can be followed by a number, such as “FESM5” or “FESM2015”. The number refers to the language level of JavaScript within the module. So the FESM5 file will be ESM+ES5 (import/export statements and ES5 source code).

To generate a flat ES module index file, use the following configuration options in the tsconfig.json file:

{
  "compilerOptions": {
    ...
    "module": "es2015",
    "target": "es2015",
    ...
  },
  "angularCompilerOptions": {
    ...
    "flatModuleOutFile": "my-ui-lib.js",
    "flatModuleId": "my-ui-lib"
  }
}

Once the index file (such as my-ui-lib.js) is generated by NGC, binders and optimizers (such as Rollup) can be used to generate flat ESM files.

Inlining of templates and stylesheets

Component libraries are typically implemented using stylesheets and HTML templates stored in separate files. Although not required, we recommend that component authors inline templates and stylesheets into their FESM files and *.metadata.json files by replacing styleUrls and templateUrls with style and template metadata attributes, respectively. This simplifies the use of components by application developers.

Starting with APF V10, we recommend adding TSLIB as a direct dependency for the primary entry point, because the TSLIB version is associated with the TypeScript version used to build the library.

Some terms

  • Package: The smallest set of files published to NPM and installed together, such as @angular/core. This package contains a manifest named package.json, compiled source code, TypeScript files, source mappings, metadata, and more. This package is installed via NPM install @angular/core.
  • Symbols: A class, function, constant, or variable that is contained in a module and optionally visible to the outside world through the module export.
  • Module ID: The identifier of the module used in the import statement, such as “@spartacus/core”. The ID is usually mapped directly to a path on the file system, but this is not always the case due to various module resolution strategies.
  • Module Format: A module syntax specification that covers at least the syntax used to import and export from files. Common module formats are CommonJS (CJS, typically used in Node.js applications) or ECMAScript modules (ESM). The module format represents only the encapsulation of a single module, not the JavaScript language features used to make up the module’s content. For this reason, the Angular team often uses language-level specifiers as suffixes for module formats. For example, ESM+ES5 specifies that a module is ESM formatted and contains code from below to ES5. Other common combinations: ESM+ES2015, CJS+ES5, CJS+ES2015.
  • Bundle: An artifact in the form of a single JS file generated by a build tool, such as Webpack or Rollup, that contains symbols from one or more modules. Bundling is a browser-specific solution that reduces the network stress that can occur when a browser starts downloading hundreds or even tens of thousands of files. Node.js does not normally use packages. The common bundling formats are UMD and System.Register.
  • Language Level: The language of the code (ES5 or ES2015). Independent of 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 to which that module ID is referenced. An example is @angular/core or @angular/core/testing. There are two entry points in the @angular/core package, but they export different symbols. A package can have many entry points.
  • Deep import: The process of retrieving symbols from a module that is never the entry point. These module IDs are generally considered private APIs that can be changed during the life of a project or when a package for a given package is created.
  • Top level import: Import from the entry point. The available top-level imports define the public API and expose it in the “@angular/name” module, such as @angular/core or @angular/common.
  • Tree shaking: The process of identifying and removing unused code from an application — also known as dead code removal. This is done at the application level using tools such as Rollup, Closure Compiler, or Uglify.

More of Jerry’s original articles can be found on “Wang Zixi “: