本文由Claude3.7生成,总结了angular组件库如何实现按需引入,文章中提到的踩坑记录确实是本人和claude老师一起踩过的哈哈哈哈哈哈哈哈哈哈哈
前言
大家好!今天我要分享一个让我又爱又恨的技术实践 ------ Angular组件库的按需引入。这个看似简单的需求,实际操作起来却是"坑"满为患。不过别担心,跟着这篇指南,你也能让你的组件库"轻装上阵"!
为什么需要按需引入?
想象一下,你做了一个超棒的组件库,里面有20个组件,但用户可能只需要用到其中的2个。如果用户必须引入整个库,那就像点了一份小面却被送来了一桌满汉全席 ------ 既浪费资源又影响性能。
按需引入就是让用户能够只"点"他们需要的"菜",从而减小打包体积,提高应用性能。
实现方法:从理论到实践
1. 模块化设计
首先,每个组件都应该有自己的模块文件:
less
// loading.module.ts
@NgModule({
declarations: [LoadingDirective],
imports: [CommonModule],
exports: [LoadingDirective]
})
export class CheeseLoadingModule { }
这就像把每道菜都装在单独的盘子里,方便客人单点。
2. 二级入口文件配置
为每个组件创建二级入口文件:
javascript
// loading/public-api.ts
export * from './loading.directive';
export * from './loading.module';
同时配置ng-package.json:
bash
// loading/ng-package.json
{
"$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
}
这就像给每道菜都做了一个单独的菜单,并且标明了配料表。
3. 主模块导出子模块
kotlin
// cheese-tool.module.ts
@NgModule({
exports: [
CheeseDebounceModule,
CheeseLoadingModule,
CheesePlaceholderPicModule,
CheeseProgressModule
]
})
export class CheeseToolModule { }
这相当于提供了一个"套餐选项",但客人仍然可以单点。
4. 主入口文件导出所有模块
javascript
// public-api.ts
export * from './cheese-tool.module';
export * from './lib/loading/loading.directive';
export * from './lib/loading/loading.module';
// ... 其他导出
这是总菜单,列出了所有可用的选项。
5. 配置package.json的exports字段
json
"exports": {
".": {
"sass": "./scss/index.scss",
"default": "./fesm2015/cheese-tool.js"
},
"./debounce": "./fesm2015/cheese-tool-src-lib-debounce.js",
"./loading": "./fesm2015/cheese-tool-src-lib-loading.js",
// ... 其他导出
}
这是告诉客人每道菜在哪个位置,方便他们直接找到。
踩坑记录:那些年我们一起追过的bug
坑1:模块名不匹配
最初我们配置的exports路径是:
json
"./loading": "./fesm2015/cheese-tool-loading.js"
但实际构建出来的文件名是:
css
cheese-tool-src-lib-loading.js
这就像告诉客人3号桌有菜,结果菜在5号桌 ------ 当然找不到了!
坑2:二级入口点配置错误
我们尝试使用secondaryEntryPoints
配置:
json
"secondaryEntryPoints": {
"debounce": "src/lib/debounce"
}
结果报错:不允许属性 secondaryEntryPoints
。
原来是ng-packagr的配置方式变了,需要为每个组件单独创建ng-package.json文件。
坑3:类型声明找不到
即使exports配置正确,TypeScript仍然报错:
scss
找不到模块"cheese-tool/loading"或其相应的类型声明。ts(2307)
这需要在package.json中添加typesVersions字段:
json
"typesVersions": {
"*": {
"loading": ["./src/lib/loading/public-api.d.ts"]
}
}
这就像菜单上有图片,但客人看不到 ------ 需要给每道菜配上清晰的图片说明。
使用方法:享用成果
经过一番折腾,终于可以愉快地按需引入了:
typescript
// 只引入需要的组件
import { CheeseLoadingModule } from 'cheese-tool/loading';
import { CheeseProgressModule } from 'cheese-tool/progress';
@NgModule({
imports: [
CheeseLoadingModule,
CheeseProgressModule
]
})
export class AppModule { }
这样,用户就可以只"点"他们需要的"菜",应用也变得更轻量、更高效!
总结
实现Angular组件库的按需引入,关键在于:
- 模块化设计
- 二级入口配置
- 正确的导出路径
- 类型声明配置
虽然过程有些曲折,但成果是值得的 ------ 用户获得了更好的体验,我们也掌握了更专业的技能。
希望这篇指南能帮助到你,让你的组件库也能"轻装上阵"!如果有任何问题,欢迎在评论区交流讨论~
番外:关于 exports 和 typesVersions 路径的解释
fesm2015
这个名称其实挺有意思的,它是 Angular 打包系统中的一个专业术语,全称是 "Flat ECMAScript Module 2015"(扁平化的 ECMAScript 2015 模块)。
fesm2015 是什么?
简单来说,fesm2015
是 Angular 打包工具(ng-packagr)生成的一种特定格式的输出目录,它包含了:
- 扁平化(Flat):将多个小模块合并成单个文件,减少了网络请求
- ES2015:使用 ECMAScript 2015 语法(也就是 ES6)
- 模块格式:采用标准的 ES 模块格式(import/export)
这就像是厨师把多种食材预先处理好,打包成半成品,方便后续烹饪使用。
为什么路径不一样?
在我们的配置中:
json
"exports": {
"./loading": "./fesm2015/cheese-tool-src-lib-loading.js"
},
"typesVersions": {
"*": {
"loading": ["./src/lib/loading/public-api.d.ts"]
}
}
这两个路径指向不同的文件是因为它们服务于不同的目的:
- exports 路径 :指向编译后的 JavaScript 代码(
./fesm2015/cheese-tool-src-lib-loading.js
)
-
- 这是实际运行时使用的代码
- 已经经过了打包、优化和编译
- 位于构建输出目录中
- typesVersions 路径 :指向 TypeScript 类型声明文件(
./src/lib/loading/public-api.d.ts
)
-
- 这是开发时 TypeScript 编译器使用的类型信息
- 帮助编辑器提供代码补全和类型检查
- 通常指向源码目录或类型声明目录
这就像餐厅菜单上既有成品菜的照片,又有详细的配料表 ------ 照片让你知道菜的样子(运行时代码),配料表让你了解菜的成分(类型声明)。
为什么需要这样配置?
这种双重配置确保了:
- 运行时:JavaScript 引擎能找到正确的代码文件执行
- 开发时:TypeScript 编译器能找到正确的类型声明,提供智能提示
如果没有正确配置 typesVersions
,就会出现我们之前遇到的错误:
scss
找不到模块"cheese-tool/loading"或其相应的类型声明。ts(2307)
这就像客人能吃到菜(代码能运行),但看不到配料表(没有类型提示)------ 体验不够好!
通过这种配置方式,我们既保证了代码能正确运行,又提供了良好的开发体验,真是一举两得!