Skip to content

chore(deps): upgrade phase 4 — pnpm 11, TS 6, ESLint 10, React 19 - #19

Merged
Jerry-CodeHub merged 7 commits into
masterfrom
chore/deps-phase4
Sep 23, 2026
Merged

Jerry-CodeHub merged 7 commits into
masterfrom
chore/deps-phase4

Conversation

@Jerry-CodeHub

@Jerry-CodeHub Jerry-CodeHub commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

叠加 PR:依赖 #16 → #17 → #18(按顺序合并)

背景

依赖升级 阶段 4:pnpm 11、TypeScript 6、ESLint 10 / stylelint 17、React 19。以阶段 3(chore/deps-phase3)为 base。

⚠️ CI 在 2026-09-24 01:25 UTC 之前会失败(已确认原因):pnpm 11 默认的 minimumReleaseAge(1 天)拒绝了 lockfile 中两个发布不满 24 小时的版本:

  • tinymce@8.9.2(阶段 1 引入,2026-09-23 01:25 UTC 发布)
  • ignore@7.0.10(传递依赖,2026-09-22 15:25 UTC 发布;本阶段本地解析时为绕过上一条设置了 pnpm_config_minimum_release_age=0,顺带解析进来的)

pnpm install 和 pnpm run(每次都会重新校验 lockfile)在这段时间内都会报 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION。过了上面的时间点重跑 CI 即可,不需要改代码。

改动

pnpm 10 → 11.27.1

  • pnpm 11 不再读取 package.json#pnpm 和 .npmrc 中鉴权以外的配置。直接升版本会让 49 条安全 overrides 和提升配置静默失效。因此:
    • overrides 与各项配置迁入 pnpm-workspace.yaml
    • .npmrc 只保留 registry 说明
    • 用 allowBuilds 显式记录 core-js、es5-ext、esbuild 的安装脚本不执行(与 pnpm 10 下的实际行为一致)
  • Dockerfile 在 frozen 安装前拷贝 pnpm-workspace.yaml,否则 lockfile 与缺失的 overrides 对不上
  • lockfile 仍是 lockfileVersion 9.0,内容逐字节不变
  • 没选 12.x 的原因:12 是 Rust 重写版,以各平台原生二进制分发。本机的 pnpm 10 自动切换到它时报 ENOEXEC;11.x 能正常切换,且配置模型相同

关闭 shamefullyHoist

  • node_modules 顶层只保留已声明的依赖
  • 产物秘钥扫描脚本与 mock 可加载性测试原本直接从根目录读传递依赖,现改为沿依赖链解析(cesium → @cesium/engine、@umijs/max → umi → @umijs/preset-umi)

TypeScript 5.9 → 6.0:6.0 是最后一个带 JS 编译器 API 的版本。TS 7 暂缓,因为 typescript-eslint 最新版的 peer 为 <6.1.0。实测 TS 7.0.2 对本项目做类型检查:0 错误,耗时 1.35 秒。

ESLint 8 → 10,stylelint 14 → 17(脱离 @umijs/lint 预设)

  • ESLint 9 已于 2026-08-06 停止维护,因此直接上 10
  • eslint.config.mjs 逐条移植 umi 的推荐规则集:
    • jest 规则改用 @vitest/eslint-plugin 的同名规则
    • typescript-eslint 8 删除的 ban-types / no-empty-interface 换成对应的新规则
    • 用一个故意写满违规的探针文件确认了每一类规则都在生效
  • stylelint.config.mjs 移植 umi 的 stylelint 预设
  • 新规则暴露的问题都已修复:d3 回调声明 this 类型;去掉 3 个未使用的 catch 变量
  • 两个坑已在配置注释里写明:
    • eslint-plugin-react 在 version: 'detect' 下会调用 ESLint 10 已删除的 context.getFilename() 而直接崩溃,版本号已写死
    • umi 内置的 Less 解析不了媒体查询范围语法,stylelint 对 .less 固定使用前缀写法

React 18 → 19(umi 4.7 会把 react 别名到项目自己安装的版本;产物 framework chunk 中确认为 19.3.0)

  • @pansy/react-amap 移除:它的 Marker 调用了 React 19 已删除的 unmountComponentAtNode,且该库 2024-06 之后没有再发布。高德页改为直接调用 JS API 2.0;加载器仍用不依赖 React 的 @pansy/amap-api-loader,所以未配置 key 时的行为不变
  • antd 5 的静态方法内部用 ReactDOM.render 挂载,React 19 已删除该 API,因此加载官方的 @ant-design/v5-patch-for-react-19
  • react-pdf 11:
    • 要求 React 19,且默认启用 Suspense。本页设置 suspense={false},保留原来由 onLoadError 驱动的告警
    • pdfjs-dist 同步到 6.3.289;cmaps、standard_fonts 和 worker 的路径都不变
  • @types/react 19:useRef 必须传初始值;RefObject<T> 的类型现在包含 null

