PixiJS 构建与测试体系解析:build.mts 双阶段构建、test.mts 检查选择器与 playground 构建状态联动
PixiJS 构建与测试体系解析build.mts 双阶段构建、test.mts 检查选择器与 playground 构建状态联动【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs本文以 PixiJS 仓库中 scripts/README.md 为主体系统讲解该引擎仓库的构建与测试编排机制如何理解npm run build/build:lib/watch:lib等 npm script 背后的 build.mts 双阶段并行流程、--lib与--dev两个开关如何改变产物范围以及 test.mts 如何通过 selector 机制把 lint、类型检查、barrel index 校验、dead-code 分析和 Jest 单元/视觉回归测试组织成静态检查先行、阻塞失败即中止的两阶段流水线。读完本文你可以独立运行、观察并排查 PixiJS 本地开发与 CI 质量检查的完整链路。一、快速参考npm script 命令总览PixiJS 仓库根目录 package.json 的scripts字段定义了全部入口命令scripts/README.md 将其归纳为如下快速参考表命令说明npm run build完整生产构建bundle 类型声明 DTS 打包npm run build:lib开发构建bundle 类型声明跳过 DTS 打包npm run build:docs生成 TypeDoc API 文档npm start启动 playground 开发服务器 watch 模式npm test运行全部检查lint、types、index、prune、unit、visualnpm run test:unit仅运行单元测试npm run test:visual仅运行视觉回归测试npm run test:lint仅运行 lint 检查npm run test:types仅运行类型检查npm run lintlint 并自动修复等价于node ./scripts/test.mts lint --fix从 package.json 的 scripts 定义可以直接看到它们与 scripts/ 目录的对应关系build: node ./scripts/build.mts, build:lib: node ./scripts/build.mts --lib --dev, start: npm run dev --prefix playground npm run watch:lib, test: node ./scripts/test.mts, test:visual: node ./scripts/test.mts visual, test:unit: node ./scripts/test.mts unit, test:types: node ./scripts/test.mts types, test:lint: node ./scripts/test.mts lint, lint: node ./scripts/test.mts lint --fix可以看到npm script 只是薄薄一层build、build:lib都是scripts/build.mts加上不同参数所有test*命令则都是scripts/test.mts加上不同 selector。真正的编排逻辑全部沉淀在scripts/目录中这正是 scripts/README.md 的核心内容。二、构建流水线build.mts 的开关与双阶段结构2.1 两个命令行开关--lib 与 --devbuild.mts 通过解析process.argv中的 flag 来决定行为const flags new Set(process.argv.slice(2)); const isLib flags.has(--lib); const isDev flags.has(--dev);按 scripts/README.md 的说明并结合源码--lib—— 仅构建 library。它会跳过 package exports 校验和 DTS bundle 生成并为 Rollup 注入环境变量LIB_ONLY1使 Rollup 只输出 library 产物即lib/下的模块化产物不再构建dist/下的 IIFE/ESM bundle。--dev—— 开发模式。允许tsc出现类型错误而不使构建失败tsc的失败会被静默吞掉见下文同时跳过dts-bundle-generator加快迭代速度。两个开关组合出来的关键判断在 build.mts 中体现为两条规则// exports 校验非 lib-only 或 dev 模式下执行 if (!isLib || isDev) { step1.push(spawn(node, [./scripts/utils/exports.mts], { signal })); } ... // 类型声明链非 lib-only 或 dev 模式下执行 const needsTypes !isLib || isDev;也就是说npm run build无 flag完整流程exports 校验、DTS bundle 一个不少npm run build:lib--lib --devexports 校验因isDev为真而仍然执行但 DTS bundle 因isDev为真而被跳过——这与 scripts/README.md 中exports step 仅在--lib且不带--dev时跳过的表述完全一致若单独执行build.mts --lib无--dev跳过 exports 校验、跳过整个 types 链是最快的 library 构建路径。2.2 阶段一index 再生成 exports 校验并行阶段一的任务数组在 build.mts 中组装const step1: Promisevoid[] [ spawn(node, [./scripts/index/index.mts, --write], { signal }), ]; // ... 条件性加入 exports 校验 await parallel(step1);其中 index/index.mts 负责自动再生成各模块的 barrelindex.ts它用 glob 扫描src/下每个一级目录生成形如export * from ./xxx;的导出语句并额外把*.vert/*.frag/*.glsl/*.wgsl着色器文件按驼峰命名导出为原始字符串如export { default as BlendTemplateFrag }显式排除init.ts、browserAll.ts、webworkerAll.ts、*.worker.ts、__tests__/、__docs__/等文件保证自动生成的index.ts与公开 API 严格一致。该脚本支持两个子命令--write内容变化时写回index.ts构建流程使用--check内容不一致时报错退出提示run npm run build to update it测试流程的index检查使用见第四节。而 utils/exports.mts 负责校验并回写 package.json 的exports与sideEffects字段。它以硬编码的subImports清单./accessibility、./events、./filters、./text、./mesh、./particle-container等约 20 个子包入口为基础叠加.、./browser、./webworker、./gif、./html-source五个固定条目自动生成完整的exports映射并把所有init.ts入口以及browserAll、webworkerAll、index、rendering/init等登记到sideEffects白名单中——这与 package.json 中现有的exports/sideEffects字段内容一一对应说明该字段确实是脚本维护、随构建同步的。值得一提的是阶段一与阶段二的并行调度统一依赖 build.mts 里的parallel辅助函数它用Promise.all并发执行任务一旦任一任务失败就通过AbortController发出 abort 信号、Promise.allSettled等待其余任务收尾后再抛出错误避免留下孤儿进程。子进程层同样响应 abortutils/spawn.mts 在signal触发时向子进程发送SIGTERM并把被中断的子进程视为已解决而非失败。2.3 阶段二Rollup 打包与类型声明链并行阶段二由 build.mts 组装const rollupEnv isLib ? { LIB_ONLY: 1 } : {}; const rollup spawn(rollup, [-c, .configs/rollup.config.mjs, --failAfterWarnings], { signal, env: rollupEnv, });Rollup 侧的完整逻辑在 .configs/rollup.config.mjslibrary 产物lib/以package.json的sideEffects列表换算出的入口集合为inputpreserveModules: true保持与src/相同的目录结构输出到lib/同时产出 CJS[name].js与 ESM[name].mjs两套格式。注意 CJS 输出是条件性的!process.env.LIB_ONLY { format: cjs, ... }——即--lib模式下只输出 ESM这是LIB_ONLY1的直接效果。bundle 产物dist/由 package.json 的bundles字段驱动同样被LIB_ONLY环境变量门控if (bundles !process.env.LIB_ONLY)。每个 bundlebundle.browser.ts→dist/pixi.js/dist/pixi.mjs以及math-extras、unsafe-eval、advanced-blend-modes、gif、html-source、webworker等插件包都会额外构建一组esbuildminify _DEBUG: false的生产版本pixi.min.js等并给 IIFE 输出加上版本 banner。插件链中jscc注入_VERSION取自 package.json 版本号与_DEBUG常量rollup-plugin-string把.vert/.frag/.glsl/.wgsl内联为字符串webworker()处理.worker.ts入口alias则把~/*与test-utils映射回源码路径。与 Rollup 并行的类型声明链needsTypes为真时依次执行tsc -p .configs/tsconfig.types.json—— 依据 .configs/tsconfig.types.json 以emitDeclarationOnly模式把src/types/编译出.d.ts到lib/rootDir: src、outDir: lib排除bundle.*与__tests__。--dev模式下这一步失败不会中断流程catch里仅在非 dev 时throw err。与 tsc 并行copyfiles -u 1 src/**/*.d.ts lib/—— 把源码里手写的.d.ts如各模块的*Mixins.d.ts声明文件原样拷贝到lib/。node ./scripts/types/fixTypes.mts—— 修正生成的类型文件对应 package.json 中的 fix types 步骤。非--dev模式追加dts-bundle-generator --config .configs/dts.config.js—— 产出单一 DTS bundle作为消费方 IDE 中更完整的类型入口。三、Watch 模式源码变更驱动的增量重建package.json 中定义了三个基于 nodemon 的 watch 脚本watch:build: nodemon --watch \./src/*\ --exec \node ./scripts/build.mts --dev\ -e ts,js,vert,frag,wgsl,d.ts --ignore \index.ts\, watch:lib: nodemon --watch \./src/*\ --exec \npm run build:status start node ./scripts/build.mts --lib --dev npm run build:status done\ -e ts,js,vert,frag,wgsl,d.ts --ignore \index.ts\, watch:docs: http-server .s3_uploads/docs -p 8080 -c-1 nodemon --watch \./src/*\ --exec \npm run build:docs\ -e ts,md结合 scripts/README.md 的说明watch:lib监听src/下ts,js,vert,frag,wgsl,d.ts变更自动忽略再生成的index.ts以免自我触发每次变更以--lib --dev执行 build.mts并在构建前后调用npm run build:status start/done写入状态文件供 playground 显示编译中/就绪指示见第五节。watch:build同样的监听配置但执行完整--dev构建含类型链。watch:docs监听src/的ts,md变更重新执行build:docsTypeDoc →html-to-md.mts转换 → 拷贝压缩 bundle 到.s3_uploads/docs/并额外启动一个http-server在 8080 端口预览生成的文档。四、测试流水线test.mts 的 selector 与两阶段调度scripts/README.md 定义了统一的测试入口语法npm run test [selector...] [-- flags]scripts/test.mts 的实现与之一致以-开头的参数被收集为passthrough透传给底层工具其余参数进入 selector 集合debug被单独取出作为修饰符并从 selector 中删除不传 selector 时默认运行全部检查const all selectors.size 0。4.1 检查项一览Selector阶段是否阻塞说明含 test.mts 中的真实参数lint静态是eslint ./ --cache --max-warnings 0零容忍 lint 策略types静态是依次对.configs/tsconfig.build.json、examples/tsconfig.json、playground/tsconfig.json三套配置执行tsc --noEmit因此一次types检查实际展开为三条子检查types:.configs/tsconfig.build.json等index静态是node ./scripts/index/index.mts --check校验 barrel index 文件是否为最新与 2.2 节同一脚本的只读模式prune静态否knip --config .configs/knip.jsonc --exclude enumMembers --no-gitignoredead-code 分析失败会被记录但不立即终止unitJest是jest --config .configs/jest.config.js --testPathIgnorePatternstests/visualvisualJest是jest --config .configs/jest.config.js --testPathPatterntests/visualdebug修饰符—设置DEBUG_MODE1并去掉--silent让 Jest 以可见浏览器headful方式运行4.2 两阶段调度与退出码scripts/test.mts 把流程分为两个阶段阶段一静态检查并行。lint、types三份配置、index、prune通过Promise.allSettled并行执行随后printSummaryTable输出带 ✓/✗ 彩色图标的汇总表。若存在任一阻塞性检查失败blockingFailure脚本打印Jest tests skipped.当本还计划跑 Jest 时并process.exit(1)直接退出——这就是 scripts/README.md 所说任一阻塞检查失败则跳过 Jest 并以 1 退出。prune属于非阻塞失败仅被记录到hasNonBlockingFailure留到所有测试跑完后决定最终退出码。阶段二Jest 顺序执行。unit与visual按顺序await运行共享 .configs/jest.config.js 配置。该配置值得注意的几点runner/environment 使用pixi/jest-electron在真实 Electron 浏览器环境中跑测试这对 WebGL 类库至关重要worker文件走pixi/webworker-plugins的 jest 转换.vert/.frag/.wgsl用jest-raw-loader原样读入~/*与test-utils通过moduleNameMapper映射到源码与 tests/utils/index.ts。debug模式下额外设置环境变量DEBUG_MODE1见 scripts/test.mts。最后若 Jest 抛出错误直接process.exit(1)若此前累积过非阻塞失败knip同样以 1 退出——因此npm test的退出码语义是任何阻塞检查或测试失败、或 dead-code 分析失败都会返回非零符合 CI 门禁要求。4.3 从 npm script 到 test.mts 的映射对照 package.jsonnpm test→node ./scripts/test.mts无 selector跑全部npm run test:unit/test:visual/test:types/test:lint→ 各自传入单一 selectornpm run lint→test.mts lint --fixselector 为lint--fix作为 passthrough 进入 eslint 参数因此它带自动修复而test:lint不带。五、构建状态助手build-status.mjs 与 playground 联动scripts/README.md 提到的 scripts/build-status.mjs 是一个独立的小 CLInode scripts/build-status.mjs start # 写入 { status: compiling, startedAt: ... } node scripts/build-status.mjs done # 写入 { status: ready, completedAt: ... }其实现只有几十行start分支写入{ status: compiling, startedAt: Date.now() }done分支写入{ status: ready, completedAt: Date.now() }目标文件固定为仓库根目录的.build-status.jsonresolve(__dirname, ../.build-status.json)非法参数则报错退出。消费端在 playground/src/hooks/useBuildStatus.ts该 hook 每 2 秒fetch(/.build-status.json)轮询一次URL 加时间戳防缓存状态从compiling变为ready时自动延迟 500ms 刷新页面以加载新构建的 bundle取不到文件时默认按ready处理。配合watch:lib中build:status start→build.mts --lib --dev→build:status done的包裹结构就构成了 scripts/README.md 描述的playground 显示黄色compiling/绿色ready指示的完整闭环。npm start正是把 playground 开发服务器npm run dev --prefix playground与watch:lib用并发拉起的组合命令。六、小结PixiJS 仓库的scripts/目录把构建与质量检查两条链路收敛到了三个核心入口build.mts--lib/--dev双开关 两阶段并行 abort 兜底、test.mtsselector 选择 静态检查并行、阻塞失败快停 Jest 顺序执行以及 build-status.mjswatch 模式下与 playground 的构建状态联动。理解这套编排后你可以用npm run build:lib快速迭代 library 产物用npm run build产出发布级 bundle DTS用npm run test lint之类精确指定单个检查用npm run test debug unit以可见浏览器模式调试单测用--后的参数向底层工具透传选项如npm run lint已内置--fix通过 .configs/ 下的 rollup/jest/tsconfig/knip 配置进一步定制构建与测试行为。【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考