Skip to content

088. 模块注册器与组件打包配置 ​

学习目标 ​

这一节实现通用表格的模块注册器,并开始把组件打包成可发布的包。

学完后,你应该能理解:

  • 模块注册器解决什么问题;
  • 全局模块注册器和局部模块注册器有什么区别;
  • 如何添加、删除、覆盖模块;
  • 为什么模块安装要支持前置和后置覆盖;
  • package.json 里哪些字段影响发包;
  • main、module、types 分别代表什么;
  • tsup 和 tsc 在打包里分别负责什么。

模块注册器的目标 ​

前面模块还是手动安装。

类似:

ts
useConfigModule(autoTable)
useStateModule(autoTable)
useHandlerModule(autoTable)
useRenderModule(autoTable)

这种写法能跑。

但外部项目无法方便地添加自己的模块。

所以需要一个模块注册器。

它负责维护模块列表,并按顺序安装到表格对象上。

模块结构 ​

每个模块都包含几个基本信息:

  • seq:执行顺序;
  • key:模块标识;
  • use:模块函数。

模块函数接收 autoTable 对象。

执行后,把自己的能力挂到 autoTable 上。

例如:

ts
{
  seq: 10,
  key: "queryForm",
  use: useQueryFormModule,
}

为什么模块可以为空 ​

模块映射里允许某个模块值为空。

这是为了删除模块。

如果想删除某个默认模块,可以把对应 key 的值置空。

安装时过滤掉空模块。

这样就能实现:

  • 默认组件带查询表单;
  • 某个项目不需要查询表单;
  • 项目局部把查询表单模块删除。

添加模块 ​

模块注册器提供添加模块的方法。

添加时,把模块放进内部映射。

key 是模块标识。

value 是模块定义。

可以理解成:

ts
moduleMapper[key] = module

后续安装时,注册器会读取这个映射。

安装模块 ​

安装模块时,注册器会做几件事:

  1. 合并默认模块、本地模块、覆盖模块;
  2. 过滤空模块;
  3. 按 seq 排序;
  4. 依次执行模块函数;
  5. 把能力安装到 autoTable 对象上。

核心思路还是:

ts
modules.forEach(module => {
  module.use(autoTable)
})

只是模块来源更灵活。

全局模块注册器 ​

全局模块注册器放在组件库层面。

它影响所有通用表格实例。

例如组件库默认带:

  • 配置模块;
  • 状态模块;
  • 事件模块;
  • 渲染模块;
  • 查询表单模块;
  • 搜索栏模块。

只要使用这个通用表格,都会先加载这些全局模块。

局部模块注册器 ​

局部模块注册器挂在具体的表格 Hook 或表格实例上。

它只影响当前这类表格。

例如项目里同时对接两个后端系统:

  • useCimTable;
  • useErpTable。

它们可以基于同一个通用表格,但做不同扩展。

一个可以删除查询表单。

另一个可以额外添加按钮。

这就是局部模块注册器的价值。

删除模块示例 ​

假设某个表格不需要查询表单。

可以在局部注册器里删除 queryForm 模块。

删除后,查询表单本身不会渲染。

它往搜索栏添加的按钮也不会出现。

因为整个查询表单模块没有执行。

这比只隐藏 UI 更干净。

添加模块示例 ​

局部注册器也可以添加模块。

例如给 ERP 表格添加一个自定义按钮。

这个模块可以通过搜索栏渲染钩子往搜索区域注册内容。

按钮点击后,可以读取当前表格状态。

例如读取当前数据条数:

ts
autoTable.state.data.length

这说明模块不仅能渲染 UI,也能使用表格对象上的状态和方法。

覆盖模块示例 ​

覆盖模块是用同一个 key 注册新的模块。

例如覆盖默认查询表单。

新的模块可以不再渲染原来的查询区域,而是在表格底部渲染一段提示或公告。

这样就实现了“同一个表格能力,不同项目有不同呈现方式”。

为什么要同时支持三种动作 ​

复杂组件在不同项目里需求差异很大。

只支持添加还不够。

还需要:

  • 删除默认功能;
  • 覆盖默认功能;
  • 调整顺序;
  • 在默认模块前后插入模块。

这样组件库才能被多个项目复用。

否则组件越强,越容易因为默认功能太多而不适合某个项目。

进入组件打包 ​

模块注册器完成后,下一步是把通用表格打成 npm 包。

这样其他项目安装包后,也能通过模块注册器添加或删除模块。

打包前要先处理几个文件:

  • package.json;
  • 打包入口文件;
  • tsup.config.ts;
  • 类型声明生成配置;
  • 需要导出的工具和服务。

package.json 的包名 ​

name 是包名。

别人安装或引入包时会用它。

例如:

ts
import { useAutoTable } from "@scope/agent"