验证

  • 逐像素对比:与阶段 3 相比,17 个页面全部 0 像素差
  • 24 个路由的控制台为 0 报错,没有任何 React 19 告警
  • 交互检查全部通过:
    • 高德地图:点击落点出现坐标提示和 marker,离开页面后卸载无报错
    • PDF:渲染出 2 页
    • 日历、表格、签名、拖拽、OpenLayers
  • 在不提升依赖的布局下也验证过:类型检查、ESLint 10、stylelint、59 个单测、生产构建(体积一致)、产物秘钥扫描,以及 dev 服务器(MFSU)
  • 最终状态:pnpm check、build:github、pnpm size、产物秘钥扫描全部通过;audit 为 0 high / 4 moderate / 1 low

需要注意

  • 体积余量很小:React 19 让首屏约增加 23KB。入口 762KB(预算 780),登录页 764KB(预算 780),首页 1204KB(预算 1250)。预算未放宽
  • 本地首次切换到 pnpm 11:pnpm 10 会自动切到 11,但需要清空重建 node_modules,非交互终端要加 --config.confirm-modules-purge=false。从本分支切回旧分支时同样需要重装
  • 未验证:Docker 镜像构建(本机 Docker 未运行);Vercel 上 pnpm 11 的实际安装表现(lockfileVersion 未变)
  • antd 6 + pro-components 3 不在本 PR:pro-components 3 至今仍是 beta。相关尝试保留在本地的实验分支 chore/deps-phase4-antd6-experimental,未推送。功能上已通过,但 umi 的布局模板需要改走 v3 的 API,且 D3 频率图会溢出卡片,尚未修复

pnpm 11 stops reading package.json#pnpm and every non-auth .npmrc key,
so a bare version bump would silently drop all 49 security overrides
and shamefully-hoist.
- overrides, shamefullyHoist, autoInstallPeers, strictPeerDependencies
  move to pnpm-workspace.yaml; .npmrc keeps only the registry guidance
- allowBuilds records the existing decision explicitly: core-js,
  core-js-pure, es5-ext and esbuild postinstalls stay disabled (pnpm 10
  already skipped them; 11 fails the install until each is decided)
- Dockerfile copies pnpm-workspace.yaml before the frozen install,
  otherwise the lockfile no longer matches the (missing) overrides
- lockfile stays lockfileVersion 9.0, unchanged byte for byte

pnpm 11.27.1 rather than 12.x: 12 is a Rust rewrite shipped as native
per-platform optional dependencies (released 2026-08-26); the pnpm 10
self-switch to it fails locally with ENOEXEC because the exe placeholder
only becomes a binary through a postinstall that pnpm skips. The 11 line
self-switches cleanly and carries the same config model.

