1. 前言
Angular CLI 7.0.0 增加了脚手架配置项:--create-application,其中默认值是true,如果不设置,则在新工作空间的src文件夹中创建一个新的初始应用程序项目。如果为false,则创建一个没有初始应用程序的空工作区。想了解更多配置项,点击这里。
2. 创建库
第1步:创建库工作空间
Angular CLI 7.0.0的键入以下命令:
ng new foo-lib --create-application=false
这个时候我们会看到项目文件中的一些变化:
- package.json
- angular需要的所有常用依赖项
- angular.json
- Angular配置文件,但没有项目
- README.md、tsconfig.json、tslint.json、node_modules
- 基本和我们的构建初始化项目的内容结构一致
因为我们使用--create-application=false创建的应用,所以目录中是没有src目录的。
第2步:初始化库项目
键入以下命令创建Lib项目:
cd foo-lib
ng generate library foo-lib --prefix=foo
其中--prefix指令是用于初始项目的时候生成选择器(ng genreate)的前缀。详细配置项请看前言部分的超链接。如果你不指定,默认是lib。
执行完命令之后,我们发现项目中多了一个project文件夹,里边有个Library工程:foo-lib。
第3步:创建库测试项目(一定要创建,否则无法运行)
我们需要一个可以用来调用我们的Angular库的项目,键入以下命令:
ng generate application foo-tester
执行完命令之后,我们可以看到,project文件下又多出了一个文件夹:foo-tester,即我们的测试项目。另外,Angular CLI还添加了一个foo-tester-e2e项目,用于端到端测试。对于不写测试用例.spec的强迫症患者拯救大心丸:--minimal=true。
第4步:开发和测试
Codeing...
第5步:构建打包
Angular CLI从6.1开始,始终在生产模式下构建库,因此我们不使用--prod,只需键入以下命令:
ng build foo-lib
提醒:
如果想构建自己的测试项目则键入以下命令:
ng build foo-tester --prod
和构建Library库不一样的是,构建测试应用必须指定:--prod。
如果想启动自己的测试项目,则键入以下命令:
ng serve foo-tester
如果想测试自己的Library,则键入以下命令:
ng test foo-lib
如果想测试自己的测试项目,则键入以下命令:
ng test foo-tester第6步:发布我们自己的库
如果想发布到npm,则需注册一个自己的npm账号,如果已经有了且已经登录,则键入以下命令:
cd dist/foo-lib
npm publish
第7步:使用我们的库
和其他第三方包一样,只需要npm install你的自己发布的Library包即可,项目根目录终端键入以下命令:
npm i -S foo-lib
这个时候你会看到你的项目package.json中的dependencies依赖项中增加了一项:foo-lib。然后在Angular模块中引入即可。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FooLibModule } from 'foo-lib'; // 导入你的Library
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FooLibModule // 导入你的Library
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
第8步:最后的惊喜,如何维护自己的库
npm发布版本有些注意事项,每次构建发布需要注意以下规则:
// 1.npm插件发布
npm addUser // 分别输入用户名、密码、邮箱
npm publish // 直接发布
npm login // 非第一次发版本则用此命令
npm unpublish --force // 取消插件发布【谨慎使用】
npm deprecate <pkg>[@<version>] <message> // 并不会在社区里撤销你已有的包,但会在任何人尝试安装这个包的时候得到警告
npx force-unpublish package-name '原因描述' // 撤销不了??试试这个
// 2.npm插件更新
npm version patch // 补丁【1.0.1】
npm version minor // 小改【1.1.0】
npm version major // 大改【2.0.0】
// 注意需要再一次执行:npm publish
// 3.查看远程包版本信息
npm view xxx versions
// 4.npm查看本地全局安装过的包
npm list -g --depth=0
// 5.npm查看全局的包的安装路径
npm root -g
// 6.npm查看当前包的安装路径
npm root
// 7.npm将包安装到全局环境中
npm install xxx -g
// 8.npm将信息写入package.json,并自动把模块和版本号添加到dependencies部分
npm install xxx –save
npm i -S xxx // 简写版本
// 9.npm将信息写入package.json,并自动把模块和版本号添加到devdependencies部分
npm install xxx –save-dve
npm i -D xxx // 简写版本
// 10.npm单独更新某个包
npm update xxx
// 11.npm更新至最新版
npm install -g npm
// 12.npm淘宝镜像
npm config set registry http://registry.npm.taobao.org
将库提交代码至 gitLab/github仓库
// 第一种: 从命令行创建新的存储库
echo "# init project" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin xxx.git
git push -u origin master
第二种:从命令行推送现有存储库
git remote add origin xxx.git
git push -u origin master
安装我们私有的库
使用的方式有两种:ssh和http
- ssh
npm i -S git+ssh://git@xxx.git
- http
npm i -S git+http://xxx.git
温馨提示:我们开发中可能需要安装特定的某个分支或者tag或者某次提交的版本,命令是: npm i -S xxx.git#<tag|branch|commit>
更多npm install信息请手动点击这里查阅官方文档:官方文档
3. 使用我们私有的库
引入的方式很多,常用的是通过引入module的方式,而我们工作中平时都是封装一个附带路由的大模块,因此懒加载的方式更适合我们。但是这还是不够,由于安全以及其他业务上的关联模块之间可能会有一些数据依赖:例如token。所以使用路由插座router-outlet的方式,插座提供了两个回调:即实例化新组件时,路由器插座会发出激活activate事件,以及销毁组件时的停用deactivate事件。灵感来源于此文章,以下以我们真实的项目作为例子:
- 第一步:创建Library载体
// 1. 创建一个component组件作为Lib的载体,使用router-outlet的方式引入Lib库,且配置配置相应的信息和注册必要的Lib库Event回调
import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
export const ENV_TITLE: string = environment.title;
@Component({
selector: 'app-monitor-platform-outlet',
template: `
<!-- 路由插座作为Library库载体 -->
<router-outlet (activate)="onActivate($event)" (deactivate)="onDeactivate()"></router-outlet>
`,
styles: [``]
})
export class LibOutletComponent implements OnInit {
cloudCall: Subscription;
classDetail: Subscription;
constructor() { }
ngOnInit(): void { }
// 组件激活
onActivate(componentReference) {
// 1. @Input() baseInfo【object】为Lib库需要的基础信息,请严格按照格式传入!
componentReference.baseInfo = {
env: ENV_TITLE,//用来判断运行环境,或者直接赋值给window在库中获取
token: 'xxxx'
};
// 2. @Output cloudCallClicked【function】事件回调
if (componentReference.cloudCallClicked) {
this.cloudCall = componentReference.cloudCallClicked.subscribe((data) => {
console.log('点击云呼click回调信息: ', data);
});
}
}
// 组件销毁
onDeactivate() {
if (this.cloudCall) {
this.cloudCall.unsubscribe();
}
}
}
第二步:创建容器
创建容器的原因呢是为了解决CLI编译的问题,详细的可以查看angular-cli github issess。
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FooModule } from 'foo-lib/dist'; // 我们的库模块
@NgModule({
declarations: [],
imports: [
CommonModule,
FooModule // 我们库模块
]
})
export class LibContainerModule { }
第三步:配置路由懒加载Library模块
我们以创建的载体作为主路由,Lib库作为子路由挂载。angular8里的懒加载子路由有变动,需要格外注意,angular8的具体变动信息可以查阅官方博客。
const routes: Routes = [
{
path: 'xxx', component: LibOutletComponent, children: [
/* 这里的loadChildren路径为LibContainerModule容器的相对路径 */
{ path: '', loadChildren: './lib-outlet/lib-container.module#LibContainerModule' }
]
}
];
第四步:导入样式
我们的Library难免会有自己的全局样式,这个时候我们可以在Lib库里创建一个assets文件夹,在assets文件夹下创建一个全局的样式文件golbal.scss。使用者使用我们的Lib库的时候只需要导入我们Lib库的这个全局样式即可,如下:
/* Lib库样式,必须引入 */
@import '~xxx-lib/dist/golbal.scss';
第五步:资源文件配置
angular.json在build的时候提供了assets选项在构建项目时复制文件和文件夹,我们可以使用资产对象的形式从项目外部复制assets资源。想了解更详细的配置信息可以访问官方文档。这里举个例子:
angular.json文件中配置以下代码- glob:一个 node-glob 它使用 input 作为基准目录。
- input:相对于工作空间根目录的路径。
- output:相对于 outDir 的路径(默认为 dist/project-name )。
- ignore:要排除的 glob 列表。
{
"assets": [
{
"glob": "**/*",
"input": "./node_modules/xxx-lib/dist/images/",
"output": "assets/images"
}
]
}
第六步(可选):编译配置
当项目在build的时候找不到Library模块时,我们需要手动配置ts编译选项,以便让cli在将ts编译为js的时候能找到我们的Lib模块。typescript创建的项目在根目录下都会有一个
tsconfig.json文件,文件中指定了用来编译这个项目的根文件和编译选项。更详细的配置项,可以查阅官方文档,编译选项可以查阅这个官方文档。我们的解决办法是手动配置compilerOptions下的path选项:添加非相对路径模块映射。即:在tsconfig.json中配置Lib的路径
{
"compilerOptions": {
// 加入以下代码【模块名到基于baseUrl的路径映射】。这里要注意:如果已经在tsconfig.app.json里定义里baseUrl会覆盖tsconfig.json中的baseUrl!
"baseUrl": "./",
"paths": {
"xxx-lib": [
"node_modules/xxx-lib/dist"
]
}
}
}
4. 以下是目前遇到的问题
Lib库打包assets文件没有被打包到dist里?
目前并没有好的方式可以打包静态资源,详情问题可以查阅angular-cli github issues。所以我们只能自己手动写脚本或者使用其他插件打包我们的静态assets资源文件,例如使用gulp。
Lib写的路由在项目中使用提示:Error: Cannot match any routes. URL Segment: 'xxx/xxx'
这个就是路由没有匹配上,在Lib库里边我们的路由要写成相对路由,这样前置路径就可以正确的匹配上。更详细的介绍可以查阅官方文档。举个例子如下:
// 注入服务
constructor(
private route: ActivatedRoute,
private router: Router
) { }
// 相对路由写法
this.router.navigate([`../monitor-progress`], {
relativeTo: this.route, // 相对路径
queryParams: {
classLessonId: item.classLessonId
}
});
本地跑的好好的,一到线上就不好,Lib包在Jenkins发版的时候不更新
由于我们私有的Lib库没有发布到npm上,没有每一次打包的dist版本镜像,所以npm install的时候无法匹配版本导致无法更新Lib包。我们可以通过重新安装Lib的方式来进行更新(PS:当然如果Jenkins构建的时候删除了node_module文件夹的话,就不需要进行这样的配置了)。
package.json配置如下:
{
"scripts": {
"update:lib": "npm install monitor-platform-lib",
"build:prod": "npm run update:lib && npm run service_worker && node --max_old_space_size=4096 ./node_modules/.bin/ng build --prod && npm run static_share prod"
},
"private": true,
"dependencies": {
"monitor-platform-lib": "git+http://gs.blingabc.com/web/monitor-platform-lib.git"
}
}
npm下载的Library库,除了dist目录之外还有其他一堆文件和文件夹,我并不希望下载这些,应该怎么办?
package.json的配置项里有一个files选项可以满足你的需求。详细的介绍可以查阅官方文档,以下是我们的package.json配置:
{
"name": 'xxx',
"scripts": {
...
},
"dependencies": {
...
},
"files": [
"dist"
]
}
Lib库如何和我们的项目进行通信呢?
我们使用router-outlet路由插座作为中间载体,组件被激活(挂载)的时候router-outlet会提供activate回调,回调里会给我们这个激活(挂载)组件的实例,我们在组件实例使用@Input和@output装饰器即可完成双向通信。具体的例子可以查阅[这篇文章](medium.com/@sujeeshdl/…
Lib库里的某些组件样式我不满意,Lib库又没有提供相应的方法和属性供我使用,我改怎么办呢?
首先视图封装的模式,一般四种:ShadowDom、Native 、Emulated、None,angular默认的视图封装模式是Emulated,顾名思义也就是:只进不出,全局样式能进来,组件样式出不去。我们可以使用伪类deep做到修改Lib库样式。具体的使用方式可以查阅官方文档。当然不建议使用,样式的封装还是交由开发Library库的人员来进行维护。
Ionic封装的Library模块,项目在安装完build的时候报错:Interface 'HTMLIonActionSheetControllerElement' Cannot simultaneously extend types 'HTMLStencilElement' and 'HTMLStencilElement'. Named property 'componentOnReady' of types 'HTMLStencilElement.'
我们在封装Lib模块的时候@ionic/angular版本不一致导致,为了做兼容,我们可以把这个组件库依赖项写到dependencies里。
{
"dependencies": {
...
},
"devDependencies": {
"@ionic/angular": "^4.0.1"
}
}
Lib模块在开发的时候好好的,但是在别的项目了引入之后build编译报一些依赖项错误
检查以下看是否是我们对应Lib模块库package.json里的dependencies配置选项里锁死里某个package包的版本导致其他项目使用的时候版本不兼容编译报错。
项目本地运行好好的,但是一发到线上就报编译错误...
仔细查阅报错信息,看看是不是对应的依赖package包最近有更新,更新的版本是否有向下兼容,如果没有就锁住当前版本(PS:使用里package-lock.json的除外)。