项目:my-ts-axios | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
my-ts-axios(npm 包名 ts-axios-zhangzhengyang,package.json 中 version: 0.2.1)是一个使用 TypeScript 从零实现的 axios 学习版 HTTP 客户端库。它不是对官方 axios 源码的复制,而是围绕 axios 的公开行为规范,逐个特性独立重写了一套可构建、可测试、可发布的完整库工程:浏览器端基于 XMLHttpRequest,Node.js 端基于原生 http / https 模块,另附一个基于全局 fetch 的适配器用于 Service Worker / Cloudflare Workers 等无 XHR、无 Node http 的运行时。
项目的核心学习价值在于:它把 axios 这类"看似简单"的 HTTP 库背后真正的工程难点完整展开——
dispatchRequest、响应转换全部挂在一条动态拼装的 Promise 链上,这是理解 axios 行为(如"请求拦截器后注册先执行"、"响应拦截器 rejected 能捕获适配器错误")的关键;AxiosRequestConfig / AxiosResponse / AxiosError / AxiosStatic 等接口构成对外契约,兼有"混合对象"(既是函数又挂载属性/方法)的类型表达;按 README 的描述与源码验证,这是一个库项目而非应用项目:没有页面路由与业务界面,examples/ 下的 Vite + Vue 3 多页面站点仅作为库的手工验证沙盒。因此本套文档的重心是模块设计与请求链路,而非页面设计。
| 项 | 值 | 出处 |
|---|---|---|
| npm 包名 / 显示名 | ts-axios-zhangzhengyang / ts-axios | package.json name、README.md 标题 |
| 当前版本 | 0.2.1(package.json 与 src/axios.ts 中 VERSION 常量一致,发布前由 scripts/smoke-dist.cjs 强制校验二者同步) | package.json、src/axios.ts |
| 许可与归属 | MIT;作者 zhangzhengyang <1074385735@qq.com>;仓库 github.com/zhangzhengyang27/ts-axios | package.json |
| 运行时依赖面 | 零硬依赖:Node 端仅用内置模块,form-data 为可选依赖(缺失时自动降级) | package.json、src/core/adapters/http.ts |
| 关键开发依赖 | TypeScript 5.9、Rollup 4.24、Vite 8.1、Vue 3.5、Jest 29.7、ts-jest 29.2、ESLint 9、Express 4.21、jasmine-ajax 4.0 | package.json devDependencies |
把它与"照抄官方源码"式学习项目区分开的,是三件事:其一,所有模块都带有中文设计注释,注释里频繁出现"与官方对齐""此前曾因……出过问题"的决策记录(例如 scripts/smoke-dist.cjs 头部记录的 CJS named 导出坏包事故、src/helpers/util.ts 中记录的 Vite/esbuild 下 class 方法不可枚举漏拷问题),这使得源码本身可以作为"踩坑史"来读;其二,工程闭环完整——测试、覆盖率门槛、提交规范、发布冒烟一应俱全,具备真实开源库的维护形态;其三,它刻意保留了官方 axios 的行为兼容面(transitional 配置、错误码表、拦截器 synchronous/runWhen 等较新特性均已跟进),因此可以用官方文档的心智模型来使用它。
上表中"已实现"的判定标准是:src/ 中存在该特性的消费路径(不只是类型声明),且 test/ 下能找到对应的行为用例。例如"synchronous 拦截器"一项,实现在 src/core/Axios.ts 第 70~80 行的 every(i => i.options?.synchronous === true) 分支,测试落在 test/core/interceptorRunWhen.spec.ts;而 transitional.silentJSONParsing 虽然出现在 src/defaults.ts 与类型声明中,但全库检索不到任何消费点,因此归入"声明未消费"并在第 03 篇差异清单中显式标注。读者扩展新特性时建议沿用同一口径:类型、默认值、消费路径、测试用例四处齐全才算落地。
下表逐项核对源码后的特性覆盖情况。"对应模块"给出承担该特性的主要源文件(相对项目根)。
| 特性 | 实现状态 | 对应模块 |
|---|---|---|
Promise API(request 返回 AxiosPromise) | 已实现 | src/core/Axios.ts |
请求/响应拦截器(use/eject) | 已实现 | src/core/InterceptorManager.ts、src/core/Axios.ts |
拦截器 runWhen 条件执行、synchronous 同步执行 | 已实现(对齐官方 v1.x 新特性) | src/core/InterceptorManager.ts、src/core/Axios.ts |
请求数据转换(transformRequest,普通对象自动 JSON) | 已实现 | src/defaults.ts、src/helpers/data.ts、src/core/transform.ts |
响应数据转换(transformResponse,字符串尝试 JSON 解析) | 已实现 | src/defaults.ts、src/helpers/data.ts |
transitional.forcedJSONParsing 关闭时跳过响应 JSON 解析 | 已实现 | src/core/dispatchRequest.ts |
| 请求取消:CancelToken 旧机制 | 已实现 | src/cancel/CancelToken.ts、src/cancel/Cancel.ts |
| 请求取消:AbortSignal 新机制(与 CancelToken 双轨并存) | 已实现 | src/core/adapters/xhr.ts、src/core/adapters/http.ts、src/core/adapters/fetch.ts、src/cancel/CanceledError.ts |
可插拔适配器 config.adapter(字符串 / 函数 / 数组回退) | 已实现 | src/core/getAdapter.ts |
| 内置 xhr 适配器(浏览器 / jsdom) | 已实现 | src/core/adapters/xhr.ts |
| 内置 http/https 适配器(Node:重定向、解压、代理、stream) | 已实现 | src/core/adapters/http.ts |
| 内置 fetch 适配器(SW / Workers 环境) | 已实现 | src/core/adapters/fetch.ts |
超时控制(timeout,transitional.clarifyTimeoutError) | 已实现(三适配器) | xhr.ts / http.ts / fetch.ts |
状态码校验(validateStatus,4xx/5xx 错误码区分) | 已实现 | src/core/settle.ts |
错误码体系与 isAxiosError | 已实现(13 个错误码常量) | src/helpers/error.ts |
| AxiosHeaders 首部类(set/get/has/delete/merge/toJSON 等) | 已实现(官方方法子集) | src/core/AxiosHeaders.ts |
| headers 三层结构(common / method / 顶层)与拍平 | 已实现 | src/helpers/headers.ts(flattenHeaders) |
XSRF 防护(xsrfCookieName/xsrfHeaderName/withXSRFToken) | 已实现(xhr 与 fetch 适配器) | xhr.ts、fetch.ts、src/helpers/cookie.ts |
Basic Auth(config.auth) | 已实现(三适配器) | xhr.ts、http.ts、fetch.ts |
| withCredentials | 已实现(xhr / fetch) | xhr.ts、fetch.ts |
上传/下载进度(onUploadProgress/onDownloadProgress,AxiosProgressEvent) | 已实现;fetch 适配器无上传进度 | xhr.ts、src/helpers/progressStream.ts、fetch.ts |
Node 限速 maxRate(上行/下行分开) | 已实现 | src/helpers/progressStream.ts |
URL 构建与 paramsSerializer(函数 / {serialize, encode, indexes} 对象形式) | 已实现 | src/helpers/url.ts |
baseURL 拼接与 allowAbsoluteUrls 开关 | 已实现 | src/core/dispatchRequest.ts(transformURL)、src/helpers/url.ts |
表单序列化(postForm/putForm/patchForm、toFormData/formToJSON、formSerializer) | 已实现 | src/core/Axios.ts(_requestForm)、src/helpers/formData.ts |
静态 API(axios.VERSION/AxiosHeaders/HttpStatusCode/mergeConfig/getAdapter/CanceledError 等) | 已实现 | src/axios.ts |
并发辅助 axios.all / axios.spread | 已实现 | src/axios.ts |
Node 重定向(maxRedirects、beforeRedirect、跨 host 剥离凭据、301/302/303 方法改写) | 已实现(自实现,非 follow-redirects) | src/core/adapters/http.ts |
Node gzip/deflate/br 解压(decompress) | 已实现 | src/core/adapters/http.ts(buildDecompressStream) |
Node 代理(config.proxy / 环境变量 HTTP_PROXY 等 / NO_PROXY;HTTPS CONNECT 隧道) | 已实现 | src/core/adapters/http.ts、src/helpers/proxyFromEnv.ts |
Node maxContentLength / maxBodyLength(含流式边发边计数) | 已实现 | src/core/adapters/http.ts |
Node responseType: 'stream'、httpAgent/httpsAgent、socketPath、responseEncoding | 已实现 | src/core/adapters/http.ts |
实例工厂 axios.create(config) | 已实现 | src/axios.ts |
axios.getUri(config) | 已实现 | src/core/Axios.ts |
未覆盖(源码中不存在对应实现):请求去重/缓存、AxiosError.cause 透传、HTTP2 支持、AxiosHeaders 的 forEach/match/asType/setCookie 等完整方法面、浏览器 XSRF 对 document.cookie 的写入等。若需扩展可参考第 03 篇的差异清单。
| 维度 | 选型 | 出处 |
|---|---|---|
| 语言 | TypeScript 5.9(strict: true,target ES2017,lib ES2019 + DOM) | tsconfig.json |
| 构建产物 | Rollup 4:CJS(Node 主入口)+ ESM + UMD(浏览器)+ tsc 独立输出类型声明 | rollup.config.mjs、package.json scripts |
| Node 端运行时依赖 | 仅 Node 内置模块 http/https/zlib/stream;form-data 为 optionalDependencies(未安装时降级) | src/core/adapters/http.ts、package.json |
| 示例工程 | Vite 8 + Vue 3.5 + Express 4(mock API 内嵌进 Vite dev server) | examples/vite.config.ts、examples/plugins/mock-api.ts |
| 测试 | Jest 29 + ts-jest + jsdom 环境 + jasmine-ajax(带自建兼容引导层) | package.json jest 段、test/boot.ts |
| 代码质量 | ESLint 9 flat config + typescript-eslint + prettier;husky + lint-staged;commitlint + commitizen | eslint.config.mjs、package.json |
| Node 版本 | 库运行时要求 >=14.0.0(engines);开发/构建建议 Node 20+(Vite 8 要求) | package.json、README.md |
值得注意的一个工程选型:浏览器构建把 Node 内置模块与 form-data 通过 @rollup/plugin-alias 全部映射到 src/shims/node-builtins.ts 占位实现(调用即抛错),因此 UMD 产物没有任何 external 依赖,<script> 标签可直接引入;而 http 适配器代码随包携带,只在真正被调用时才会触发占位实现抛错。这解决了"一份源码、双端产物、浏览器包零依赖"的矛盾。
构建工具的双轨分工也体现了同样的思路:Rollup 只负责库产物(cjs/esm/umd 三形态 + sourcemap + 类型声明),因为它对多格式输出、tree-shaking 与 external 控制更精细;Vite 只负责示例工程(dev server、HMR、多页面扫描),因为它对开发体验更友好。两者通过 examples/vite.config.ts 里的 resolve.alias(把 stream/http/https/url/zlib 指到 examples/shared/node-stub.ts)衔接,使示例页可以直接 import 库源码入口而不必先跑库构建。测试则选择 Jest + jsdom 而非 Vitest,主要动因是 jasmine-ajax 这套成熟的 XHR mock 生态仍挂在 jasmine 全局上(test/boot.ts 为此写了专门的兼容引导层)。
my-ts-axios/
├── package.json # 包元数据:exports 三入口映射、jest 配置、scripts、质量工具配置
├── tsconfig.json # TS 编译选项(strict、ES2017 target、声明输出用)
├── rollup.config.mjs # 双 Rollup 构建:Node(cjs+es, external 内置模块) / 浏览器(umd, 别名 shim)
├── eslint.config.mjs # ESLint 9 flat config(ignores 构建产物,放宽 any)
├── .husky/pre-commit # git hook:npx lint-staged
├── src/ # 库源码(唯一被构建的目录)
│ ├── index.ts # 库入口:export * from './types' + default axios
│ ├── axios.ts # createInstance 工厂、默认实例、全部静态 API 挂载
│ ├── defaults.ts # 默认配置:method/timeout/xsrf/headers 分层/转换器/validateStatus/transitional
│ ├── types/index.ts # 全部公共类型:AxiosRequestConfig/AxiosResponse/AxiosStatic/Cancel 系列等
│ ├── core/ # 请求链路核心
│ │ ├── Axios.ts # Axios 类:request 编排 Promise 链 + 各 HTTP 方法 + getUri + postForm 系
│ │ ├── dispatchRequest.ts # 链上真正发请求的一环:取消预检 → 配置加工 → 取适配器 → 响应转换
│ │ ├── mergeConfig.ts # 策略模式配置合并(default/fromVal2/deepMerge 三类策略)
│ │ ├── InterceptorManager.ts # 拦截器管理器(use/eject/forEach,支持 synchronous/runWhen 选项)
│ │ ├── transform.ts # 转换器遍历执行器(单函数或数组统一按数组跑)
│ │ ├── settle.ts # 三适配器共享的 resolve/reject 判定(validateStatus + 4xx/5xx 错误码)
│ │ ├── getAdapter.ts # 适配器注册表与解析(xhr/http/fetch,支持回退数组)
│ │ ├── AxiosHeaders.ts # 首部类(小写规范化键名存储)
│ │ └── adapters/
│ │ ├── xhr.ts # 浏览器 XMLHttpRequest 适配器
│ │ ├── http.ts # Node http/https 适配器(重定向/解压/代理/限流/流)
│ │ └── fetch.ts # fetch 适配器(内部 AbortController 统一取消与超时)
│ ├── cancel/ # 取消机制三件套
│ │ ├── CancelToken.ts # 旧版取消令牌(executor + 内部 promise)
│ │ ├── Cancel.ts # 旧版取消原因对象 + isCancel 判定
│ │ └── CanceledError.ts # 新版取消错误(继承 AxiosError,code=ERR_CANCELED)
│ ├── helpers/ # 纯函数工具集
│ │ ├── url.ts # buildURL/isAbsoluteURL/combineURL/isURLSameOrigin
│ │ ├── headers.ts # processHeaders/parseHeaders/flattenHeaders
│ │ ├── data.ts # 默认 transformRequest/transformResponse 实现
│ │ ├── error.ts # AxiosError 类、AxiosErrorCodes 常量、createError 工厂、isAxiosError
│ │ ├── util.ts # isDate/isPlainObject/extend/deepMerge/isFormData
│ │ ├── cookie.ts # document.cookie 读取(XSRF 用)
│ │ ├── formData.ts # toFormData / formToJSON(dots/indexes/visitor)
│ │ ├── progressStream.ts # Node 端进度统计 + 限速 Transform 流
│ │ ├── proxyFromEnv.ts # HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY 环境变量代理推导
│ │ └── HttpStatusCode.ts # 完整 HTTP 状态码常量表
│ └── shims/node-builtins.ts # 浏览器构建占位:http/zlib/stream/form-data 调用即抛错
├── test/ # Jest 测试(根级功能域 + core/ + helpers/ + cancel/ 分目录)
│ ├── boot.ts # 全局 setup:jasmine-ajax 兼容引导 + 剥离代理环境变量
│ └── *.spec.ts # 见第 06 篇测试体系一节
├── examples/ # Vite + Vue 3 多页面示例与 mock 服务
│ ├── vite.config.ts # 多入口扫描 + node-stub 别名 + mockApi 插件
│ ├── plugins/mock-api.ts # 把 examples/routes 的 Express 路由挂进 dev server
│ ├── routes/*.js # base/simple/cancel/config/error/extend/headers/interceptor/more 路由
│ ├── server2.js # 8088 端口跨域服务器(CORS + cookie 示例用)
│ └── */ # simple、base、cancel、error、interceptor、upload-file 等示例页
├── scripts/smoke-dist.cjs # 发布前冒烟:CJS/UMD/ESM/types 四产物导出形态 + VERSION 一致性
└── dist/ # 构建产物(不入库):node/axios.cjs、index.esm.js、browser/axios.umd.js、types/对上表再做一轮"按职责归组"的文字导览,便于建立空间感。src/ 下的代码分为四组:入口组(index.ts、axios.ts、defaults.ts)回答"库长什么样、默认行为是什么";链路组(core/ 除 adapters 外)回答"一次请求如何被编排与加工";端点组(core/adapters/)回答"具体怎么把字节发出去、怎么把字节收回来";支撑组(helpers/、cancel/、types/、shims/)提供无状态的纯函数、取消原语、类型契约与构建占位。组间依赖是单向的:端点组与链路组都依赖支撑组,入口组依赖链路组,任何反向依赖都不存在。
test/ 的组织与 src/ 形成镜像:根级 spec 按"用户可见行为"划分(实例、默认值、转换、取消、进度……),test/core/ 按链路内部构件划分(拦截器、settle、适配器解析、三个适配器各自的专项),test/helpers/ 按纯函数划分。这意味着回归定位有一条清晰的路径——先从根级 spec 复现行为异常,再下钻到 core/helpers 的单元级 spec 找根因。examples/ 则是"人工验收层":routes/*.js 的八组 Express 路由与各示例页一一对应,npm run dev 一次启动即可覆盖拦截器、取消、上传、跨域(8088 的 server2)等真实浏览器场景。
scripts/smoke-dist.cjs 是发布前的最后一道闸:它不以"构建成功"为满足,而是把三种产物逐一加载、逐项断言导出面(含 VERSION 与 package.json 的同步),从消费端视角验证包的可用性。这个"以消费者身份检查自己"的习惯贯穿了整个工程——rollup 配置里关于 CJS exports: 'default' 的长注释、util.ts 里关于 extend 沿原型链遍历的修复,都是同一思路的体现。
以下命令均可在项目根目录 /Users/zhangzhengyang/Desktop/publish/my-ts-axios 执行(摘自 package.json 的 scripts):
# 1) 安装依赖(开发/构建建议 Node.js 20+,examples 使用 Vite 8)
npm install
# 2) 构建:先 rimraf dist → tsc 输出声明到 dist/types → rollup 产出 cjs/esm/umd
npm run build
# 3) 测试(默认带覆盖率,四项阈值:branches 80% / functions 85% / lines 90% / statements 90%)
npm test
npm run test:watch # 监听模式
npm run test:prod # lint + 无缓存测试(发布前组合)
# 4) 跑示例:启动 Vite dev server(http://localhost:5173),
# mock-api 插件会同时挂载 Express mock 路由并拉起 8088 跨域服务器
npm run dev
# 5) 示例构建(多页面输出到 examples/__dist__/)
npm run examples:build
# 6) 代码检查 / 监听构建
npm run lint # eslint .
npm run start # rollup -w 监听模式
# 7) 发布链路(prepublishOnly 自动执行):测试 → 构建 → dist 冒烟检查
node scripts/smoke-dist.cjs消费构建产物(package.json 的 exports 映射):Node require 走 dist/node/axios.cjs,打包器 import 走 dist/index.esm.js,浏览器 <script> 走 dist/browser/axios.umd.js(全局 window.axios),类型统一读 dist/types/index.d.ts。
几条命令背后的流水线值得展开说明,以免"只会敲不会改"。npm run build 实际是三段式的:npm 的 prebuild 生命周期钩子先执行 rimraf dist 保证产物目录干净;随后 build:types 用 tsc --declaration --emitDeclarationOnly 只产出声明文件到 dist/types/;最后 rollup -c 按 rollup.config.mjs 的两个构建任务产出 cjs/esm/umd 三种 JS 产物。npm test 固定带 --coverage,且 package.json 的 coverageThreshold 设了硬门槛(branches 80% / functions 85% / lines 90% / statements 90%),覆盖率不达标即判定失败——这是该仓库"测试即门禁"的核心机制。npm run dev 启动的不只是静态服务器:examples/plugins/mock-api.ts 会在 Vite 的 configureServer 钩子里挂载一组 Express 中间件(含 XSRF-TOKEN-D cookie 种植、connect-multiparty 文件上传解析与八组示例路由),并尝试拉起 8088 端口的跨域服务器 examples/server2.js,所以示例页里连跨域、cookie、上传这类需要真实服务端配合的场景都能离线跑通。
首次上手建议的验证顺序:先 npm test 确认环境健康(全绿且覆盖率达标),再 npm run build 确认构建链路可用,最后 npm run dev 打开 http://localhost:5173 在示例页里实际发起一次请求。三步都通过,即可开始阅读源码。
若目标是通读实现,推荐按"由外向内、先编排后端点"的顺序:第一步读 src/axios.ts(80 行以内,看清混合对象如何拼装、静态 API 如何挂载);第二步读 src/core/Axios.ts 的 request 方法(Promise 链编排是全库的灵魂);第三步读 src/core/dispatchRequest.ts 与 src/core/mergeConfig.ts(配置合并与加工的三步曲);第四步按需深入 src/core/adapters/ 三兄弟(xhr 最短、适合先读;http 最长、涵盖重定向/代理/流);最后把 src/helpers/ 当作工具书按需查阅。src/types/index.ts 建议在第二步之前先速览一遍,带着类型读实现事半功倍。
七篇文档的共同约定:所有结论均直接来自对 src/、test/、examples/、构建配置的逐文件阅读,引用路径一律相对项目根(如 src/core/Axios.ts),行号引用精确到当前源码版本;文中有三处经全库检索仍无法闭环确认的语义(transitional.silentJSONParsing 的消费点、FormSerializerOptions.metaTokens/maxDepth 的消费点、_requestForm 未走 env.FormData 是否有意),均以 ⚠️ 待确认 显式标注而非猜测,后续维护者可从这些标记入手核对或补齐。