note: minimumReleaseAge (default 1 day) rejects tinymce 8.9.2 until
2026-09-24T01:25Z; `pnpm run` re-verifies the lockfile, so scripts and
the pre-commit hook fail in that window too. Verified with
pnpm_config_minimum_release_age=0.
6.0 is the last release with the JavaScript compiler API. The project
tsconfig (umi's generated bundler-mode config, no baseUrl) hits none of
the 6.0 deprecations; typecheck, lint and the organize-imports prettier
plugin all run clean on it.

TypeScript 7 stays out: its package entry only exports `version`, and
typescript-eslint (latest 8.70.1) peers on typescript <6.1.0. As a probe,
`npx -p typescript@7.0.2 tsc --noEmit` checks this codebase with zero
errors in 1.35s (5585 files) - ready once the lint toolchain catches up.
@umijs/max pins ESLint 8.35 (EOL 2024-10), typescript-eslint 5 and
stylelint 14 behind `extends: @umijs/max/eslint` and `max lint`. ESLint
9 is EOL as well (2026-08-06), so the target is ESLint 10.
- eslint.config.mjs ports the umi recommended rule set one to one (core,
  react, rules-of-hooks, typescript) plus the project overrides; jest
  rules on test files map to @vitest/eslint-plugin; typescript-eslint 8
  replacements for the removed ban-types / no-empty-interface. Probed
  with a planted file: every rule family still fires
- eslint-plugin-react / jsx-a11y peer on ESLint <=9 but run clean on 10
  (they only use context APIs that 10 kept); peer warnings accepted
- stylelint.config.mjs ports the umi stylelint preset (standard,
  css-modules, no-ignored-properties, postcss-less for .less);
  stylelint-config-prettier is obsolete since formatting rules are gone.
  import-notation is `string` because Tailwind v4 only resolves string
  @imports; lint:style now also covers tailwind.css
- new findings fixed rather than silenced: d3 callbacks declare their
  `this` type (no-invalid-this), three unused catch bindings dropped
  (caughtErrors defaults to all in v8); media-feature-range-notation is
  pinned to `prefix` for .less - umi's bundled Less fails on
  `(width <= 768px)` with "Missing closing ')'" (caught by a build)
- lint-staged uses --no-warn-ignored; .eslintignore folds into the flat
  config ignores; the import guard no longer mistakes rule names like
  'vitest/no-mocks-import' for imports
With every source import declared (repo-hygiene gate) and the lint
toolchain now a direct dependency, the flat hoisted layout is no longer
needed - node_modules top level now holds only the declared packages
instead of every transitive one, so an undeclared import fails the
same way on every machine instead of resolving by accident.
- check-dist-secrets resolves @cesium/engine's Ion.js through the
  cesium package (it is cesium's dependency, not ours); its test does
  the same
- the mock loadability test reaches @umijs/preset-umi along the
  @umijs/max -> umi chain; the hygiene allowlist shrinks to react and
  react-dom (umi-provided by design)

verified without hoisting: typecheck, ESLint 10, stylelint, 59 tests,
production build (sizes identical), dist secret scan, dev server with
MFSU across 24 routes plus calendar/table/signature/PDF/Cesium/OL
interaction checks
umi 4.7 aliases react/react-dom to the project's own copies and mounts
through createRoot, so React 19 is a direct dependency now (19.3.0 is
what ships in the framework chunk).
- @pansy/react-amap is gone: its Marker goes through @pansy/use-portal,
  which calls unmountComponentAtNode - removed in React 19 - and the
  library has not been published since 2024-06. The AMap page talks to
  JS API 2.0 directly (map, click-to-place marker, destroy on unmount)
  and keeps @pansy/amap-api-loader, which is React-free, so the
  no-AMAP_KEY fallback behaves exactly as before
- antd 5 static methods mount via ReactDOM.render; the official
  @ant-design/v5-patch-for-react-19 is loaded first thing in app.tsx
  (drop it together with an antd 6 upgrade)
- react-pdf 11 requires React 19 and suspends by default; the PDF page
  opts out with suspense={false} to keep its onLoadError alert.
  pdfjs-dist follows to 6.3.289 exactly (hygiene gate); cmaps and
  standard_fonts keep their paths, the worker path is unchanged
- @types/react 19: useRef needs an initial value, RefObject<T> now
  carries null
- eslint-plugin-react keeps an explicit version: its 'detect' path calls
  context.getFilename(), which ESLint 10 removed (hard crash)

verified: 17 pages pixel-identical to phase 3; 24 routes with zero
console errors (no React 19 warnings); AMap click places a marker and
unmount is clean; PDF renders 2 pages; calendar/table/signature/dnd/OL
interactions pass; size gate passes (entry 762 KB of 780, login 764 of
780, home 1204 of 1250 gzip)
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 43 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d59ddb2e-3399-46b5-88a4-edc794e48a07

📥 Commits

Reviewing files that changed from the base of the PR and between 4600c7c and 36fe74b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .claude/CLAUDE.md
  • .eslintignore
  • .eslintrc.js
  • .lintstagedrc.json
  • .npmrc
  • .stylelintrc.js
  • CHANGELOG.md
  • CONTRIBUTING.md
  • Dockerfile
  • README.md
  • eslint.config.mjs
  • nginx/security-headers.conf
  • package.json
  • pnpm-workspace.yaml
  • scripts/check-dist-secrets.mjs
  • scripts/check-dist-secrets.test.ts
  • scripts/mock-loadable.test.ts
  • scripts/repo-hygiene.test.ts
  • src/app.tsx
  • src/pages/Feature/AudioFeature/AudioVisible/index.tsx
  • src/pages/Feature/D3/Frequency.tsx
  • src/pages/Feature/Map/AutonaviMap/index.tsx
  • src/pages/Office/Pdf/index.tsx
  • src/pages/Table/index.tsx
  • stylelint.config.mjs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying umi-react-admin with  Cloudflare Pages  Cloudflare Pages

Latest commit: 36fe74b
Status:🚫  Build failed.

View logs

Base automatically changed from chore/deps-phase3 to master September 23, 2026 07:25
@Jerry-CodeHub
Jerry-CodeHub merged commit 709300d into master Sep 23, 2026
2 of 4 checks passed
@Jerry-CodeHub
Jerry-CodeHub deleted the chore/deps-phase4 branch September 23, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant