这一章讲 sag-icons 的 npm 包骨架——package.json 关键字段、TypeScript 双层配置、为什么选 Vite 库模式。
你可能觉得这些都是"npm 包常识",没什么好讲的。但 sag-icons 有几个有意的设计值得拎出来:
exports 字段精确控制子路径导入tsconfig.json 和 tsconfig.build.json 用途分离vite.config.ts 选了 ES-only,放弃 CJS读完这一章你会知道为什么 sag-icons 的 package.json 长那个样子,以及怎么抄。
package.json 全文解读sag-icons 的 package.json:
对应文件:
package.json
下面逐个字段拆。
type: "module"声明这个包是 ESM。所有 .js 文件默认按 ES Module 解析,而不是 CommonJS。
这对 sag-icons 来说没问题,因为:
import "@jujiuyey/sag-icons",ESM 完全够用代价是不支持特别老的工具链(Babel 6、Webpack 4)。sag-icons 的目标用户是现代前端,这条线是清楚的取舍。
main /module /typesmain:Node.js / 旧工具的入口module:现代打包器的入口(webpack 4+ / rollup 优先看)types:TypeScript 类型入口sag-icons 这三个字段都指向同一个文件——./dist/index.js。因为 sag-icons 只发 ESM 一份产物,没有 CJS 兜底。
对应字段:
package.json:6-8
exports —— 这是关键exports 是 Node.js 12+ 引入的精确子路径控制机制。它比 main / module 更严格:
import "@jujiuyey/sag-icons" 走 "." 块"types" 条件 → TS 类型"import" 条件 → ESM 产物为什么不写 "require"?因为 sag-icons 只发 ESM。强写 require 反而会让 Node.js CJS 用户报错,提示明确。
现在 sag-icons 的 exports 只暴露了根路径。但理论上你可以加子路径:
让用户可以:
这套子路径设计是 sag-icons 的精华,第 7 章会细讲。
filesnpm publish 时只把 dist/ 目录发到 npm。src/、docs/、node_modules/ 这些都不发。
如果忘了写 files,npm 会发整个仓库——你的 README.md、开发脚本、文档站都会被发出去,包体积膨胀,也泄露内部细节。
对应字段:
package.json:15-17
keywords /licensekeywords 影响 npm 搜索排序。icon / svg 是必须的,web-components / custom-elements / framework-agnostic 是 sag-icons 的差异化卖点。
license: MIT 是事实标准——允许商业用、修改、分发。
sag-icons 有两个 tsconfig:
tsconfig.json —— 给 IDE 和 tsc --noEmit 用(开发期)tsconfig.build.json —— 给 pnpm build:lib 用(产物生成)tsconfig.json注意 noEmit: true——开发期不产生任何文件。只做类型检查(pnpm typecheck)。
另外 allowImportingTsExtensions: true 允许 import "./foo.ts"。这在源码里很方便。
tsconfig.build.json关键差别:
declaration: true:生成 .d.ts 类型声明文件outDir: "./dist":产物输出到 dist/rootDir: "./src":源码根目录include:精确指定要打包的文件——排除了 src/docs-components/(文档站用的演示组件,不该发到 npm)exclude:排除测试、文档组件把 tsconfig.json 和 tsconfig.build.json 分开,是因为:
import "./foo.ts"、不开 declaration、包含所有文件把这两件事混在一个 tsconfig 里会两头不讨好。sag-icons 用两层配置解决这个问题。
对应文件:
tsconfig.json、tsconfig.build.json
sag-icons 用 Vite 而不是 tsup / rollup / unbuild。理由:
build.lib 一段配置就够sourcemap: true 一行几个关键点:
emptyOutDir: false:不清理 dist/,因为 tsc 已经先把 .d.ts 放那了。Vite 的 JS 产物会和 .d.ts 共存。lib.entry:Vite 从 src/index.ts 开始打包formats: ["es"]:只发 ESMfileName: () => "index.js":固定文件名(不区分 hash),方便 package.json 引用minify: false:保留可读性。图标库不需要极致压缩,gzip 已经够小对应文件:
vite.config.ts
tsup 也是 esbuild 包装的好选择,更适合纯库。但 sag-icons 同时跑 tsc(生成类型)和 vite build(打包 JS),这套流水线已经够稳定了。
要不要简化成 tsup 一把梭?你可以试:
sag-icons 没切是因为切换要重新跑 pnpm build:lib 验证,稳定优先。
sag-icons 把 CSS 写成 .ts 文件导出字符串:
而不是传统的 .css 文件。这是有意为之:
src/index.ts 里 import { svgIconCss },然后塞 <style> 到 headimport "@jujiuyey/sag-icons" 多次也只注入一次(看 injectStyles() 函数)import 一次主入口就完事import "@jujiuyey/sag-icons/css/svg-icon.css" 拿原始 CSSpurgecss)扫 CSS 时看不到这部分样式sag-icons 的 CSS 很简单(一个 :root 变量 + 几个选择器),PostCSS 处理的收益不大。所以选了运行时字符串方案。
.css 文件为了兼容传统 CSS 用户,sag-icons 还提供原始 CSS 入口:
这里 .css 文件需要 Vite 单独处理——但 sag-icons 现在的 vite.config.ts 只配置了 lib.entry,没有处理 CSS。
这是 sag-icons 当前的一个小坑:用户 import "@jujiuyey/sag-icons/css/svg-icon.css" 会找不到文件,因为 vite build 没把 CSS 拷到 dist。
教程的第 8 章会讲怎么补这个。
build:lib 双步流水线这两步各管一件事:
tsc 生成类型跑 tsconfig.build.json,产出:
这一步只产类型和模块化 JS,不做 tree-shaking 或打包。慢但稳。
vite build 产打包后的 JS跑 vite.config.ts,清掉 dist/ 里 tsc 产的多文件 JS,重新打成一个 bundle:
emptyOutDir: false 让 Vite 不清空 dist,所以 .d.ts 还在。Vite 只覆盖 .js 文件。
这种"双流水线"配置是 Vite 库模式的常见 pattern:tsc 产类型,vite 产 JS。
pnpm dev 是什么sag-icons 有个反直觉的地方:pnpm dev 不是开发图标库——是跑文档站:
sag-icons 本身的开发流程其实很简单:
src/data/icons/*.ts 加新图标src/css/svg-icon.ts 调样式src/web-components/sag-icon.ts 调组件逻辑pnpm typecheck 验类型pnpm build:lib 验产物pnpm dev 跑文档站看效果文档站 (rspress dev) 是给用户看的,不是给自己开发用的。
对应文件:
package.json:19-22
到这里你应该能:
package.json 每个字段tsconfig.json 和 tsconfig.build.json 的用途pnpm build:lib 整个流水线下一章进入 sag-icons 的核心——Web Component。这是为什么 sag-icons 一个包能跨 React / Vue / 原生 HTML 的关键。
| 内容 | 路径 |
|---|---|
| package.json | package.json |
| 开发期 tsconfig | tsconfig.json |
| 构建期 tsconfig | tsconfig.build.json |
| Vite 配置 | vite.config.ts |
| CSS 字符串 | src/css/svg-icon.ts |