1. 最近工作上用到Angular,需要查阅一些英文资料,虽然英文非常烂,但是种种原因又不得不硬着头皮上,只是每次看英文都很费力,因此决定将一些比较重要的特别是需要反复阅读的资料翻译一下,以节约再次阅读的时间。

2. 另外一方面,如果只是看英文,不做笔记和记录,通常会很浮躁,很多知识点都是一知半解,因此倒不如翻译一下,以加深自己的理解(虽说是翻译,但实际上只是按照自己的理解,复述一下,因此不敢擅用直译、意译了,更加不敢说什么信雅达了)。

3. 再强调一下,由于英文水平有限,错误在所难免(并非客套),如果因为这篇文章的错误而误导,深表歉意!也因此在下面首先贴出原文链接。

原文链接

在这篇博客里面,主要讲述向npm发布Angular组件所必需的相关知识,这些组件具备以下特性:

  • 平台中立的(比如可运行在浏览器和Web Workers环境)
  • 可以将所有文件打包在一起,也可以多个文件的形式发布
  • 可以使用Angular的预编译
  • 使用TypeScript时允许IDE智能提示,可以进行编译时类型检查

这篇文章不会说明怎么开发一个npm模块,如果想了解这方面的知识,可以访问下面的链接:

下面,就以我最近发布的ngresizeable组件为例来说明,ngresizable是一个能够调整DOM元素大小的简单组件。

平台中立的组件

Angualr的一个突出优势就是平台中立的,基本上所有需要交互的模块都是通过一个抽象层来进行——Renderer,我们自己写的组件也应该依赖于抽象层,而不是具体平台的API(依赖倒置原则),换句话说,如果你为Web创建一个类库,不要直接操作DOM,因为这样将不能在Web Workers和服务器环境中运行。

看下面的例子:

@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');
}
});
}
}

这段代码和底层平台耦合的很紧,包含很多“反模式”,例如:

  1. 直接访问header元素的addEventListener方法
  2. 没有清除添加在header上的事件监听
  3. 直接访问headerclassList属性
  4. 访问全局对象document,而document对象在其它平台可能是无效的

重构一下,以实现平台中立:


@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();
}
}

使用Renderer代替直接操作DOM和全局对象访问,上面的代码看上去好多了,也可以在多个平台运行,但是手工操作还是太多,比如绑定和取消click事件,因此还可以如下优化:

@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;
}
}

上面是一个最优实现,平台中立,并且容易测试(在toggleZippy方法中切换组件的可见性,从而更容易测试)。

发布组件

发布组件并不是不重要的事,甚至Angular都发布了好几种不同结构的npm模块。

一般来说,编写我们自己的模块包时,需要考虑下面这些事项:

  1. 支持摇树优化。如果把系统当成一棵代码树,摇树优化指的就是把不需要的代码从系统发布包中移除,就想摇树一样,甩掉不需要的枝叶,摇树优化在发布产品包时非常重要。
  2. 开发者在开发模式下应该尽可能方便的使用,实际上就是既要支持产品模式下的体积小,又要支持开发模式下的易于调用。
  3. 需要保持发布包尽可能小,从而节约带宽和下载时间。

为了添加摇树优化特性,我们需要使用ES2015的模块化方式(也称之为esm),从而可以让诸如RollupWebpack之类的打包器能够处理未使用的exports。为了实现这种特性,可以在tsconfig.json中如下配置(从ngresizable项目中复制过来):

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

如果想具有更广泛的适用性,还应该提供ES5的版本,有两种选择方案:

  • 使用两个目录,分别存放esmES5两个版本

    • esm:包括使用ES2015模块化的部分
    • ES5:不使用ES2015语法的部分(比如使用CommonJS、System或UMD等模块化方法来替代)
  • 在一个包中同时提供esmES5 UMD两种版本

第二种方案有如下的优势:

  • 不会有很多附加文件,只包含esm版的文件和一个包含所有功能的已经打好的单一包
  • 开发人员在使用SystemJS格式开发时,只需要一次request请求,否则的话,SystemJS会为每个文件发起一次请求

