项目:my-ts-axios | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
| 项 | 要求 | 出处 |
|---|---|---|
| Node.js(库运行时) | >=14.0.0 | package.json engines |
| Node.js(开发/构建) | 建议 20+(README 说明;examples 使用的 Vite 8 有相应要求) | README.md |
| 包管理器 | npm(scripts 全按 npm 组织;form-data 走 optionalDependencies,未装也可构建运行但 Node 端 multipart 降级) | package.json |
| 操作系统 | 无特殊要求(脚本均为跨平台 npm scripts;rimraf 处理 Windows 删目录) | package.json |
运行时依赖策略值得单独说明:库零硬依赖。Node 内置模块(http/https/zlib/stream)由 Node 本身提供;form-data 声明在 optionalDependencies,src/core/adapters/http.ts 顶部用 try/catch require('form-data') 探测,未安装时 toNodeFormData 返回 null,请求体按字符串降级发送(http.ts 第 22~53 行注释)。
开发依赖可以按用途分四组理解,便于按需增删:构建组(rollup 及其四个插件、typescript、rimraf、@types/node)只服务于 npm run build;测试组(jest、ts-jest、jest-environment-jsdom、jasmine-ajax、jasmine-core 及其 @types)只服务于 npm test;示例组(vite、@vitejs/plugin-vue、vue、express、body-parser、cookie-parser、connect-multiparty、qs)只服务于 npm run dev 与示例构建——其中 Express 系虽然出现在 devDependencies,却是在 Vite 插件进程内运行的,不产生独立服务进程(8088 的 server2 除外);规范组(eslint 系、prettier、husky、lint-staged、commitlint、commitizen、cz-conventional-changelog)服务于代码质量与提交流程。四组之间没有耦合,例如给示例加新路由只需动示例组,库构建与测试完全不受影响。
在项目根目录执行(全部来自 package.json scripts)。命令的编排大量利用了 npm 生命周期钩子:prebuild 先于 build 自动执行(清理 dist),prepublishOnly 先于 npm publish 自动执行(测试+构建+冒烟),因此"直接跑 rollup/tsc 裸命令"与"跑 npm scripts"的行为并不等价——例如绕过 npm run build 直接执行 rollup -c 会跳过 dist 清理与类型声明产出,属于应避免的操作。
# 安装依赖
npm install
# 开发库本身:Rollup 监听模式,改动 src/ 即时增量构建到 dist/
npm run start # 等价 rollup -c rollup.config.mjs -w
# 跑浏览器示例:Vite dev server,监听 http://localhost:5173
npm run dev # 等价 vite --config examples/vite.config.ts
# 类型检查(tsc 全量编译检查,不产出 JS)
npx tsc --noEmit
# 代码检查 / 自动修复
npm run lint # eslint .
npm run lint:fix # eslint . --fix
# 单次构建(含类型声明)
npm run build # prebuild: rimraf dist → build:types(tsc 声明到 dist/types) → rollup
# 测试
npm test # jest --coverage
npm run test:watch # jest --coverage --watch
npm run test:prod # npm run lint && npm test -- --no-cache
# 构建示例站点(多页面输出到 examples/__dist__/)
npm run examples:build # vite build --config examples/vite.config.ts
# 发布前链路(npm publish 时自动触发)
npm run prepublishOnly # npm test && npm run build && node scripts/smoke-dist.cjsrollup.config.mjs 导出两个构建任务,均以 src/index.ts 为入口:
| 构建 | external | 插件链 | 产物(来自 package.json 字段) | 用途 |
|---|---|---|---|---|
| Node 消费 | ['form-data','http','https','zlib','stream'] | json → typescript → resolve → commonjs | dist/node/axios.cjs(main,format cjs,exports: 'default',sourcemap)+ dist/index.esm.js(module,format es) | require / 打包器 import |
| 浏览器消费 | 无(零 external) | json → typescript → alias(五个内置模块与 form-data → src/shims/node-builtins.ts) → resolve → commonjs | dist/browser/axios.umd.js(browser,format umd,name: 'axios',exports: 'default') | <script> 直接引入,window.axios 即 axios 函数 |
类型声明独立产出:npm run build:types 执行 tsc --declaration --emitDeclarationOnly --declarationDir dist/types,入口为 dist/types/index.d.ts(对应 typings 与 exports['.'].types)。
两个与正确性强相关的设计(配置内注释明确记载):
exports: 'default':ESM 源只有 default 导出,若 CJS 按 named 导出会发出 module.exports = { default: axios },普通 require() 拿到的是包装对象,破坏所有 CJS 消费方。指定 exports: 'default' 后 module.exports = axios 函数本身。src/shims/node-builtins.ts 为每个被替代的模块导出"调用即抛错"的占位函数(request、createGunzip、createInflate、createBrotliDecompress、Readable、Transform 及默认导出),使 UMD 产物自包含;http 适配器代码随包携带,但浏览器请求只会命中 xhr/fetch 适配器,占位实现仅在错误调用时抛出中文提示错误。从消费端视角看,这个三产物矩阵覆盖了全部主流接入方式:老的 Node 服务用 require 命中 CJS;现代打包器(webpack/rollup/Vite/esbuild)按 import 条件命中 ESM并获得 tree-shaking 友好的模块结构;无构建的浏览器页面用 <script src=".../axios.umd.js"> 后直接使用 window.axios; bundler 的 browser 条件解析还会优先命中 UMD(若未走 import 条件)。四份产物各司其职但同源于一次构建,因此不存在版本漂移问题——scripts/smoke-dist.cjs 正是按这个消费矩阵逐项验收的。
package.json 的 exports 条件映射(消费端解析顺序):
".": {
"types": "./dist/types/index.d.ts",
"browser": "./dist/browser/axios.umd.js",
"require": "./dist/node/axios.cjs",
"import": "./dist/index.esm.js",
"default": "./dist/node/axios.cjs"
}axios.VERSION 在 src/axios.ts 中以常量 '0.2.1' 手工维护,scripts/smoke-dist.cjs 第 52~57 行在发布前断言它与 package.json 的 version 一致(注释记录了"曾与发布版本脱节"的教训)。prepublishOnly = npm test && npm run build && node scripts/smoke-dist.cjs——测试不过不发布;构建产物必须先过冒烟。scripts/smoke-dist.cjs):①CJS require 得到函数本身且 create/CancelToken/CanceledError/AxiosError/isCancel/isAxiosError/HttpStatusCode/mergeConfig/getAdapter/toFormData/formToJSON/all/spread/Axios/VERSION/AxiosHeaders 全在;②UMD 以 require 方式加载形状一致;③ESM 文本级检查含 default 导出;④dist/types/index.d.ts 存在;⑤VERSION 与 package.json 一致。files: ["dist"],仅发布产物目录;registry 指向 npmjs(publishConfig)。prepare: husky)+ .husky/pre-commit 执行 npx lint-staged,对 {src,test}/**/*.ts 先 prettier --write 再 eslint --fix;commit-msg 由 commitlint(@commitlint/config-conventional)校验;npm run commit 走 commitizen 交互式提交。质量工具的协作是一条完整的流水线,理解触发时序才能定位"改动为何被拦":开发者在 git commit 时,husky 的 pre-commit hook 触发 lint-staged,它只对暂存区里的 {src,test}/**/*.ts 依次执行 prettier 格式化与 eslint 修复(改动的文件会自动 re-stage);随后 commit-msg hook 调用 commitlint 按 Conventional Commits 规则校验提交信息(feat:/fix:/docs: 等前缀)。进入发布流程后,prepublishOnly 再串起 test → build → smoke-dist 三道闸。因此该仓库的质量拦截共有四个时机:编辑时(IDE 里的 eslint/prettier 插件)、提交时(husky 双 hook)、发布时(prepublishOnly)与手动门禁(npm run test:prod 组合了 lint 与无缓存测试)。
.github/workflows/ci.yml,GitHub Actions 于 push/PR 到 main 时触发,Node 20/22 矩阵下 npm ci(HUSKY: 0)后依次执行 lint → test(含覆盖率阈值)→ build → examples:build 四步门禁——即本地四个拦截时机之外另有远端集成闸;CI 现存的四个缺口登记于《13》条目 8,逐项展开见《09》第 7 节。Jest 配置内嵌于 package.json:ts-jest 转译、testEnvironment: jsdom(覆盖 xhr/cookie/同源判定等浏览器 API 用例)、testRegex: /test/.*\.(test|spec)\.(ts)$、全局 setup 为 test/boot.ts。覆盖率门槛:branches 80% / functions 85% / lines 90% / statements 90%,任何一项不达标即测试失败;coveragePathIgnorePatterns 排除 node_modules、test 与 src/shims(占位代码不参与统计)。
test/boot.ts 做两件关键事:
jasmine-ajax 仍依赖 jasmine 全局。boot 手工构造独立 jasmine-core 实例并挂 getJasmineRequireObj / global.jasmine,同时刻意不挂载 jasmine 的 expect/test 接口以免覆盖 jest-circus 同名全局(文件头注释详述)。http_proxy/https_proxy/all_proxy/no_proxy(含大写),防止开发机代理劫持 http 适配器测试;需要验证环境变量代理行为的用例(test/core/httpAdapterEnvProxy.spec.ts)自行在用例内设置。覆盖率门槛的意义在于把"测试写了多少"转成"源码被验证了多少"的硬约束:collectCoverageFrom 覆盖 src/ 全部 ts 文件,未写测试的新文件会直接稀释覆盖率直至跌破门槛,从而在 CI/本地强制补测。src/shims/ 被显式排除统计——占位抛错代码没有可测的行为分支。实践上,给新适配器或新 helper 补测试时可以对照 coverage/lcov-report/index.html(npm test 后生成)定位未覆盖分支;test/core/httpAdapterHardening.spec.ts(健壮性专项)就是围绕这类分支生长出来的用例集合。写用例时还应遵循两条既有惯例:浏览器行为用例依赖 jsdom + jasmine-ajax mock XHR(见 test/xhr.spec.ts),Node 行为用例则用真实本地 http server 或 mock transport(见 test/core/httpAdapter*.spec.ts)。
| 目录 | 文件 | 覆盖域 |
|---|---|---|
test/ 根 | instance.spec.ts、defaults.spec.ts、mergeConfig.spec.ts、transform.spec.ts、headers.spec.ts、xhr.spec.ts、auth.spec.ts、xsrf.spec.ts、cancel.spec.ts、progress.spec.ts、static.spec.ts、batch4.spec.ts、advancedConfig.spec.ts、miscAlignment.spec.ts | 实例方法、默认配置、合并策略、转换器、分层 headers、xhr 基础、Basic Auth、XSRF、取消、进度、静态 API、高级配置(withXSRFToken/allowAbsoluteUrls/transitional 等)、官方行为对齐项 |
test/core/ | interceptorRunWhen.spec.ts、AxiosHeaders.spec.ts、settle.spec.ts、getAdapter.spec.ts、fetchAdapter.spec.ts、httpAdapter.spec.ts、httpAdapterExtras.spec.ts、httpAdapterProxy.spec.ts、httpAdapterEnvProxy.spec.ts、httpAdapterProgress.spec.ts、httpAdapterHardening.spec.ts、AxiosForm.spec.ts | runWhen/synchronous、首部类、settle 判定、适配器解析回退、fetch 适配器、http 适配器主体/扩展配置/代理/环境变量代理/进度/健壮性(超限、非法 URL 等)、postForm 系 |
test/cancel/ | CancelToken.spec.ts、Cancel.spec.ts | 取消令牌与取消值 |
test/helpers/ | url.spec.ts、headers.spec.ts、data.spec.ts、error.spec.ts、util.spec.ts、cookie.spec.ts、formData.spec.ts、axiosError.spec.ts | 各工具纯函数 |
新增适配器或配置项时,按上述分区落位:链路行为进 test/ 根或 test/core/,纯函数进 test/helpers/,并保持覆盖率阈值不回退。
eslint.config.mjs):忽略 dist/、coverage/、examples/、构建配置自身;基于 typescript-eslint recommended + eslint-config-prettier(关闭格式冲突规则);定制三条:no-explicit-any 关闭(HTTP 载荷语义大量使用 any,注释说明)、no-require-imports 关闭(boot.ts 与 http 适配器可选依赖探测需要 require)、no-unused-vars 允许 _ 前缀。semi: false、singleQuote: true(package.json 内嵌配置),经 lint-staged 只处理 src/test 下的 ts 文件。strict: true、target ES2017、moduleResolution: node;类型声明由同一 tsconfig 以 --emitDeclarationOnly 产出,保证声明与实现同源。.editorconfig 统一缩进与换行。示例是 Vite 8 + Vue 3 的多页面应用,与库源码同仓。它承担的职责严格限定为"库的人工验收沙盒":不参与 npm run build 的库产物、不被 eslint 检查(eslint.config.mjs 的 ignores 明确排除 examples/**)、不进入发布包(files: ["dist"])。反过来说,示例对库的引用方式是直接 import 源码而非 import dist 产物,因此修改 src/ 后刷新示例页即可见效,不必先跑库构建——这是"示例跟着源码走"的有意安排,代价是示例验证的永远是源码形态而非发布形态,发布形态由 smoke-dist 脚本另行兜底。
examples/vite.config.ts 用 readdirSync 找出每个含 index.html 的子目录作为入口(外加根 index.html),构建输出到 examples/__dist__/。examples/plugins/mock-api.ts 是一个 Vite 插件,在 configureServer 中挂载 Express 应用——复用 examples/routes/*.js 的模块化路由(base/simple/cancel/config/error/extend/interceptor/more 八组;headers 示例页复用 /base/get 路由,无独立路由文件),预置 XSRF-TOKEN-D cookie(XSRF 示例用),经 connect-multiparty 处理文件上传,并拉起 examples/server2.js(8088 端口,带 CORS 头的跨域服务器,供 more/server2 cookie/CORS 示例使用;端口占用时仅告警不崩溃)。stream/http/https/url/zlib 别名到 examples/shared/node-stub.ts,使直接 import 源码入口(含 http 适配器)也能在浏览器里运行。simple(基础请求)、base(GET/POST/参数)、cancel(取消)、config(配置项)、error(错误处理)、interceptor(拦截器)、headers、extend(create/扩展)、fetch、form(表单上传)、upload-file(文件上传)、more(并发/跨域等综合)、abort 等,均为手工验证沙盒,不参与库构建与发布。页面与服务端的对应关系是"一示例一路由组":例如 examples/cancel/ 页面的请求打到 routes/cancel.js 的 /cancel/get(服务端延迟 1 秒应答,为取消操作留出窗口),examples/upload-file/ 依赖 mock-api 的 connect-multiparty 中间件接收 multipart 并落盘到 examples/upload-file 目录,跨域示例则同时牵动 8088 的 server2.js(其 CORS 响应头明确放行 http://localhost:5173 与凭据)。examples/shared/ 下还有可复用的组合式工具(useRequest.ts、format.ts、node-stub.ts)与组件,新写示例页时可直接取用;examples/tsconfig.json 为示例目录提供了独立的 TS 编译配置,与库的 tsconfig.json 互不干扰。__build__ 与 __dist__ 两个下划线前缀目录是 Vite 的构建产物,不入版本管理。
| 现象 | 排查点 |
|---|---|
require() 拿到 {default: ...} 包装 | 确认 dist 为最新(npm run build);smoke-dist 第 10~33 行即为此设计 |
| 本机测试被代理干扰 | test/boot.ts 已剥离代理变量;手动跑 node 脚本时可临时 unset http_proxy https_proxy all_proxy |
| Node 端 multipart 变成 JSON 字符串 | form-data 未安装(optionalDependencies),安装后重装依赖即可恢复管道发送 |
| 浏览器控制台报"Node.js 内置模块 xxx 在浏览器构建中不可用" | 请求被路由到了 http 适配器:检查 config.adapter 或环境探测(getAdapter.ts) |
| 构建后类型缺失 | npm run build 会先执行 build:types;单独构建 JS 时需手动补跑该脚本 |