@epxing/cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,41 @@
1
- # Temporary Holding Version
1
+ # @epxing/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 独立的产品/项目开发工具,包含创建、Vue3 迁移、静态检查和升级技能安装。CLI 使用自己的版本号,目标平台由 `--epx-frame` 指定;发布包没有 npm 运行时依赖,不包含 Vue、Element Plus、设计器或 `epx-frame`。迁移代码与框架仓库的 `tools/` 共用,打包时编译为独立 Node 程序。
4
+
5
+ ```powershell
6
+ npm install -g @epxing/cli@0.1.0 --registry https://registry.npmjs.org/
7
+ epx create my-product --epx-frame <目标平台版本>
8
+ epx migrate E:/products/old-product --epx-frame <目标平台版本>
9
+ epx migrate E:/products/old-product --epx-frame <目标平台版本> --apply
10
+ epx skill install --dir E:/products/my-product
11
+ ```
12
+
13
+ 如已全局安装旧的 `epx-frame` CLI,先用 `npm uninstall -g epx-frame` 释放 `epx` 命令。产品工程里的 `epx-frame` 依赖保留。工具不在安装阶段运行迁移、下载平台或改产品文件;产品仍需安装目标框架依赖才能开发和构建。
14
+
15
+ `create` 和 `migrate --apply` 自动写入 `runtime: "vue3"`。迁移默认预览;apply 要求目标 Git 工作树干净。实际执行 `.vue` 转换时才按需安装并缓存 gogocode。标识表示迁移目标,仍需完成转换、构建和业务复测。
16
+
17
+ `epx scan <目录>` 扫描静态风险,`epx verify <目录> --online <地址|文件>` 对照组件清单。技能安装到 `.claude/skills/epxing-frontend-vue3-migrate/`,可用 `--skills-dir` 指定其他客户端要求的位置。已有技能默认不覆盖;确认替换使用 `--force`。平台基类源码分析仍需准备对应版本源码或文档。
18
+
19
+ 在框架源码仓库发布工具,不需要构建平台 `lib/`:
20
+
21
+ ```powershell
22
+ cd frontend/frame/packages/cli
23
+ npm login --registry https://registry.npmjs.org/
24
+ # 首次使用 0.1.0;后续发布前调整为未发布过的新版本
25
+ # npm version <新的CLI版本> --no-git-tag-version
26
+ $previousPublicPublish = $env:EPX_PUBLISH_PUBLIC
27
+ try {
28
+ $env:EPX_PUBLISH_PUBLIC = '1'
29
+ npm publish --access public --registry https://registry.npmjs.org/
30
+ } finally {
31
+ $env:EPX_PUBLISH_PUBLIC = $previousPublicPublish
32
+ }
33
+ ```
34
+
35
+ 发布账号需要拥有 `@epxing` 名称空间的发布权限,并完成 npm 要求的发布身份验证。`publishConfig` 固定公开发布到 npm 公网;上面的命令仅为这次操作启用共享守卫的显式公开发布开关,并在结束后恢复原值。`prepack` 自动构建工具并带入当前技能文件。可在框架根执行 `pnpm build:cli`,或在这里运行 `npm pack` 生成可独立安装的 tgz。技能与 CLI 同版本交付,平台升级不要求工具同步发版;工具规则、模板或技能更新时才发布新 CLI。新平台若改变接口或构建约定,仍需验证并更新相应工具规则。
36
+
37
+ 在框架根运行 `npm --prefix packages/cli test`,验证真实 tgz 的离线安装、无平台依赖运行、创建、迁移、扫描和技能安装。
38
+
39
+ 首次发布前,需创建或加入 npm 的 `epxing` 组织。可用 `npm org ls epxing --json --registry https://registry.npmjs.org/` 核对组织角色。若发布返回 `403 Two-factor authentication or granular access token with bypass 2fa enabled is required`,表示当前发布凭据未满足 npm 要求;仅 `npm whoami` 成功不代表可以发布。可在 npm 网站创建具有目标 scope 发布权限、启用 `Bypass 2FA` 的细粒度令牌,并配置到本机用户 npm 设置;令牌不写入项目或发布包。参见 [npm 发布身份验证要求](https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification/)。
40
+
41
+ 令牌的 `Packages and scopes` 权限应选择 `Read and write (publish and stage)`,范围覆盖 `@epxing`;`Read and write (stage only)` 无法直接发布。`Organizations` 里的读写权限只管理组织、团队和成员,不授予组织包的发布权限。首次创建包时需覆盖相应 scope。参见 [npm 令牌权限说明](https://docs.npmjs.com/creating-and-viewing-access-tokens/)。
@@ -0,0 +1,21 @@
1
+ @babel/parser 7.29.3
2
+
3
+ Copyright (C) 2012-2014 by various contributors (see AUTHORS)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/dist/cli.mjs ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console -- CLI 输出 */
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import { fileURLToPath, pathToFileURL } from 'node:url'
7
+
8
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
+ const payload = path.join(packageRoot, 'dist')
10
+ const skillName = 'epxing-frontend-vue3-migrate'
11
+
12
+ function takeOption (args, name) {
13
+ const index = args.findIndex(arg => arg === name || arg.startsWith(name + '='))
14
+ if (index < 0) return null
15
+ const arg = args[index]
16
+ if (arg.startsWith(name + '=')) {
17
+ args.splice(index, 1)
18
+ const value = arg.slice(name.length + 1)
19
+ if (!value) throw new Error(`${name} 缺少值`)
20
+ return value
21
+ }
22
+ const value = args[index + 1]
23
+ if (!value || value.startsWith('--')) throw new Error(`${name} 缺少值`)
24
+ args.splice(index, 2)
25
+ return value
26
+ }
27
+
28
+ function requireTarget (args) {
29
+ const target = takeOption(args, '--epx-frame')
30
+ if (!target) throw new Error('请用 --epx-frame 指定目标平台版本或包 spec;CLI 版本不作为平台版本。')
31
+ return target
32
+ }
33
+
34
+ function runTool (script, args, cwd) {
35
+ const result = spawnSync(process.execPath, [path.join(payload, 'tools', script), ...args], { cwd, stdio: 'inherit' })
36
+ if (result.error) throw result.error
37
+ return result.status ?? 1
38
+ }
39
+
40
+ function installSkill (args, cwd) {
41
+ const dir = path.resolve(cwd, takeOption(args, '--dir') || '.')
42
+ const custom = takeOption(args, '--skills-dir')
43
+ const force = args.includes('--force')
44
+ const unknown = args.filter(arg => arg !== '--force')
45
+ if (unknown.length) throw new Error(`不支持的参数:${unknown.join(' ')}`)
46
+ const dest = path.join(custom ? path.resolve(dir, custom) : path.join(dir, '.claude/skills'), skillName)
47
+ const files = ['SKILL.md', 'pitfalls.md']
48
+ if (!force && files.some(file => fs.existsSync(path.join(dest, file)))) {
49
+ throw new Error(`技能已存在:${dest}。如需替换,核对自定义内容后使用 --force。`)
50
+ }
51
+ // 先读齐资源,再写入,避免缺少发布资源时只安装一半。
52
+ const contents = files.map(file => fs.readFileSync(path.join(payload, 'skills', skillName, file)))
53
+ fs.mkdirSync(dest, { recursive: true })
54
+ files.forEach((file, index) => fs.writeFileSync(path.join(dest, file), contents[index]))
55
+ console.log(`✓ 已安装技能:${dest}`)
56
+ return 0
57
+ }
58
+
59
+ export async function main (argv = process.argv.slice(2), cwd = process.env.INIT_CWD || process.cwd()) {
60
+ const [command, ...args] = argv
61
+ if (!command || ['help', '--help', '-h'].includes(command)) {
62
+ console.log(`@epxing/cli · 产品开发工具(独立于平台版本)
63
+
64
+ epx create <名称|路径> --epx-frame <目标版本> [--dir <目录>]
65
+ epx migrate [项目目录] --epx-frame <目标版本> [--apply] [--transform-vue] [--to-vite]
66
+ epx scan [项目目录]
67
+ epx verify [项目目录] [--online <线上JS地址|文件>]
68
+ epx skill install [--dir <项目目录>] [--skills-dir <技能目录>] [--force]
69
+ epx --version
70
+
71
+ 创建和升级自动写入 runtime: "vue3"。迁移默认 dry-run,--apply 才落盘。
72
+ 工具不会自动安装平台依赖;.vue 转换工具仅在执行 --apply --transform-vue 时下载并缓存。`)
73
+ return 0
74
+ }
75
+ if (['--version', '-v', 'version'].includes(command)) {
76
+ console.log(JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version)
77
+ return 0
78
+ }
79
+ if (command === 'skill') {
80
+ if (args.shift() !== 'install') throw new Error('用法:epx skill install [--dir <项目目录>]')
81
+ return installSkill(args, cwd)
82
+ }
83
+ if (command === 'create') {
84
+ const target = requireTarget(args)
85
+ const chosenDir = takeOption(args, '--dir')
86
+ const nameOrPath = args.shift()
87
+ if (!nameOrPath || args.length || nameOrPath.startsWith('-')) throw new Error('用法:epx create <名称|路径> --epx-frame <目标版本> [--dir <目录>]')
88
+ const dir = path.resolve(cwd, chosenDir || nameOrPath)
89
+ const name = /[\\/]/.test(nameOrPath) ? path.basename(path.resolve(cwd, nameOrPath)) : nameOrPath
90
+ const { scaffold } = await import(pathToFileURL(path.join(payload, 'tools/epx-create/scaffold.mjs')).href)
91
+ scaffold({ name, dir, epxFrameSpec: target })
92
+ return 0
93
+ }
94
+ if (command === 'migrate') {
95
+ const target = requireTarget(args)
96
+ const dirs = args.filter(arg => !arg.startsWith('-'))
97
+ if (dirs.length > 1) throw new Error('一次迁移一个项目目录')
98
+ const dir = dirs[0] || '.'
99
+ return runTool('epx-migrate/migrate.mjs', [path.resolve(cwd, dir), ...args.filter(arg => arg !== dir), '--epx-frame', target], cwd)
100
+ }
101
+ if (command === 'scan' || command === 'verify') {
102
+ const online = command === 'verify' ? takeOption(args, '--online') : null
103
+ if (args.length > 1 || args.some(arg => arg.startsWith('-'))) throw new Error(`用法:epx ${command} [项目目录]${command === 'verify' ? ' [--online <地址|文件>]' : ''}`)
104
+ const script = command === 'scan' ? 'detect-vue3-risks.mjs' : 'verify-components.mjs'
105
+ return runTool('epx-migrate/' + script, [path.resolve(cwd, args[0] || '.'), ...(online ? ['--online', online] : [])], cwd)
106
+ }
107
+ throw new Error(`未知命令:${command},运行 epx help 查看用法。平台构建命令仍在框架仓库使用 pnpm epx。`)
108
+ }
109
+
110
+ let isMain = false
111
+ try { isMain = !!process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url) } catch (error) { /* 作为模块导入 */ }
112
+ if (isMain) {
113
+ main().then(status => { process.exitCode = status }).catch(error => { console.error('✗ ' + error.message); process.exitCode = 1 })
114
+ }
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: epxing-frontend-vue3-migrate
3
+ description: epx 产品/项目远程库 Vue2/webpack → Vue3/Vite 升级 + 按需打包(lazy/chunks 形态)的完整流程、判据与踩坑清单;也用于排查远程库「构建绿、控制台干净、组件却静默失效」——不渲染 / 注册名错 / 有组件没样式 / 登录拉整包 / 弹窗标题空 / 图表画不出 / 卡片 scoped 样式丢。含 epx-migrate CLI(migrate 迁移、detect-vue3-risks 发布前静态扫风险、verify-components 组件清单对齐)与三方对比环境搭法(本地 dist + 工装 + Web Worker 定时器垫片 + 运行时实证)。触发:产品升级 / Vue3 迁移 / 迁移包对比测试 / 升级后静默失效排查。
4
+ ---
5
+
6
+ # 升级产品远程库(Vue3 + Vite + 按需打包)
7
+
8
+ ## 这是什么
9
+
10
+ epx 的产品/项目远程库(`window.library.<pkg>`,由 `FM_SYSTEM_WEBPACK` 配 url、运行时加载)
11
+ 从 Vue2/webpack 迁到 Vue3/Vite,并可选切到「清单 + 组件按需加载」形态的完整流程与踩坑清单。
12
+
13
+ **核心判断:这条链上的失败几乎全是静默的。** 构建绿、控制台干净、组件就是不出来。
14
+ 所以每一步都要有**判据**,别靠"看着没报错"。
15
+
16
+ ## 什么时候用
17
+
18
+ - 要把某个产品库迁到 Vue3 / 从 webpack 切到 Vite
19
+ - 要给某个产品库开按需打包(库超过 ~1MB 时才值得,见「按需值不值」)
20
+ - 排查远程库的诡异现象:组件不渲染 / 注册名不对 / 有组件没样式 /
21
+ 登录首屏拉了整个库 / `[loadComponent] 找不到 epx-xxx` / `[vuex] duplicate getter key`
22
+
23
+ **不适用**:平台自身(epx-frame)的构建问题;页面配置层面的组件用法(那看 `epx-frame-web`)。
24
+
25
+ ## 顺序(别调换)
26
+
27
+ ```
28
+ ① 依赖 + codemod pnpm epx migrate --apply → 出 MIGRATION-REPORT.md
29
+ ② .vue 转换 (同上,gogocode) → 逐个修构建报错
30
+ ③ 切 Vite 换 vite.config.mjs → require.context → import.meta.glob
31
+ ④ 跑起来 dev 壳验证 → 组件真渲染出来才算过
32
+ ⑤ 开按需(可选) npm run build:lib:lazy → 验清单 + 真机验渲染
33
+ ```
34
+
35
+ **②→③ 之间必须先让 `build:lib` 绿**。没绿就切按需,等于同时改两个变量,出问题分不清是谁的。
36
+
37
+ ## 每步的判据
38
+
39
+ | 步骤 | 判据(不是"没报错") |
40
+ |---|---|
41
+ | ① 依赖迁移 | `MIGRATION-REPORT.md` 的人工清单**逐条看过**——里面写着的坑会真的咬人 |
42
+ | ② .vue 转换 | `npx eslint src --ext .js,.jsx,.vue` 能跑(迁移会删掉 eslint 生态,先修工具链) |
43
+ | ③ 切 Vite | `npm run build:lib` exit 0 **且** `lib/` 里有 `index.js`/`index.css`/`index.deps.json` |
44
+ | ④ dev 壳 | 浏览器里 `#app.__vue_app__._context.components` 能数到你的组件个数;对自定义页、继承组件和多分支 render,核对实际根节点的样式命名空间及关键元素的 `getComputedStyle` |
45
+ | ⑤ 按需 | `index.manifest.json` 组件数 == `.vue` 文件数,**且路径兜底名 0 个** |
46
+
47
+ 第⑤条那个"路径兜底名"是最容易漏的:注册名抽不到就退成 `hrcomponentsSearch` 这种,
48
+ **dev 壳看不出来**(dev 读运行时 `comp.name`),只在按需形态下炸。
49
+
50
+ ## 快速排错
51
+
52
+ | 现象 | 多半是 |
53
+ |---|---|
54
+ | 组件不渲染,`[loadComponent] 找不到 epx-xxx` | 平台占位抢注 / 产品 install 没跑 |
55
+ | `__epxInstalledLibs__` 有记录但 app 里 0 个组件 | 记账写在 install 之前,产品幂等守卫空转 |
56
+ | 注册名变成 `xxxcomponentsYyy` | `extractName` 没抽到 `name`(按需形态特有) |
57
+ | 组件出来了但完全没样式 | 先查样式表是否加载、实际 DOM 能否匹配选择器;常见原因是 CSS 与 chunk 不同批(`data-v` hash 对不上),或根节点漏了 `.epx-xxx` 样式命名空间(F19) |
58
+ | 图片 404,路径是 `/assets/xxx.png` | 嵌套构建没继承 `assetsInlineLimit` |
59
+ | `Cannot read properties of undefined (reading 'i18n')` | recorder-core 执行序(模块图里没有那条边) |
60
+ | `[vuex] duplicate getter key` | 库被装了两次(本地 + 远程) |
61
+ | `[vite:vue] reading 'type'`(无行号,某 .vue) | gogocode 漏转元素上的 `slot="x"`(F1) |
62
+ | `组件注册名冲突:… copy.vue` | 垃圾副本文件撞名(F8,`find src -iname "*copy*"`) |
63
+ | `npm i` 显示成功但 vite/依赖没装上 | `node-sass`/`fibers` 原生编译挂、exit code 被管道吞(F7) |
64
+ | `[loadComponent] 找不到 epx-xxx`(本地新迁的库) | 源码版本落后线上部署、缺组件(`verify-components.mjs --online` 对齐) |
65
+
66
+ **详细根因、判据与修法见 [pitfalls.md](pitfalls.md)**:A 迁移工具 / B Vite 化 / C 按需 / D 联调 / E 主题 / **F gogocode 残留 & 换库(recruit 实证)**。
67
+
68
+ > **CLI 现已自动处理的机械项**(不用再手管,见 `tools/epx-migrate`):依赖表迁移、入口 `require.context`→`import.meta.glob`、
69
+ > 内联事件多语句补分号、`:prop.sync`→`v-model`、删 `node-sass`/`fibers`、`--to-vite` 生成 vite.config(含 resolve.extensions)、
70
+ > 第三方 Vue2-only 库 ⑥ 告警、`verify-components` 与线上组件清单对齐。**留给人工的就是 F 节那几类**:元素级 slot/slot-scope 结构转换、换库、编译残留定位。
71
+
72
+ > **本轮新增(postAppoint 实证)**:
73
+ > - `fix-jsx-vue2` 除自动改 4 类 JSX 属性(`domProps*`/`nativeOn*`/`scopedSlots=`),现在还**报告** `<script>` 区的 `slot="xxx"` 具名插槽(第 5 类,需人工改 `v-slots`——因要重组 JSX 结构故不自动改;甄别见 F14);
74
+ > - 新增 `detect-vue3-risks.mjs`(`node tools/epx-migrate/detect-vue3-risks.mjs <项目>`):发布前静态扫 scoped 整块失效等风险(R1 = extends+scoped+无 template+裸类选择器;已改 `:deep()` 的不误报;退出码 2 可 CI 拦),见 F15。
75
+
76
+ ## 迁移完怎么验:迁移包 vs 线上 三方对比(postAppoint / recruit / hr 实证)
77
+
78
+ 构建绿 ≠ 与线上等价。把「本地新迁的包」摆到线上同一平台、同一后端、同账号,逐页对比,是唯一可靠验收。
79
+
80
+ **三组环境**:① 线上(原样)② 本地平台 dist + 线上旧包(只换平台,作基线)③ 本地平台 dist + 本地新迁包(实验组)。①↔③ 看迁移包对不对,②↔③ 把「平台的锅 / 包的锅」分开。
81
+
82
+ **工装(只读、不改项目任何文件)**:
83
+ - 起个小服务(如 `:4183`)把 `/file-server/npm/test/web_<产品>/*` 路由到本地 `<产品>/lib/*`、其余 `/file-server/*` 302 回源线上(图片等照常,避免「反代没转发」式误报);
84
+ - 平台 `dist/assets/js/config.js` 末尾追加地址改写钩子,把 `web_<产品>` 的 script/link/fetch 指到该服务——平台 `util.js` 对含 `http` 的 id **原样返回**,所以改 `MINIO_IP` 无效,**必须在 script/link/fetch 层改写**;
85
+ - ⚠️ 线上同一产品可能有**两个包路径**(如 `web_post` 在用、`web_postAppoint` 废弃,二者组件同名):先 `performance` 看平台实际加载哪个,工装两个前缀都拦;
86
+ - ⚠️ 每次 `build:pre` 重建 dist 会**冲掉**钩子——重装并 `location.reload()`(同文档 hash 跳转不重载),再用 `performance` 确认 `web_<产品>` 的 chunk 确实来自本地服务,才开始采数据。
87
+
88
+ **后台标签页定时器冻结(采数据必踩,否则满屏假象)**:Chrome 后台/被遮挡标签页里页面自身的 `setTimeout`/`setInterval`/`requestAnimationFrame` 会被密集节流甚至冻结(几十秒零触发),表现为「列表空 / echarts 不画 / 数据延迟出现」——极易误判成缺陷。页内自动化脚本 sleep 一律走 **Web Worker 定时器**(`new Worker(URL.createObjectURL(new Blob(['self.onmessage=e=>setTimeout(()=>self.postMessage(1),e.data)'])))` + 页面侧回调);应用自己的定时器同样被冻,采数据前把该 tab 切前台、或注入 setTimeout/rAF 垫片,空页先复核再定论。截图 / resize 都**不解冻**。
89
+
90
+ **运行时实证判据(别靠肉眼 / 文本 / 静态 grep)**:
91
+ - 组件在不在:`#app.__vue_app__._context.components` 数量 / 组件实例的 `$slots` 键;
92
+ - 样式生不生效:`getComputedStyle(el)` 比对(容器高度是否塌陷 0、色值 / 尺寸是否对齐线上);
93
+ - 差异定性:`el-table` 固定列会复制一份 DOM(Vue2 Element UI 行数翻倍、Vue3 Element Plus 不翻)——比对「共 N 条 / 首行文本」而非 DOM 元素数,否则把「翻倍」误当差异;
94
+ - **静态 grep 到 Vue2 写法 ≠ 运行时一定失效**:失效与否取决于消费方 / 是否被平台全局样式兜住 / 组件运行时兼容(如 JSX `slot=` 在 el-popover reference 失效、在 el-dropdown 正常),必须运行时实证再判改不改。
95
+
96
+ ## 按需值不值(实测,别想当然)
97
+
98
+ 同一平台上只换 hr 的打包形态,Fast-3G 限速、每组 3 次取中位数:
99
+
100
+ | 形态 | 内容可见 | 关键路径 |
101
+ |---|---|---|
102
+ | 整体单体 | **11.1 s** | 1467 K / 49 请求 |
103
+ | 按需(含预取) | **13.5 s** | 1523 K / **107 请求** |
104
+ | 按需(关预取) | **11.8 s** | 1177 K / 49 请求 |
105
+
106
+ **按需没提速,甚至略慢**——省下的 290 KB 被多出来的往返吃掉了。
107
+ 真正的大头是平台重建(旧平台 20–59 s → 新平台 11 s)。
108
+
109
+ 所以按需的价值是**防止劣化**(库继续长大时首屏不跟着涨),不是当下提速。
110
+ 库还不到 1MB 就别开。
111
+
112
+ ## 部署前必查
113
+
114
+ 1. **平台支不支持按需 ABI**:抓线上平台 bundle `grep registerLazyLibrary`。
115
+ 没有就只能走降级②(壳自建 wrapper),没有集中预取、`deps.json` 也没人读——
116
+ **平台不先升级,产品这边开按需是白开**。
117
+ 2. **按需形态必须传整个 `lib/` 目录**,不是 `index.js` + `index.css` 两个文件。
118
+ `FM_SYSTEM_WEBPACK` 里仍只配那两条 url,其余按相对路径取。
119
+ 3. **`chunks/` 的 `import()` 需要文件服务给 CORS 头**,跨域没配会整库降级。
120
+ 4. `index.deps.json` **必须与 index.js 同目录同名一并上传**,漏传不报错但精确 ensure 会静默退化。
121
+
122
+ ## 常见错误
123
+
124
+ - **只看 dev 壳就以为好了** —— dev 走 `import.meta.glob` + 运行时 `comp.name`,
125
+ 按需形态那几个 bug 一个都不走。要验按需必须用构建产物。
126
+ - **两个构建并发写同一个 `lib/`** —— 实测会丢文件。`build:component` 第 1 趟
127
+ `emptyOutDir:true` 会清空整个目录,跑一半断了就只剩残缺产物。
128
+ - **拿"构建 exit 0"当验证** —— 这条链上大部分故障构建期完全看不出来。
129
+ - **把窗口/阈值调大当修复** —— 比如 `extractName` 的字符窗口,调大只是挪门槛。