惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
Lohrmann on Cybersecurity
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
腾讯CDC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog
TaoSecurity Blog
TaoSecurity Blog
博客园_首页
Vercel News
Vercel News
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Last Week in AI
Last Week in AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
The Exploit Database - CXSecurity.com
量子位
Project Zero
Project Zero
A
Arctic Wolf
小众软件
小众软件
NISL@THU
NISL@THU
C
CERT Recently Published Vulnerability Notes
有赞技术团队
有赞技术团队
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
News and Events Feed by Topic
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Security Latest
Security Latest
B
Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
Schneier on Security
Schneier on Security
The Hacker News
The Hacker News
K
Kaspersky official blog
C
Check Point Blog
Hacker News: Ask HN
Hacker News: Ask HN
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
人人都是产品经理
人人都是产品经理
AI
AI
Cisco Talos Blog
Cisco Talos Blog
MyScale Blog
MyScale Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
S
Schneier on Security
P
Palo Alto Networks Blog

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
Distributing an Angular Library - The Brief Guide
2017-01-21 · via Minko Gechev's blog

Edit · Jan 21, 2017 · 10 minutes read · Follow @mgechev Angular TypeScript

In this post I’ll quickly explain the minimum you need to know in order to publish an Angular component to npm. By the end of the post you’ll know how your module can:

  • Be platform independent (i.e. run in Web Workers, Universal).
  • Should be bundled and distributed.
  • Work with the Angular’s Ahead-of-Time compiler.
  • Play well with TypeScript by allowing autocompletion and compile-time type checking.

If you’re only interested in a quick checklist of things you need to consider for distributing your Angular library, go directly to the “Distributing an Angular Library - Checklist” section.

Note that this article doesn’t aim to provide complete guidelines for developing an npm module. If you’re looking for this, I can recommend you:

Along the way, I’ll provide examples from a module I recently released called ngresizable. ngresizable is a simple component which can make a DOM element resizable.

If you find anything important missing, please don’t hesitate to comment below.

Writing platform independent components

One of the greatest strengths of Angular is that the framework is platform agnostic. Basically, all the modules which have to interact with the underlying platform depend on an abstraction. This is the abstract Renderer. The code you write should also depend on an abstraction, instead of a concrete platform APIs (see the dependency inversion principle). In short, if you’re building your library for the Web, you should not touch the DOM directly because this will make it unable to work in Web Workers and on the server and most developers need that!

Decouple package

Lets take a look at an example:

// ANTI-PATTERN

@Component({
  selector: 'my-zippy',
  template: `
    <section class="zippy">
      <header #header class="zippy-header">{{ title }}</header>
      <section class="zippy-content" id="zippy-content">
        <ng-content></ng-content>
      </section>
    </section>
  `
})
class ZippyComponent {
  @Input() title = '';
  @Input() toggle = true;
  @ViewChild('header') header: ElementRef;

  ngAfterViewInit() {
    this.header.nativeElement.addEventListener('click', () => {
      this.toggle = !this.toggle;
      document.querySelector('#zippy-content').hidden = !this.toggle;
      if (this.toggle) {
        this.header.nativeElement.classList.add('toggled');
      } else {
        this.header.nativeElement.classList.remove('toggled');
      }
    });
  }
}

This snippet is quite coupled to the underlaying platform, and contains plenty of other anti-patterns, for instance we:

  1. Direclty interact with the header DOM element by invoking it’s method addEventListener.
  2. Do not clear the event listener we add to the header element.
  3. Access the classList property of the native header element.
  4. Access property of the global object document, however, document is not available on other platforms.

Lets refactor the code, in order to make it platform agnostic:

// Alright...

@Component({
  selector: 'my-zippy',
  template: `
    <section class="zippy">
      <header #header class="zippy-header">{{ title }}</header>
      <section #content class="zippy-content" id="zippy-content">
        <ng-content></ng-content>
      </section>
    </section>
  `
})
class ZippyComponent implements AfterViewInit, OnDestroy {
  @ViewChild('header') header: ElementRef;
  @ViewChild('content') content: ElementRef;
  @Input() title = '';
  @Input() toggle = true;
  
  private cleanCallback: any;

  constructor(private renderer: Renderer) {}

  ngAfterViewInit() {
    this.cleanCallback = this.renderer.listen(this.header.nativeElement, 'click', () => {
      this.toggle = !this.toggle;
      this.renderer.setElementProperty(this.content.nativeElement, 'hidden', !this.toggle);
      this.renderer.setElementClass(this.header.nativeElement, 'toggled', this.toggle);
    });
  }

  ngOnDestroy() {
    if (typeof this.cleanCallback === 'function')
      this.cleanCallback();
  }
}

The code above looks better. It’ll work on multiple platforms because we use the Renderer instead of directly manipulating the DOM and accessing globals.

Although it works, we can do a bit better. We do a lot of manual things, for instance, imperatively listening for the click event and after that removing the listener:

// Best

@Component({
  selector: 'my-zippy',
  template: `
    <section class="zippy">
      <header (click)="toggleZippy()" [class.toggled]="toggle"
        class="zippy-header">{{ title }}</header>
      <section class="zippy-content" [hidden]="!toggle">
        <ng-content></ng-content>
      </section>
    </section>
  `
})
class ZippyComponent implements AfterViewInit, OnDestroy {
  @Input() title = '';
  @Input() toggle = true;

  toggleZippy() {
    this.toggle = !this.toggle;
  }
}

The code above contains a better implementation, which is platform agnostic and also testable (we toggle the visibility of the content in the method toggleZippy so we can write tests for this easier).

Distributing the components

The components’ distribution is not a trivial problem. Even Angular went through several different structures of their npm modules. The things we need to consider for our packages are:

  1. They should be tree-shakable. Tree-shaking is exclusively used for producing a production bundle, because it allows us to drop unused exports.
  2. Developers should be able to use them as easy as possible in development mode, i.e. no transpilation of any kind should be required.
  3. We need to keep the package lean to save network bandwidth and download time.
Distribute package

In order to keep the module tree-shakable, we need to distribute it in a way that it uses ES2015 modules (also known as esm). By having it in this format bundlers, such as Rollup and Webpack, will be able to get rid of unused exports.

For this purpose we can use tsc so our tsconfig.json should look something like:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "sourceMap": true,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "outDir": "./dist",
    "lib": ["es2015", "dom"]
  },
  "files": [
    "./lib/ngresizable.module.ts"
  ]
}

This is a configuration file copied from ngresizable.

If we want to make the life of people who are using our package easier, we should also provide ES5 version of it. Here are two options to be considered:

  • Distribute it in two directories - esm and es5 which respectively contain two different versions of our module’s JavaScript files:
    • esm - contains an ES5 version that uses ES2015 modules.
    • es5 - contains an ES5 version of the package which doesn’t use any ES2015 syntax (i.e. the modules are in commonjs, amd, system or UMD format).
  • Distribute the packages with esm and also provide ES5 UMD (Universal Module Definition) bundle.

The second approach has a few advantages:

  • We don’t bloat the package with additional files - we have only the esm version of our package and a single bundle which contains everything else.
  • When developers use our package in development with SystemJS their browser can load the entire library with only a single request to the UMD bundle. In contrast, if we distribute the package without bundling the module but providing it as individual files instead, SystemJS will send request for each file. Once your project grows this can become inconvenient by slowing down each page refresh dramatically.

It doesn’t matter much which tool we’d choose for producing the UMD ES5 bundle. Google, for instance, uses rollup for bundling Angular, which is also the case for ngresizable. It’s pretty neat that you can compile your module to ES5 and ES2015 modules. Later your bundler can translate the ES2015 module syntax to UMD so you don’t need any additional steps of transpilation.

In the end, since we don’t have to complicate the directory structure of the package additionally, we can simply output both, the esm version of our code and the UMD bundle, in the root of directory of the distribution.

Configuring the package

So, now we have two different versions of our code esm and ES5 UMD bundle. The question is what entry file in package.json should we provide? We want bundlers which understand esm to use the ES2015 modules, and bundles which don’t know how to use ES2015 modules to use the UMD bundle instead.

In order to do this we can:

  • Set the value of the main property of package.json to point to the ES5 UMD bundle.
  • Set the value of the module property to point to the entry file of the esm version of the library. module is a field in package.json where bundlers such as rollup and webpack 2 expect to find a reference to the ES2015 version of the code. Note that some older versions of the bundlers use jsnext:main instead of module so we need to set both properties.

Our final package.json should look something like:

{
  ...
  "main": "ngresizable.bundle.js",
  "module": "ngresizable.module.js",
  "jsnext:main": "ngresizable.module.js",
  ...
}

So far so good, but that’s not all!

Providing type definitions

Since most likely the users of the package will use TypeScript, we need to provide type definitions to them. To do this, we need to enable the declaration flag in tsconfig.json and set the types field of our package.json.

tsconfig.json should look like:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "sourceMap": true,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "outDir": "./dist",
    "lib": ["es2015", "dom"]
  },
  "files": [
    "./lib/ngresizable.module.ts"
  ]
}

…and respectively package.json:

{
  ...
  "main": "ngresizable.bundle.js",
  "module": "ngresizable.module.js",
  "jsnext:main": "ngresizable.module.js",
  "types": "ngresizable.module.d.ts",
  ...
}

Compatibility with Angular’s AoT Compiler

Ahead-of-Time compilation is a great feature and we need to develop & distribute our modules compatible with the compiler.

Compatible with compiler

If we distribute our module as JavaScript without any additional metadata, users who depend on them will not be able to compile their Angular application. But how can we provide metadata to ngc? Well, we can include the TypeScript version of our modules as well…but it’s not required.

Instead, we should precompile our module with ngc and enable the skipTemplateCodegen flag in tsconfig.json’s angularCompilerOptions. After the last update our tsconfig.json will look like:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "sourceMap": true,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "outDir": "./dist",
    "lib": ["es2015", "dom"]
  },
  "files": [
    "./lib/ngresizable.module.ts"
  ],
  "angularCompilerOptions": {
    "skipTemplateCodegen": true
  }
}

By default ngc generates ngfactories for the components and modules. By using skipTemplateCodegen flag we can skip this and only get *.metadata.json files.

Recap

After applying all these build steps, the final structure of the ngresizable module looks like this:

.
├── README.md
├── ngresizable.actions.d.ts
├── ngresizable.actions.js
├── ngresizable.actions.js.map
├── ngresizable.actions.metadata.json
├── ngresizable.bundle.js
├── ngresizable.component.d.ts
├── ngresizable.component.js
├── ngresizable.component.js.map
├── ngresizable.component.metadata.json
├── ngresizable.module.d.ts
├── ngresizable.module.js
├── ngresizable.module.js.map
├── ngresizable.module.metadata.json
├── ngresizable.reducer.d.ts
├── ngresizable.reducer.js
├── ngresizable.reducer.js.map
├── ngresizable.reducer.metadata.json
├── ngresizable.store.d.ts
├── ngresizable.store.js
├── ngresizable.store.js.map
├── ngresizable.store.metadata.json
├── ngresizable.utils.d.ts
├── ngresizable.utils.js
├── ngresizable.utils.js.map
├── ngresizable.utils.metadata.json
└── package.json

As recap, notice that in the final package we have:

  • ngresizable.bundle.js - an ES5 UMD bundle of the module.
  • esm ES5 version of our code - allows tree-shaking.
  • *.js.map - source map files for easier debugging.
  • *.metadata.json - metadata required by ngc to do its job.
  • *.d.ts - type definitions which allow TypeScript to perform compile-time type checking and allow intellisense for our library.

Other topics

Very important topic that needs to be considered is related to following the style guide. Your modules should follow best practices especially when with given practice you can impact a dependent project. For further information visit angular.io/styleguide.

For instance, the ngresizable component violates a practice from the styleguide:

  • It uses a component as an attribute. This is a practice which violation is acceptable in this specific case because of implementation details of the component.

Note that using ng as prefix of selector for your directives/components is not recommended because of possible collisions with components coming from Google.

Distributing an Angular Library - Checklist

In this section we’ll wrap things up, by briefly mentioning each point you need to consider:

  • Try not to directly access DOM APIs (i.e. follow the dependency inversion principle).
  • Provide esm version of your library in order to allow tree-shaking.
    • Reference the esm version under the module and jsnext:main properties in package.json.
  • Provide ES5 UMD bundle of your library.
    • Reference the bundle under the main property of your package.json.
  • Provide the type definitions of your library by generating them with tsc with the declaration flag set to true.
    • Reference the type definitions corresponding to the main module of your package in the types property in your package.json.
  • Compile your library with ngc and include the generated *.metadata.json files in your package. The tsconfig.json used by ngc should have skipTemplateCodegen set to true, under angularCompilerOptions.

Conclusion

In this blog post we briefly explained the most important things you need to consider when it comes to distributing your Angular library.

We explained how to keep the library decoupled from the underlying platform. After that, we went to distributing our code in a way that it’s tree-shakable and has minimum overhead over the user. As next section we described how to make the library friendly to the Angular’s Ahead-of-Time compiler.

Finally, we summarized all the practices in a short list which can serve you as a checklist.