项目:my-ts-axios | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
第 06 篇《部署运行》按"怎么把项目跑起来"组织命令与流程;本篇按"每个配置文件为什么写成这样"逐段拆解工程配置——rollup.config.mjs 逐段、tsconfig.json、eslint.config.mjs、package.json 的 scripts 与 jest 段全量、测试引导链、examples 工程结构。目标读者是要维护或调整构建/质量链路的人。原则与全篇文档一致:每个配置项的解释都锚定到它服务的具体需求,不存在的配置(如本仓库没有的 postcss、terser 配置)不虚构。
这份配置集有一个统一的工程审美可以先行指出:每个非默认选择旁边都有一行注释记录它防住的事故——CJS exports: 'default' 防坏包、shim 别名防浏览器打包失败、jest 的 collectCoverageFrom 防未测文件逃逸门槛、boot.ts 防 jasmine 全局覆盖 jest-circus。读懂这些"防事故注释",比记住配置项本身更重要。
const pkg = createRequire(import.meta.url)('./package.json') 用 createRequire 在 ESM 配置文件里以 CJS 方式读取 package.json——产物路径全部来自 pkg.main/module/browser 字段而非硬编码,这保证"改 package.json 的产物位置,构建自动跟随";const root = path.dirname(fileURLToPath(import.meta.url)) 取配置文件所在目录作为根,配合第 19 行的 path.join(root, 'src/shims/node-builtins.ts'),使别名替换使用绝对路径——相对路径在 rollup 的 alias 插件里对子路径解析不可靠,这里是不踩坑的写法。
NODE_BUILTINS = ['http', 'https', 'zlib', 'stream'],EXTERNAL = ['form-data', ...NODE_BUILTINS]。这两个数组是双构建分工的核心:Node 构建把五者声明为 external(产物里保留 require('http') 等引用,不打源码);浏览器构建把它们全部 alias 到同一个文件 src/shims/node-builtins.ts(第 20~23 行的 map 生成五条 find/replacement 规则)。这个设计的成立条件在第 01 篇 3.7 节论证过:http 适配器对内置模块的依赖只有"静态 import + 顶层 try/catch require"两种形态,前者被 alias 静默替换、后者在浏览器里 require 到的是 shim 的 default 导出(返回抛错函数而非抛异常——require('form-data') 被 try/catch 包住不会崩溃,只是 NodeFormData 拿到占位函数,真正调用时才抛错)。维护契约:http 适配器引入新的内置 API 时,shim 的导出面必须同步扩充,否则浏览器构建到运行时才暴露缺符号。
两个构建的插件链只在中间多一个 alias:json()(处理 package.json 版本导入等场景)→ typescript({ tsconfig: './tsconfig.json' })(用同一份 tsconfig 编译,保证类型检查口径一致)→ (浏览器专属 alias)→ resolve()(解析 node_modules 与 TS 路径)→ commonjs()(处理 form-data 的 CJS 形态等)。顺序要点:typescript 在 resolve 之前,alias 在 typescript 之后(alias 的是解析后的模块 id)——这套顺序是"TS 源码参与 rollup 树摇"的标准排布。注意没有 terser/esbuild 压缩插件:产物不压缩(保留可读性,学习版的刻意选择),体积控制依赖源码本身的精简。
input: 'src/index.ts',external: EXTERNAL,双输出:CJS 到 pkg.main(dist/node/axios.cjs,format: 'cjs', sourcemap: true, exports: 'default')与 ESM 到 pkg.module(dist/index.esm.js,format: 'es', sourcemap: true)。exports: 'default' 是整个配置里最重要的一行:ESM 源只有 default 导出,rollup 的 CJS 输出默认按 named 导出推断会生成 module.exports = { default: axios }——普通 require() 拿到包装对象,破坏所有 CJS 消费方(scripts/smoke-dist.cjs 头部注释记录的坏包事故);指定 exports: 'default' 后生成 module.exports = axios 函数本身。UMD 输出(第 51~59 行)同样带 exports: 'default',原因相同。
无 external 字段(零 external,一切依赖打进产物或被 alias 替换),单输出 UMD:format: 'umd', name: 'axios', sourcemap: true, exports: 'default',产物路径 pkg.browser(dist/browser/axios.umd.js)。name: 'axios' 决定 <script> 引入时挂在 window.axios 上的是 axios 函数本身。与第 06 篇的 exports 条件映射对照:browser 条件命中本产物、require 条件命中 CJS、import 条件命中 ESM、default 兜底回 CJS——消费矩阵的每一格都有产物对应,smoke-dist.cjs 按 same 矩阵验收。
| 项 | 值 | 说明 |
|---|---|---|
moduleResolution | node | 经典 Node 解析;未用 bundler(与《13》条目 12 相关的登记项) |
target / module | ES2017 / ES2015 | 编译目标:async/await 与对象展开原生可用,Promise 原生可用;产物兼容 engines 的 Node>=14 与主流浏览器 |
lib | ["ES2019", "DOM"] | DOM lib 使 xhr/cookie/document 等浏览器 API 有类型;ES2019 补齐 flat 等标准库类型 |
strict | true | 全严格模式——strictNullChecks 迫使适配器对 url!、config.method! 这类"依赖 defaults 保证存在"的断言显式标注 |
sourceMap | true | 与 rollup 的 sourcemap 输出配套 |
allowSyntheticDefaultImports | true | 允许对无 default 导出的模块用默认导入语法(CJS 互操作场景) |
typeRoots | node_modules/@types | 显式声明,配合 @types/node、@types/jest |
include | ["src"] | 只编译 src——test 由 ts-jest 按自己的转译链处理,examples 有独立 tsconfig,三者互不干扰 |
两个衍生事实:其一,npm run build:types(tsc --declaration --emitDeclarationOnly --declarationDir dist/types)复用这份 tsconfig,因此声明文件与实现同源同口径;其二,npx tsc --noEmit 也读这份配置,是全库类型检查的标准命令。
ESLint 9 flat config,结构四段:①ignores——dist/coverage/examples/node_modules 加 rollup.config.mjs 与 eslint.config.mjs 自身(构建产物、报告、示例工程不检查;两个构建配置文件豁免是务实选择);②prettier(eslint-config-prettier,关闭与格式化冲突的规则——格式问题全权交给 prettier,lint 只管代码质量);③tseslint.configs.recommended(typescript-eslint 推荐集展开);④三条定制规则:no-explicit-any: 'off'(注释说明"项目大量使用 any 表达 HTTP 载荷语义"——HTTP 库的 data/headers 天然是 any,开启会全面报错);no-require-imports: 'off'(boot.ts 的 jasmine 兼容层与 http 适配器的 form-data 探测需要 require);no-unused-vars 定制为 argsIgnorePattern: '^_' 与 varsIgnorePattern: '^_'(下划线前缀豁免,transform 的 _encoding、formToJSON 的 _opts 等占位参数因此合法)。三条规则每条都有存在的具体理由,这是"配置即决策记录"的又一例。
| 脚本 | 命令 | 说明 |
|---|---|---|
dev | vite --config examples/vite.config.ts | 示例 dev server(5173),mock-api 插件挂载 Express 路由并拉起 8088 |
examples:build | vite build --config examples/vite.config.ts | 示例多页面构建到 examples/__dist__/ |
lint / lint:fix | eslint . / eslint . --fix | 按 eslint.config.mjs 的 ignores 圈定范围 |
prebuild | rimraf dist | npm 生命周期钩子,构建前清理产物 |
build | npm run build:types && rollup -c rollup.config.mjs | 先类型声明后 JS 产物 |
build:types | tsc --declaration --emitDeclarationOnly --declarationDir dist/types | 声明独立产出(第 3 节) |
start | rollup -c rollup.config.mjs -w | 库开发监听模式 |
test / test:watch | jest --coverage(/ --watch) | 固定带覆盖率(门槛见 jest 段) |
test:prod | npm run lint && npm run test -- --no-cache | 发布前组合门禁:lint + 无缓存测试 |
prepublishOnly | npm run test && npm run build && node scripts/smoke-dist.cjs | 发布三道闸 |
prepare | husky | npm install 时安装 git hooks |
commit | git-cz | commitizen 交互式 Conventional Commits |
main/module/browser/typings 四字段与 exports 条件映射构成消费入口(第 06 篇第 3 节已完整列出,不重复);files: ["dist"] 只发布产物;engines.node: ">=14.0.0"(库运行时);optionalDependencies.form-data: ^4.0.0(缺失降级);publishConfig.registry 指向 npmjs。lint-staged 对 {src,test}/**/*.ts 先 prettier 后 eslint --fix;prettier 内嵌配置 semi: false, singleQuote: true(与源码风格一致——阅读源码时看到的"无分号 + 单引号"即来自这里);commitlint extends @commitlint/config-conventional。
transform 用 ts-jest(裸配置 {});testEnvironment: jsdom(默认环境,Node 环境的 spec 用文档注释 @jest-environment node 局部切换,见 test/core/httpAdapter*.spec.ts);testRegex: /test/.*\.(test|spec)\.(ts)$;moduleFileExtensions: [ts, tsx, js];coveragePathIgnorePatterns 排除 node_modules/test/src/shims;coverageThreshold.global 四项 branches 80 / functions 85 / lines 90 / statements 90(硬门槛实现原理见《09》第 4 节);collectCoverageFrom: ["src/*.{js,ts}", "src/**/*.{js,ts}"](未测文件强制进统计);setupFilesAfterEnv: ["<rootDir>/test/boot.ts"](全局引导,在每个测试框架安装后执行,见第 6 节引导链)。
一次 npm test 的完整链路:jest 启动 → 按覆盖配置收集 src 全部文件 → 为每个测试模块应用 ts-jest 转译 → 执行 test/boot.ts(全局 setup)→ 运行 36 个 spec。boot.ts 的三步引导(jasmine-core 实例构造与 getJasmineRequireObj/global.jasmine 挂载、刻意不挂 interface、剥离八个代理环境变量)在《09》第 3 节已逐行展开;本节补链路视角的两个要点:第一,引导顺序敏感——JasmineCore.core(JasmineCore) 与 getEnv() 必须先于 require('jasmine-ajax'),因为后者加载时立即向 jasmine 全局注册;第二,环境耦合点——jasmine-ajax 仅在 typeof window !== 'undefined' 时加载(boot.ts 第 20 行),这正是"浏览器行为 spec 依赖 jsdom 环境"的机制基础,任何切换到 node 环境的 spec 文件都不会实例化 XHR mock,http 系测试因此不受 jasmine-ajax 干扰。配套的 test/helper.ts 只提供 getAjaxRequest()(setTimeout(0) 后取最近一次 mock 请求),是 xhr 系 spec 的公共助手。
四段:①多入口扫描——readdirSync 找出每个含 index.html 的子目录生成 rollup input 映射(外加根 index.html 作 index 入口),新增示例目录零配置自动纳入;②插件——vue() 与 mockApi();③别名——stream/http/https/url/zlib 五个 Node 模块指到 examples/shared/node-stub.ts(一个行为友好的空桩:Transform/Readable 有空实现、url.parse 用 WHATWG URL 模拟、zlib 返回空 Transform——比库构建用的"抛错 shim"更温和,目的是让直接 import 源码入口的示例页能正常加载运行,只有真正发起 http 适配器请求才会走到抛错函数);④server/build——端口 5173、构建输出 examples/__dist__(emptyOutDir: true)。
examples/plugins/mock-api.ts 是 Vite 插件,在 configureServer 钩子里:种 XSRF-TOKEN-D cookie(XSRF 示例用)→ 挂 bodyParser/cookieParser/connect-multiparty(上传落盘到 examples/upload-file/)→ require('../routes') 注册八组路由 → 挂到 server.middlewares → try/catch 拉起 examples/server2.js(8088 跨域服务器,CORS 头显式放行 http://localhost:5173 与凭据;端口占用仅告警)。examples/routes/*.js 八个文件(base/simple/cancel/config/error/extend/interceptor/more,CommonJS 形态,因为被 mock-api 以 require 加载;headers 示例页复用 /base/get 路由)与示例页一一对应,routes/index.js 是聚合注册入口。examples/shared/ 提供 useRequest.ts/format.ts 等组合式工具与四个组件,examples/tsconfig.json 为示例提供独立 TS 配置。三个与库的边界(第 06 篇第 7 节已述,此处只列结论):示例不参与库构建、不被 eslint 检查、直接 import 库源码(验证源码形态而非发布形态)。
把上述配置的引用关系收成一张文字图:package.json 是配置网络的中心——scripts 引用 rollup.config.mjs(build/start)、tsc(build:types)、jest(test)、vite 配置(dev/examples:build)、smoke-dist.cjs(prepublishOnly);tsconfig.json 被三方引用(rollup 的 typescript 插件、build:types 的 tsc、npx tsc --noEmit);eslint.config.mjs 被 lint scripts 与 lint-staged 引用;jest 段引用 test/boot.ts;vite 配置引用 examples/plugins/mock-api.ts 与 shared/node-stub.ts;husky hook(.husky/pre-commit)引用 lint-staged。改任何一个配置前,先在这张图上确认它有几个上游消费方——例如改 tsconfig 会同时影响构建、声明产出与类型检查三个链路,改 prettier 配置会影响全部源码的格式化基线。这也是本篇把所有配置集中成篇的原因:配置的复杂度不在单项,在耦合。