这里的 @scope/agent 就来自包名。

项目初始化时,name 可能只是文件夹名。

发包前需要改成真正要发布的包名。

private ​

private 控制包是否允许发布。

如果要发布到 npm,不能是 true。

公开 npm 包通常需要:

json
{
  "private": false
}

如果公司要发私有包,一般不是直接发公开 npm。

而是搭建私有 npm 仓库。

课程里提到可以用 Nexus 这类工具做私有仓库。

版本号 ​

公开 npm 的版本不能重复。

同一个包已经发布过 1.0.0,下一次就必须换版本号。

私有仓库是否允许覆盖同版本,取决于仓库配置。

所以正式发包要养成改版本号的习惯。

npm 包可信度 ​

npm 上有些包会显示是否来自可信发布流程。

如果包不是通过标准 CI/CD 流程发布,而是本机手动发布,可信度会差一些。

原因是无法确认发布者环境是否安全。

例如开发者 token 泄露后,攻击者也可能发布恶意版本。

这也是为什么大型开源包会重视发布流程。

type: "module" ​

type: "module" 表示这个包按 ES Module 处理。

没有它时,有些打包器可能按 CommonJS 理解。

现代前端项目通常更偏向 ES Module。

所以组件库发包时,需要明确模块格式。

main、module、types ​

这三个字段定义入口。

main 通常给 CommonJS 使用。

module 通常给 ES Module 使用。

types 指向 TypeScript 类型声明文件。

例如:

json
{
  "main": "dist/main.cjs",
  "module": "dist/main.js",
  "types": "dist/main.d.ts"
}

使用者只写包名引入时,打包器会根据这些字段找到真正入口。

files ​

files 控制发布到 npm 的文件范围。

组件库打包后,只需要发布 dist。

所以可以配置:

json
{
  "files": ["dist"]
}

这样源码、临时文件、测试文件不会被一起发布。

打包入口文件 ​

组件库需要一个入口文件。

例如:

ts
// src/main.ts
export { useAutoTable } from "./auto-table"

课程里先只导出通用表格相关内容。

完整组件库还要导出更多组件、类型和工具。

但这里目标是验证通用表格打包和扩展能力。

所以先做最小可用导出。

tsup.config.ts ​

tsup 用来把源码打包成发布文件。

配置里要指定:

  • 入口文件;
  • 输出格式;
  • 是否清理 dist;
  • 是否处理样式;
  • 哪些依赖不打进包里。

输出格式常见有三种:

  • esm:给 ES Module 使用;
  • cjs:给 CommonJS 使用;
  • umd:给浏览器 script 标签使用。

这一节先只打 esm。

正式组件库通常会同时输出多种格式。

为什么不用 tsup 生成类型 ​

tsup 可以生成类型声明。

但这里用了声明合并和类型扩展。

生成出来的类型结构不够理想。

所以课程里选择:

  • tsup 负责打包 JS 和样式;
  • tsc 负责生成类型声明。

这比全部交给一个工具更可控。

tsconfig 配置 ​

打包用的 tsconfig 和开发运行用的 tsconfig 可以不同。

开发项目需要服务 Vite、React、Node 配置。

组件库打包则需要生成声明文件。

关键配置是:

json
{
  "declaration": true
}

它告诉 TypeScript 生成 .d.ts 类型声明。

外部依赖 ​

打包组件库时,不应该把所有依赖都塞进包里。

例如 React、Ant Design 这类通常作为外部依赖。

否则使用者项目里可能出现重复 React 或样式冲突。

所以打包配置需要 external。

它表示哪些依赖不打包进产物。

样式打包 ​

通用表格组件可能引用样式文件。

例如 Sass。

所以打包配置要能处理样式。

生成后,dist 里会出现 JS 文件、样式文件和类型声明文件。

使用者安装包后才能正确拿到组件样式。

导出 Token 服务 ​

测试新项目时,表格请求后端需要 token。

所以课程里把 token 服务也导出。

新项目可以直接设置一个测试 token。

这样在验证组件包时,不需要重新实现一套认证逻辑。

打包产物 ​

运行打包命令后,会生成 dist。

里面包含:

  • 入口 JS;
  • 样式文件;
  • 类型声明文件;
  • 列组件相关声明;
  • 通用表格相关声明。

入口 JS 使用 ES Module 输出。

从产物可以看到 export 语法。

这一节的核心 ​

模块注册器让通用表格从“内部固定模块”变成“外部可增删改模块”。

组件打包让这种扩展能力可以跨项目复用。

这一节还没有系统展开完整组件库工程化。

但已经跑通关键链路:

  • 模块注册;
  • 全局和局部扩展;
  • 包入口导出;
  • JS 打包;
  • 类型声明生成;
  • 准备发布到 npm。

AI Agent 课程学习文档。