跳到主要内容

库的一些知识

· 阅读需 6 分钟
泥豆君
哇!是泥豆侠,我们没救了

嗯,怎么说呢。 也挺滑稽的。

写了很多的包,却不知道 'package.json' 下居然要加 "sideEffects": false 。 简直让人汗颜,就好像说一个士兵居然不知道怎么上膛。

备注

说来更尴尬的是,之前在 项目构建基础 中已经涉及了该内容,但是。。。

一、副作用

1. sideEffects: false

适用于 包内所有文件都无全局副作用 的情况,是纯组件库 、 工具函数库的最优配置。

1.1 使用条件

  • 包内仅导出「纯函数 、 纯组件 、 静态变量」,没有任何全局环境修改
  • 没有全局样式导入( import './global.css' , 仅组件内联样式 / CSS-in-JS 除外 )
  • 没有 polyfill/全局 API 扩展(如 Object.prototype.extend = ...
  • 没有全局注册行为( 如 window.GlobalUtils = ...
  • 没有副作用代码(如顶层 console.log 、 自动执行的函数 、 定时器/事件监听)

1.2 典型示例

package.json
{
"name": "shared-utils",
"version": "1.0.0",
"module": "dist/esm/index.js",
"sideEffects": false, // 显示声明:所有的文件无副作用
"exports": {
".": "./dist/esm/index.js",
"./Button": "./dist/esm/Button.js"
}
}

1.3 配置效果

消费端构建工具( Webpack/Vite/Rollup )会认为:包内所有的文件都是「无副作用」的,未被显式使用的模块可安全剔除(完美支持 Tree Shaking)。

1.4 禁忌场景(绝对不能设为 false

如果包内存在以下内容,设置为 false 会导致构建工具误删关键代码:

  • 全局样式文件(如 dist/global.css
  • polyfill 文件(如 dist/polyfill.js )
  • 顶层自动执行代码(如 src/index.js 中直接写 initGlobalConfig()

2. sideEffects: [文件路径数组]

适用于 包内大部分无副作用,但存在少量有全局副作用的文件 的情况,是带全局样式 、 polyfill 的组件库的标准配置。

2.1 适用条件

  • 包内大部分文件(组件 、 工具函数)无辅助用,可被 Tree Shaking
  • 存在少量 「必须保留的有副作用的文件」,请明确列举其路径
  • 副作用文件多为: 全局样式 、 polyfill 、 全局注册文件等
package.json
{
"name": "shared-utils",
"version": "1.0.0",
"module": "dist/esm/index.js",
"sideEffects": [
// 明确声明:以下文件有副作用,不可被删除
"./dist/css/reset.css", // 全局样式重置
"./dist/css/global.scss", // 全局公共样式
"./dist/polyfill.js" // 全局 API 兼容
"*.css", // 通配符:所有 .css 文件都有副作用(更便捷)
"*.scss" // 通配符:所有 .scss 文件都有副作用
],
"exports": {
".": "./dist/esm/index.js",
"./Button": "./dist/esm/Button.js",
"./css/reset": "./dist/css/reset.css"
}
}

2.2 配置效果

  • 数组内的文件:无论是否被显式使用,都会被消费端构建工具保留(避免副作用丢失)
  • 数组外的文件:视为「无副作用」,未被使用时会被 Tree Shaking 剔除

2.3 路径写法技巧

  • 支持 精确路径./dist/css/reset.css (精准匹配单个文件)
  • 支持 通配符*.css (匹配所有后缀为 .css 的文件) 、 ./dist/css/* (匹配 css 目录下所有文件)
  • 支持 目录匹配./dist/styles/ (匹配 styles 目录下所有文件及子目录)

3. 整个包有副作用

  • 不配置 sideEffects 等价于 true ,不属于「不为 true
  • 显式声明有副作用: sideEffects: true
  • 效果 : 消费端构建工具会放弃对该包的 Tree Shaking ,即使只适用一个小模块,也会打包整个包的代码,导致体积冗余
  • 使用场景 : 极少使用,仅适用于包内所有文件都有强副作用,且无法列举的极端场景(几乎不存在)

二、依赖方式

区分 peerDependencies/dependencies/devDependencies 是为了避免重复打包、版本冲入的核心,尤其针对 styled-componentsreact 这类公共依赖:

依赖类型适用场景示例
peerDependencies宿主环境需提供的公共依赖"react": "^19.3.0"
dependencies库自身必须的、宿主项目无需感知的私有依赖小众工具库
devDependencies开发/编译依赖(如 Rollup 、 TS 、 babel )"rollup": "^4.54.0"
package.json
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"styled-components": "^6.0.0"
},
"peerDependenciesMeta": {
"react": { "optional": false }, // 必须提供,否则报错
"styled-components": { "optional": false }
},
"dependencies": {
"date-fns-tz": "^2.0.0" // 库内部私有依赖
},
"devDependencies": {
"rollup": "^4.12.0", // 开发依赖
"typescript": "^5.3.3" // 开发依赖
}
}

三、子包抽离

抽离子包的核心价值时候解耦与灵活性。当函数具备独立生命力(复用性、迭代速度、体积优势)时,值的拆分为 @主包/子包 ; 反之则优先保持主包简介。推荐从单一包开始,随需求演进逐步拆分,避免过度设计。

1. 可抽离场景

  • 高复用性需求 : 该模块被多个外部项目独立使用
  • 独立迭代与版本更新 : 函数需频繁更新,且更新节奏与主包不一致(如安全补丁、紧急修复)
  • 无需加载优化 : 函数体积较大,但非所有用户都需要。抽离后可让用户选择性安装,减少主包体积
  • 明确的功能边界 : 函数属于独立的功能域(如支付网关、日志服务),未来可能可扩展成完整模块
  • 协作与权限隔离 : 不同的团队维护不同的功能(如核心团队维护主包、算法团队维护子包)。子包有自己的独立权限、 CI/CD 流程

2. 不建议抽离场景

  • 低复用性 : 函数仅被主包内部使用,无外部调用需求 ➞ 保持内聚更简单
  • 强耦合性 : 函数深度依赖主包的内部状态或私有模块 ➞ 抽离需大量重构,成本高
  • 简单功能 : 函数是轻量级工具(如格式化辅助函数) ➞ 直接放在主包更易维护
  • 初期项目 : 项目未稳定时过早的抽离 ➞ 增加维护复杂度(版本同步、依赖管理)