组件库包名规范与本地 Link 调试
概述
上一节通过相对路径导入 dist 文件、再改消费方 TS 配置跑通了 Playground,但这只是教学演示。真实组件库开发应该让「包名、产物命名、入口字段和消费方式」形成一整套闭环:组件库打包后本身就像一个规范包,使用方按包名安装、按包名导入,类型声明和样式导出都由包自身负责说明。本文围绕这个闭环,讲清三个容易混淆的命名概念、build.sourcemap 与 .d.ts 两类产物、package.json 入口字段的正确写法,以及本地联调时 npm link、pnpm link 与 file: 的差异。
学习目标
- 把「演示版组件库」推进到「可发布组件库」,让包名、产物名、入口字段、消费方式形成闭环
- 区分 npm 包名、
build.lib.fileName、build.lib.name三个命名概念 - 理解
build.sourcemap与.d.ts分别解决调试体验与类型消费体验 - 用
unplugin-dts/vite生成类型声明,并正确配置tsconfigPath、outDir、rollupTypes - 分清
package.json里type、types、main、module、exports的分工 - 掌握
npm link/pnpm link/file:的差异,以及build:watch显式脚本的写法
一、从「演示」走向「可发布」的闭环
手工把打包结果拷贝到使用方目录、再改消费方 TS 配置,这两个动作都能临时跑通,但都不是标准组件库使用方式。「能 import 到」不等于「这个包的设计已经合理」。真正的包消费链路应该尽量逼近未来的发布链路:使用方按包名安装、按包名导入,类型声明和样式导出都由包自身说明。
二、三个命名概念不能混为一谈
包名可以从 package.json 读取,但必须分清三件事:
package.json的name:npm 包名,例如view-components或@scope/view-components;build.lib.fileName:决定输出文件名,可以参考包名,但通常需要清洗成合适的文件名;build.lib.name:主要给umd/iife格式使用,是运行时全局变量名,必须是合法 JS 标识符,例如ViewComponents。
如果包名带有连字符 - 或 scope 前缀 @scope/,都不适合直接作为 UMD 全局名。实践中通常从 package.json.name 派生文件名,并单独定义全局名。
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "node:path";
import pkg from "./package.json";
const packageName = pkg.name.replace(/^@[^/]+\//, "");
const globalName = "ViewComponents";
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, "src/index.ts"),
name: globalName,
formats: ["es", "umd"],
fileName: (format) => `${packageName}.${format}.js`
}
}
});三、build.sourcemap 与 .d.ts 是正式交付的一部分
build.sourcemap: true 让构建产物和源码之间保留映射,方便本地调试;.d.ts 让使用方在导入组件时拿到类型提示,避免再去手工打开 allowJs 或额外调 TS 配置兜底。从工程交付视角看:JS 产物解决「能运行」,.map 解决「能调试」,.d.ts 解决「能被 IDE 和 TS 正确消费」。这两类产物都属于正式组件库交付的一部分。
课程里用的是 vite-plugin-dts,按当前生态更推荐它演进后的 unplugin-dts/vite:可以从 .ts(x) 和 .vue 源码生成声明文件,在 Library Mode 下直接使用,并通过 rollupTypes: true 把类型合并成一个文件。但插件不是装上就万事大吉,通常还要显式指定 tsconfigPath(如 ./tsconfig.app.json),并按需配置 outDir。
import dts from "unplugin-dts/vite";
plugins: [
dts({
tsconfigPath: "./tsconfig.app.json",
outDir: "dist",
rollupTypes: true
})
]rollupTypes: true 是「合并类型文件」,不是运行时 Rollup 配置。如果声明文件缺失,先排查 tsconfig 的 include 是否覆盖到了源码。
四、package.json 入口字段分工
最容易写错的是把 type 和 types 混在一起:type 描述包的模块系统语义(常见 "module"),types 才指向 TypeScript 类型声明入口(如 "./dist/index.d.ts")。此外,main 是 CommonJS 或兼容入口,module 是传统 bundler 的 ESM 入口,exports 是现代包导出声明,可以更明确地控制 import / require / 子路径导出。
{
"name": "view-components",
"private": false,
"files": ["dist"],
"type": "module",
"main": "./dist/view-components.umd.cjs",
"module": "./dist/view-components.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/view-components.js",
"require": "./dist/view-components.umd.cjs"
},
"./style.css": "./dist/view-components.css"
}
}注意几点:private: true 的包不能发布到 npm;导出 CSS 时 exports 里应使用子路径键 "./style.css",而不是随手写一个 style 属性。
五、本地联调:link 与 file: 的差异
包还没发布到 npm,又想在另一个项目里像第三方包一样测试,常见有三种路线:npm link、pnpm link、file: 本地依赖。三者语义不同:
npm link:两步式全局符号链接,包名取自package.json.name,不是目录名;pnpm link:可全局 link,也可直接pnpm link ../pkg,但它不会自动安装被链接包自己的依赖;file::在 pnpm 文档里被明确拿来与pnpm link对比,它会把本地包作为依赖接入,并同时处理依赖安装。
在 pnpm 体系下,本地验证组件库消费链路时,file:../view-components 往往比手动 link 更稳、更接近真实依赖管理。
{
"dependencies": {
"view-components": "file:../view-components"
}
}六、用显式 build:watch 避开多层脚本透传
课程里对比了 npm run build -- --watch 与 pnpm run build --watch,背后确有差异:官方文档说明 npm run 的参数只会传给当前脚本,嵌套脚本不会自动深层透传;pnpm run 会把脚本名后的参数追加给执行脚本。但从工程实践看,最稳的不是研究谁传得更「聪明」,而是直接写一个明确的 build:watch 脚本,避开多层脚本嵌套带来的歧义。
{
"scripts": {
"build": "vite build",
"build:watch": "vite build --watch"
}
}七、.tsbuildinfo 是增量缓存,应忽略而非提交
工程目录里多出来的 .tsbuildinfo 通常来自 TypeScript 的增量编译(--build / composite)。它保存上一次构建的项目图信息,目的是加快后续编译速度,不会影响运行时代码,可以安全删除。对组件库项目最合理的处理是:保留它作为本地增量缓存,但把它加进 .gitignore。
*.tsbuildinfo常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 产物文件名、包名、全局名全写成同一个字符串 | 混淆了 npm 包名、文件名和 UMD 全局变量语义 | 分别维护 package.json.name、lib.fileName、lib.name |
给 package.json 写了 type: "./dist/index.d.ts" 不生效 | 把 type 和 types 混淆了 | 类型入口用 types,模块系统才用 type |
使用方还要改 allowJs、include 才能识别组件库 | 组件库没有正确生成或暴露 .d.ts | 用 unplugin-dts/vite 并正确配置 types / exports |
pnpm link 后包能连上,但类型或依赖不稳定 | link 和 file: 行为不同,且 link 不会自动装被链接包依赖 | 优先考虑 workspace 或 file:;必要时再用 link |
npm run build -- --watch 与 pnpm run build --watch 行为不一致 | 两个包管理器参数透传规则不同 | 直接定义 build:watch 为显式脚本 |
目录里出现 .tsbuildinfo 以为是脏文件 | 这是 TypeScript 增量编译缓存 | 加入 .gitignore,不需要提交 |