
IPTVnator Nx 架构边界指南代码归属、模块标签与变更验证实践【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator本文基于.codex/skills/iptvnator-nx-architecture/SKILL.md与仓库内权威文档 docs/architecture/nx-workspace-boundaries.md 展开。IPTVNator 是一个基于 Nx 的跨平台 IPTV 播放器其 monorepo 同时承载 Web 前端Angular、Electron 后端、E2E 应用与多个 mock 服务器模块边界一旦失控构建缓存与 ESLint 约束都会失守。读完本文你将掌握一套完整的先发现、再归属、后验证的 Nx 工作流如何用nx show命令摸清项目全貌、如何按scope/domain/type标签决定代码归属、如何解读并遵守类型依赖方向表以及如何在提交前用行数限制与命令式 lint 目标验证变更。理解仓库全貌为什么模块边界如此重要IPTVNator 的 Nx 工作区是一个多应用、多库的 monorepo。从 apps 目录可以看到它同时包含webAngular 前端、electron-backendElectron 主进程、web-backend、remote-control-web、websiteAstro 官网、web-e2e、electron-backend-e2e、stalker-mock-server、xtream-mock-server等应用而 libs 下则按portal、playlist、ui、playback、workspace等产品域组织了大量共享库。这种规模的仓库中代码放哪里不再是一个风格问题而是一个可被机器强制校验的工程契约。nx/enforce-module-boundaries规则与scope/domain/type标签体系共同决定了哪些模块可以依赖哪些模块任何越界 import 都会在 lint 阶段直接报错。因此SKILL 文档开宗明义地提出两条铁律先发现再决策Discover Before Deciding一切放置、迁移、改标签的决策必须以 Nx 自身的项目发现结果为准而不是凭记忆或文档中的旧清单。边界只能维护不能削弱永远不要为了方便放代码而放宽约束。先发现再决策用 Nx 命令建立权威项目清单SKILL 文档强调Discovery is authoritative发现结果是权威的。在一个全新的工作树fresh worktree中第一步永远是安装依赖然后让 Nx 自己告诉你仓库里有什么pnpm install --frozen-lockfile pnpm nx show projects pnpm nx show project name pnpm nx show projects --withTarget test pnpm nx show projects --withTarget e2e逐条解释pnpm install --frozen-lockfile严格按 pnpm-lock.yaml 安装依赖。pnpm nx show projects依赖工作区本地的 Nx 包即node_modules中的 Nx 可执行文件未安装就运行会失败。pnpm nx show projects列出 Nx 项目图中全部项目是权威的项目清单来源。docs/architecture/nx-workspace-boundaries.md 明确要求Nx discovery is the canonical project inventory; avoid copying an exhaustive project list into documentation——即避免在文档里复制一份会过时的完整项目列表一切以命令输出为准。pnpm nx show project name查看单个项目的标签tags、targets、依赖关系等细节是判断这个项目是什么角色、能依赖谁的第一手证据。pnpm nx show projects --withTarget test/--withTarget e2e筛选出拥有test或e2etarget 的项目用于决定变更后应该跑哪些测试。当前应用组app groups包括web、electron-backend、web-backend、remote-control-web、website、web-e2e、electron-backend-e2e、stalker-mock-server、xtream-mock-server工具项目tool projects包括eslint-tools、packaging、release-tools、repository-skills。例如 tools/eslint/project.json 中的eslint-tools项目其标签为[scope:tools, domain:lint, type:tool]正是仓库自动化工具的典型代表。不要凭空发明 target一个常见的错误是看到某个类似项目有test、build或e2etarget就假设自己修改的项目也有。SKILL 文档明确警告do not invent targets——必须先nx show project name确认 target 真实存在。例如 libs/playback/util/project.json 的testtarget 并不是标准的nx/jest:jest而是一个通过tools/testing/run-web-esm-lib-tests.mjs运行 ESM 测试的自定义命令这正是同名 target 背后实现可能完全不同的例证。代码归属apps / libs / tools 三层目录契约SKILL 文档给出的放置规则非常清晰apps/运行时代码、开发服务器、E2E 应用与 mock 服务器例如stalker-mock-server、xtream-mock-server都放在这里。tools/仓库自动化工具lint、打包、发布、技能校验等其中的 Nx 项目统一打scope:tools标签。libs/按产品域与架构角色组织可复用代码。在libs/内部SKILL 文档要求先选角色再选路径choose the role before the path四种type角色的职责如下type 标签职责type:feature路由、页面screen与功能编排route and screen orchestrationtype:ui可复用的视觉组件type:data-access可注入的状态、API 访问、持久化或编排逻辑type:util仅用于纯函数pure helpers与契约contracts的归宿以 portal 共享模块为例SKILL 文档用一个非常具体的例子说明同域代码如何按角色分流供应商无关的集合类服务协调收藏、最近观看、EPG、播放持久化等服务应放在libs/portal/shared/data-access纯集合类型与转换函数应放在libs/portal/shared/util可复用的集合视图应放在libs/portal/shared/ui。这与 tsconfig.base.json 中的iptvnator/portal/shared/ui、iptvnator/portal/shared/data-access、iptvnator/portal/shared/util三个 scoped alias 一一对应。一个关键提醒现有位于util路径下的可注入/有状态服务属于历史遗留债务绝不能成为新代码的放置先例Existing injectable/stateful services under autilpath are legacy debt, not placement precedent。也就是说看到旧代码违反规则不代表新代码可以照做。以播放模块为例docs/architecture/nx-workspace-boundaries.md 给出了播放域的同类拆分浏览器与 Angular 播放器集成代码放在libs/ui/playback对应iptvnator/ui/playback而DOM 无关的诊断契约与分类器放在libs/playback/util即playback-util项目通过iptvnator/playback/util导入。libs/playback/util/project.json 展示了这一项目的精确标签[scope:shared, domain:playback, type:util]。它不拥有任何 Angular、DOM、设置、存储、UI 或 Electron IPC 逻辑浏览器/播放器适配器负责收集引擎事件并提供显式的能力事实playback-util只负责在不检查运行时全局对象的前提下进行分类与排序。由于它是type:util它只能依赖其他工具类项目包括共享接口契约而ui-playback和 feature 宿主可以依赖它来渲染和执行会话级的恢复动作。维护边界scope / domain / type 三标签体系每个 Nx 项目在project.json中必须从三个标签族中各持有一个标签eslint.config.mjs 强制校验scope:*所有权归属例如scope:portal、scope:workspace、scope:shared、scope:electron、scope:e2e、scope:tools。domain:*产品/运行时领域。type:*架构角色上文的 feature / ui />eslint apps/project/**/*.ts find apps/project -name *.ts | wc -l原因不加引号的**在 POSIX 下可能只展开成一个浅层子集命令照样返回成功——你以为是全量 lint实际上只检查了一小部分文件。因此每次修改这类 target 后都要把 ESLint 实际 lint 的文件数与find统计的文件数对比二者必须一致。仓库工具侧tools/内的 Node 脚本存在镜像陷阱Windows 下 Node 的execSync走cmd.exe单引号是字面字符而非引号POSIX 风格加引号的 pattern 会原样传给程序却匹配不到任何文件。正确做法是execFileSync(git, [ls-files, *.scss])——不用 shell让程序自己展开 pattern。两种陷阱的共同点是扫描了空文件集却返回成功因此检查空文件集的校验必须设计为失败而非通过。共享样式表Nx 图推不出来的依赖Nx 的项目图是从 TypeScript import 推导的而跨项目根的相对 Sassuse不会生成图边graph edge。结果被引入的 partial 不属于任何 task 的输入集改了样式后构建却报cache hit并继续产出旧 CSS——一次静默的错误构建而不是失败。docs/architecture/nx-workspace-boundaries.md 记录了防止此问题的两条规则被其他项目消费的目录本身就是一个 Nx 项目。共享 partial 集中在libs/ui/styles对应ui-styles项目project.json标签[scope:shared, domain:shared-ui, type:ui]。它不声明任何 target存在的意义就是让这些文件进入哈希输入集。每个消费者显式声明 Nx 无法推断的依赖implicitDependencies: [ui-styles]nx/enforce-module-boundaries不读取样式表所以标签方向在这里不强制——消费者保持type:feature或type:ui即可两者都允许依赖type:ui。另一个边界情况应用自己拥有的 partial 被自己引入无需声明它本就在应用自己的构建输入内但这仍是错误方向——库 → 应用的边会让图成环应用本就依赖这些库唯一的解法是把 partial 移入ui-styles。目前仓库中没有任何库样式表从apps/导入应保持这一状态。验证命令为pnpm run styles:inputs:validate见 package.json 中styles:inputs:test与styles:inputs:check。它会把工作区内所有相对use/forward/import解析到 Nx 自己的项目图上当某个被导入的样式表位于编译它的构建输入闭包之外时报错并指出应声明的项目。校验细节上import是唯一接受逗号分隔列表的规则每个目标都是独立依赖只读第一个会让后面的跨项目目标逃出缓存键use/forward模块名之后的带引号字符串属于with (...)配置是值而非输入url(...)是浏览器运行时解析的纯 CSS import同样不算构建输入。验证变更的完整工作流综合 SKILL 文档与权威文档一次合规的变更流程是# 1. 发现确认项目与 target 真实存在 pnpm nx show project name pnpm nx show projects --withTarget test pnpm nx show projects --withTarget e2e # 2. 运行受影响项目的 lint/test/build 与最近的可用 E2E target pnpm nx affected -t lint test build pnpm nx e2e e2e-project # 仅当该 E2E target 真实存在 # 3. 拆分超限文件后重新生成基线 node tools/eslint/generate-max-lines-baseline.mjs不要因为某个同名项目有test/build/e2etarget 就假定自己也该有运行受影响的affected既有 target并为变更行为运行最近可用的E2E target。此外E2E 应用即使只通过 HTTP 而非 TypeScript import 使用后端也必须声明运行时依赖——例如web-e2e在implicitDependencies中包含web-backend否则 provider 代理的改动不会使自托管 PWA 测试失效仅靠serve依赖启动后端不会让它的源文件进入测试哈希。持续集成边界在 CI 中的强制力边界约束最终由 CI 兜底。CI 的 lint 任务在 PR 上运行受影响的affected项目、在 master 推送时运行全部项目。根配置eslint.config.mjs或 lockfile 的任何变动都会影响所有项目因此模块边界、legacy 别名限制与 max-lines 强制在整个工作区范围内生效——这意味着任何人在任何项目上绕开规则都会在 CI 上被统一拦截。仓库内的配套校验命令还包括pnpm run deps:nx:validateNx 版本一致性检查与pnpm run styles:inputs:validate样式表输入闭包检查共同构成从代码归属到构建缓存正确性的完整护栏。小结IPTVNator 的 Nx 架构边界实践可以浓缩为一条决策链先用nx show发现事实再按scope/domain/type标签选择归属然后让 ESLint 与行数限制替你做最终裁决。对开发者而言最值得记住的三件事是type:util是依赖图的叶子只收纯函数与契约有状态服务该去data-access路径名里的 util 不构成先例。依赖方向是单向收敛的domain 约束与 type 约束叠加生效遇到越界 import 的正确处置是移动代码而不是放宽约束。提交前用命令验证而非猜测nx show project name确认 target、引号包裹递归 glob、对比 lint 文件数并在拆分文件后重新生成 max-lines 基线。更多细节可进一步阅读 docs/architecture/nx-workspace-boundaries.md本主题的权威文档、eslint.config.mjs边界规则的实际落地与 tools/eslint/max-lines-config.mjs行数限制的唯一权威来源。【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考