{T}

组件库包名规范与本地 Link 调试

概述

上一节通过相对路径导入 dist 文件、再改消费方 TS 配置跑通了 Playground,但这只是教学演示。真实组件库开发应该让「包名、产物命名、入口字段和消费方式」形成一整套闭环:组件库打包后本身就像一个规范包,使用方按包名安装、按包名导入,类型声明和样式导出都由包自身负责说明。本文围绕这个闭环,讲清三个容易混淆的命名概念、build.sourcemap.d.ts 两类产物、package.json 入口字段的正确写法,以及本地联调时 npm linkpnpm linkfile: 的差异。

学习目标

  • 把「演示版组件库」推进到「可发布组件库」,让包名、产物名、入口字段、消费方式形成闭环
  • 区分 npm 包名、build.lib.fileNamebuild.lib.name 三个命名概念
  • 理解 build.sourcemap.d.ts 分别解决调试体验与类型消费体验
  • unplugin-dts/vite 生成类型声明,并正确配置 tsconfigPathoutDirrollupTypes
  • 分清 package.jsontypetypesmainmoduleexports 的分工
  • 掌握 npm link / pnpm link / file: 的差异,以及 build:watch 显式脚本的写法

一、从「演示」走向「可发布」的闭环

手工把打包结果拷贝到使用方目录、再改消费方 TS 配置,这两个动作都能临时跑通,但都不是标准组件库使用方式。「能 import 到」不等于「这个包的设计已经合理」。真正的包消费链路应该尽量逼近未来的发布链路:使用方按包名安装、按包名导入,类型声明和样式导出都由包自身说明。

二、三个命名概念不能混为一谈

包名可以从 package.json 读取,但必须分清三件事:

  1. package.jsonname:npm 包名,例如 view-components@scope/view-components
  2. build.lib.fileName:决定输出文件名,可以参考包名,但通常需要清洗成合适的文件名;
  3. build.lib.name:主要给 umd / iife 格式使用,是运行时全局变量名,必须是合法 JS 标识符,例如 ViewComponents

如果包名带有连字符 - 或 scope 前缀 @scope/,都不适合直接作为 UMD 全局名。实践中通常从 package.json.name 派生文件名,并单独定义全局名。

ts
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

ts
import dts from "unplugin-dts/vite";

plugins: [
  dts({
    tsconfigPath: "./tsconfig.app.json",
    outDir: "dist",
    rollupTypes: true
  })
]

rollupTypes: true 是「合并类型文件」,不是运行时 Rollup 配置。如果声明文件缺失,先排查 tsconfiginclude 是否覆盖到了源码。

四、package.json 入口字段分工

最容易写错的是把 typetypes 混在一起:type 描述包的模块系统语义(常见 "module"),types 才指向 TypeScript 类型声明入口(如 "./dist/index.d.ts")。此外,main 是 CommonJS 或兼容入口,module 是传统 bundler 的 ESM 入口,exports 是现代包导出声明,可以更明确地控制 import / require / 子路径导出。

json
{
  "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 属性。

包还没发布到 npm,又想在另一个项目里像第三方包一样测试,常见有三种路线:npm linkpnpm linkfile: 本地依赖。三者语义不同:

  • npm link:两步式全局符号链接,包名取自 package.json.name,不是目录名;
  • pnpm link:可全局 link,也可直接 pnpm link ../pkg,但它不会自动安装被链接包自己的依赖;
  • file::在 pnpm 文档里被明确拿来与 pnpm link 对比,它会把本地包作为依赖接入,并同时处理依赖安装。

在 pnpm 体系下,本地验证组件库消费链路时,file:../view-components 往往比手动 link 更稳、更接近真实依赖管理。

json
{
  "dependencies": {
    "view-components": "file:../view-components"
  }
}

六、用显式 build:watch 避开多层脚本透传

课程里对比了 npm run build -- --watchpnpm run build --watch,背后确有差异:官方文档说明 npm run 的参数只会传给当前脚本,嵌套脚本不会自动深层透传;pnpm run 会把脚本名后的参数追加给执行脚本。但从工程实践看,最稳的不是研究谁传得更「聪明」,而是直接写一个明确的 build:watch 脚本,避开多层脚本嵌套带来的歧义。

json
{
  "scripts": {
    "build": "vite build",
    "build:watch": "vite build --watch"
  }
}

七、.tsbuildinfo 是增量缓存,应忽略而非提交

工程目录里多出来的 .tsbuildinfo 通常来自 TypeScript 的增量编译(--build / composite)。它保存上一次构建的项目图信息,目的是加快后续编译速度,不会影响运行时代码,可以安全删除。对组件库项目最合理的处理是:保留它作为本地增量缓存,但把它加进 .gitignore

gitignore
*.tsbuildinfo

常见问题

问题原因解决方案
产物文件名、包名、全局名全写成同一个字符串混淆了 npm 包名、文件名和 UMD 全局变量语义分别维护 package.json.namelib.fileNamelib.name
package.json 写了 type: "./dist/index.d.ts" 不生效typetypes 混淆了类型入口用 types,模块系统才用 type
使用方还要改 allowJsinclude 才能识别组件库组件库没有正确生成或暴露 .d.tsunplugin-dts/vite 并正确配置 types / exports
pnpm link 后包能连上,但类型或依赖不稳定linkfile: 行为不同,且 link 不会自动装被链接包依赖优先考虑 workspace 或 file:;必要时再用 link
npm run build -- --watchpnpm run build --watch 行为不一致两个包管理器参数透传规则不同直接定义 build:watch 为显式脚本
目录里出现 .tsbuildinfo 以为是脏文件这是 TypeScript 增量编译缓存加入 .gitignore,不需要提交

延伸阅读