3、依赖声明文件:package.json、setup.py、Cargo.toml 等配置文件的作用与结构
依赖声明文件,说白了就是 SDK 的「身份证」加「购物清单」。
我刚开始做 SDK 发布时,总觉得这些文件就是个形式。直到有一次,我发布的 Python 包因为 setup.py 里漏写了一个依赖,导致用户安装后直接报错……嗯,从那以后我再也不敢小看这些配置文件了。
3.1 它们到底在干什么?
每个语言生态都有自己的包管理工具。它们都依赖一个核心文件来声明三件事:
- 我是谁 —— 包名、版本号、作者、描述
- 我需要什么 —— 运行时依赖、开发依赖
- 我怎么用 —— 入口文件、脚本、构建配置
你想想看,如果没有这些文件,用户下载了你的 SDK,连依赖都装不全,那还怎么用?
核心原则:依赖声明文件是 SDK 与包管理器之间的契约。你写清楚,它帮你搞定一切。
3.2 三大主流生态的配置文件
我平时主要接触 JavaScript、Python 和 Rust 三个生态。它们的配置文件各有特点,但核心逻辑是相通的。
3.2.1 package.json(JavaScript / TypeScript)
这是 Node.js 生态的标配。我个人习惯用 npm init 生成初始模板,然后手动调整。
{
"name": "@myorg/sdk-core",
"version": "2.1.0",
"description": "核心 SDK 包",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest",
"prepublishOnly": "npm run build"
},
"dependencies": {
"axios": "^1.6.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.3.0",
"jest": "^29.7.0"
},
"peerDependencies": {
"react": ">=17.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
这里有几个关键字段,我重点说一下:
| 字段 | 作用 | 我的经验 |
|---|---|---|
name |
包名,必须唯一 | 建议加 scope,比如 @公司名/包名 |
version |
语义化版本号 | 严格遵循 semver,别乱改 |
dependencies |
运行时依赖 | 能少就少,减少用户安装体积 |
peerDependencies |
宿主环境依赖 | 比如 React 插件,让用户自己装 React |
engines |
限制 Node 版本 | 我曾经没写这个,用户用 Node 14 跑我的 SDK,直接崩了 |
小技巧:用 ^ 符号表示兼容版本,比如 ^1.6.0 表示 >=1.6.0 且 <2.0.0。这样既能获取小版本更新,又不会引入破坏性变更。
3.2.2 setup.py(Python)
Python 生态的配置文件。说实话,Python 的打包工具变化挺大的,从 setup.py 到 setup.cfg 再到 pyproject.toml。但我个人觉得,setup.py 依然是最直观的。
from setuptools import setup, find_packages
setup(
name="my-sdk",
version="1.2.0",
description="一个实用的 SDK",
packages=find_packages(exclude=["tests*"]),
install_requires=[
"requests>=2.28.0",
"pydantic>=2.0.0",
],
extras_require={
"dev": ["pytest>=7.0.0", "black"],
"ml": ["numpy>=1.24.0", "scikit-learn>=1.3.0"],
},
python_requires=">=3.9",
entry_points={
"console_scripts": [
"my-sdk-cli=my_sdk.cli:main",
],
},
)
这里有个坑,我曾经踩过:
注意:install_requires 里不要写死版本号,比如 requests==2.28.0。这样会和其他包冲突。用 >= 或者 ~= 更安全。
extras_require 是个好东西。你可以把可选依赖分组,用户按需安装:
pip install my-sdk—— 只装核心依赖pip install my-sdk[ml]—— 装核心 + 机器学习相关依赖
3.2.3 Cargo.toml(Rust)
Rust 的配置文件,我个人觉得是三个里面设计最优雅的。它用 TOML 格式,清晰又简洁。
[package]
name = "my-sdk"
version = "0.4.1"
edition = "2021"
description = "高性能 Rust SDK"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.35", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
[dev-dependencies]
criterion = "0.5"
[features]
default = ["std"]
std = []
async = ["tokio", "reqwest"]
[lib]
name = "my_sdk"
path = "src/lib.rs"
Rust 的依赖管理有几个特点:
- 特性开关(features):可以按需编译,减少二进制体积。我在项目中就用这个做「轻量版」和「完整版」
- 语义化版本:Cargo 默认用
^语义,和 npm 类似 - 编译缓存:Cargo 的依赖缓存机制很智能,第二次编译快很多
我的习惯:在 Cargo.toml 里尽量少开 features。默认只开最核心的,用户需要什么自己加。这样能避免「依赖地狱」。
3.3 依赖声明文件的通用结构
虽然语法不同,但核心结构其实差不多。我画了一张图,帮你理清思路:
3.4 版本号声明的最佳实践
依赖声明里,版本号怎么写,其实很有讲究。我总结了几条经验:
- 用范围,别用精确版本 ——
^1.2.0比1.2.0好,给用户留点弹性 - 锁定关键依赖 —— 如果某个依赖的 API 经常变,可以锁死大版本,比如
~2.3.0 - 区分依赖类型 —— 运行时依赖、开发依赖、可选依赖,分开放,别混在一起
- 定期更新 —— 我习惯每季度跑一次
npm outdated或pip list --outdated,看看有没有安全更新
我曾经踩过的坑:有一次我把 devDependencies 里的测试框架写到了 dependencies 里。结果用户安装我的 SDK 时,莫名其妙装了一堆测试工具……嗯,从那以后我每次发布前都会检查一遍依赖分类。
3.5 多语言项目的依赖管理
如果你像我一样,维护一个跨语言 SDK(比如 Python 核心 + JavaScript 前端),那依赖声明文件就更重要了。
我的做法是:
- 每个语言独立维护自己的配置文件
- 用 CI 脚本统一检查版本号是否一致
- 发布时打 tag,确保所有语言的版本号同步
说白了,依赖声明文件就是 SDK 的门面。写得好,用户用得爽;写得差,用户骂得狠。我见过太多因为 package.json 里少写一个字段导致整个发布流程卡住的案例了。
最后一个小建议:每次发布前,用 npm pack --dry-run 或 python setup.py sdist 模拟一下打包过程。看看哪些文件会被包含进去,哪些依赖会被安装。提前发现问题,总比用户反馈强。
好了,关于依赖声明文件,我就说这么多。记住一句话:配置文件写清楚,SDK 发布少踩坑。