不管使用哪个工具,例如,ngresizable组件使用Google的rollup,都可以方便的生成ES5ES2015格式的模块包,因此,后续将不需要任何额外步凑,就可以生成ES2015语法的组件包。

最后,因为没有使得目录结构更加复杂,我们可以在根路径简单的输出两种格式:esm格式的代码和符合UMD规范的包。

包配置

因此,我们有两种格式的包(esmUMD),那么问题来了,在package.json中应该配置哪个入口呢?我们想让理解esm的打包器使用ES2015模块化方案,而其它的则使用UMD模块化方案。

为此,可以如下配置package.json

  • 设置main属性指向ES5 UMD规范包
  • 设置module属性指向esm版本的入口文件,module是诸如rollupwebpack等打包器所期望的ES2015模块引用入口,但是一些旧版本的打包器使用jsnext:main属性,因此我们可以同时设置modulejsnext:main

最终,package.json配置成:

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

提供类型定义文件

因为类库的使用者很可能使用TypeScript,因此需要提供类型定义文件,从而实现IDE智能提示和类型检查,为此,需要打开tsconfig.json中的declaration标志,并且在package.json中设置types属性:

{
"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"
]
}
{
...
"main": "ngresizable.bundle.js",
"module": "ngresizable.module.js",
"jsnext:main": "ngresizable.module.js",
"types": "ngresizable.module.d.ts",
...
}

兼容Angular的预编译AOT

AOT是一个很强大的特性,我们开发/发布的组件包最好也能够兼容AOT特性。

如果我们发布一个不附加任何元数据的JavaScript模块,依赖于这个模块的Angular应用就不能AOT编译,但是我们怎么向ngc提供元信息呢?可以包括组件包所使用的TypeScript版本,但这并不是唯一的方式,我们还可以通过使用ngc预编译好组件包,同时启用tsconfig.json中的angular编译选项中的skipTemplateCodegen,最终,tsconfig.json如下所示:

{
"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
}
}

通过默认的ngc为组件和模块生成ngfactories ,并通过skipTemplateCodegen 选项仅生成*.metadata.json文件。

扼要重述

应用上面的步骤后,ngresizable结构如下:

.
├── 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

最终的包内容:

  • ngresizable.bundle.js - 组件的ES5 UMD发布包
  • esm - 可以进行摇树优化的源码
  • *.js.map - 便于调试的source map 文件
  • *.metadata.json - ngc编译需要的元信息文件
  • *.d.ts - 允许TypeScript编译器进行类型检查和智能提示的类型定义文件

其它说明

非常重要的一个主题是就是代码风格,模块应该遵循最佳实践,进一步的信息访问angular.io 的格式指南。

  • 注意,不要使用ng作为组件的前缀,因为有可能和google官方的组件冲突。 *

结论

在这篇博客中,简短的说明了发布Angular组件库需要考虑的一些最重要的事项:怎么和底层平台解耦,怎么保持摇树优化特性,怎么最小化组件包,怎么对AOT预编译友好等等。

开发Angular库的简单指导(译)的更多相关文章

  1. WebAssembly简单指导---译

    开发者指导 本页面提供一步一步的操作将一个简单的程序编译成webassembly 前提要求 为了编译成webAssembly,需要提前安装一些工具: Git.在Linux和OSX下已自带了Git,在W ...

  2. REST API设计指导——译自Microsoft REST API Guidelines(四)

    前言 前面我们说了,如果API的设计更规范更合理,在很大程度上能够提高联调的效率,降低沟通成本.那么什么是好的API设计?这里我们不得不提到REST API. 关于REST API的书籍很多,但是完整 ...

  3. REST API设计指导——译自Microsoft REST API Guidelines(二)

    由于文章内容较长,只能拆开发布.翻译的不对之处,请多多指教. 另外:最近团队在做一些技术何架构的研究,视频教程只能争取周末多录制一点,同时预计在下周我们会展开一次直播活动,内容围绕容器技术这块. 所有 ...

  4. 2015年10个最佳Web开发JavaScript库

    2015年10个最佳Web开发JavaScript库 现在的互联网可谓是无所不有,有大量的JavaScript项目开发工具充斥于网络中.我们可以参考网上的指导来获取构建代码项目的各种必要信息.如果你是 ...

  5. 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍

    GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...

  6. Asp.net Mvc模块化开发之“开启模块开发、调试的简单愉快之旅”

    整个世界林林种种,把所有的事情都划分为对立的两个面. 每个人都渴望的财富划分为富有和贫穷,身高被划分为高和矮,身材被划分为胖和瘦,等等. 我们总是感叹,有钱人的生活我不懂;有钱人又何尝能懂我们每天起早 ...

  7. iOS开发数据库篇—FMDB简单介绍

    iOS开发数据库篇—FMDB简单介绍 一.简单说明 1.什么是FMDB FMDB是iOS平台的SQLite数据库框架 FMDB以OC的方式封装了SQLite的C语言API 2.FMDB的优点 使用起来 ...

  8. 李洪强iOS开发Swift篇—01_简单介绍

    李洪强iOS开发Swift篇—01_简单介绍 一.简介 Swift是苹果于2014年WWDC(苹果开发者大会)发布的全新编程语言 Swift在天朝译为“雨燕”,是它的LOGO 是一只燕子,跟Objec ...

  9. app 下载更新 file-downloader 文件下载库的简单介绍和使用

    app 下载更新 file-downloader 文件下载库的简单介绍和使用 今天介绍一个下载库:file-downloader 文件下载库 说明: * 本文内容来自原 file-downloader ...

随机推荐

  1. Java 字节流操作

    在java中我们使用输入流来向一个字节序列对象中写入,使用输出流来向输出其内容.C语言中只使用一个File包处理一切文件操作,而在java中却有着60多种流类型,构成了整个流家族.看似庞大的体系结构, ...

  2. nao安装中文包教程

    本文介绍nao离线安装中文包 相官方索取 .demo 文件夹,里面包含很多的例程和中文包 下载winscp和putty(或者xshell),下载过程和安装我就不多说了,免费 把nao开机,连接路由器等 ...

  3. [C++]现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统

    大二的时候写的一个CV小玩意,最终决定还是把它放出来,也许会帮助到很多人,代码写的很丑,大家多多包涵.附加实验报告主要部分. 课题背景及意义: 本项目主要目标是设计一套能自动分析我校现行的试卷封面并获 ...

  4. 简学Python第六章__class面向对象编程与异常处理

    Python第六章__class面向对象编程与异常处理 欢迎加入Linux_Python学习群  群号:478616847 目录: 面向对象的程序设计 类和对象 封装 继承与派生 多态与多态性 特性p ...

  5. yii中调整ActiveForm表单样式

    Yii2中对于表单和字段的支持组件为ActiveForm和ActiveField, <?php $form = ActiveForm::begin([ 'id' => 'login-for ...

  6. SQL Server中的Merge关键字 更新表数据

    简介 Merge关键字是一个神奇的DML关键字.它在SQL Server 2008被引入,它能将Insert,Update,Delete简单的并为一句.MSDN对于Merge的解释非常的短小精悍:”根 ...

  7. Tomcat+Eclipse乱码问题解决方法

    概述 乱码问题是大家在日常开发过程中经常会遇到的问题,由于各自环境的不同,解决起来也费时费力,本文主要介绍一般性乱码问题的解决方法与步骤,开发工具采用Eclipse+Tomcat,统一设置项目编码UT ...

  8. SpringMVC REST 风格请求介绍及简单实践

    简介 REST 即 Representational State Transfer.(资源)表现层状态转化.是目前最流行的一种互联网软件架构.它结构清晰.符合标准.易于理解.扩展方便,所以正得到越来越 ...

  9. C++标准库之vector(各函数及其使用全)

    原创作品,转载请注明出处:http://www.cnblogs.com/shrimp-can/p/5280566.html iterator类型: iterator:到value_type的访问,va ...

  10. linux 私房菜 CH8 linux 磁盘与文件系统管理

    索引式文件系统 superblock 记录此系统的整体信息,包括 inode/block 的总量.使用量.剩余量,以及文件系统的格式与相关信息等: inode 记录档案的属性,一个档案占用一个 ino ...