根因:微信开发者工具(>=1.05.2112301)的 ignoreDevUnusedFiles 会按 WXML/JS 依赖分析过滤 "无依赖"文件。tabBar 图标仅在 app.json 中声明,不属于任何页面 WXML/JS 依赖,被误判为无用 文件从开发包剥离,导致 5 个图标全部加载失败(渲染为破图占位)。 修复: - 在 project.config.json 与 miniapp/project.config.json 的 setting 中显式关闭 ignoreDevUnusedFiles / ignoreUploadUnusedFiles(false),阻止图标被过滤。 - 同步关闭本地 gitignore 的 project.private.config.json(DevTools 运行时实际读取项)。 - 预览通道 scripts/preview-miniapp.mjs:补全 /assets 绝对路径→相对路径重写,并手动复刻 原生 tabBar(原生 tabBar 不在任何页面 WXML 中,静态预览需自行复刻)。 - 回归守卫 ui-tests/tabbar-icons.mjs 扩充为 5 段,覆盖根因(配置开关)+ 预览复刻。 - miniapp/assets/tabbar/DESIGN.md 增加根因警示,防止后续被误开开关。 验证:UI 契约 23/23 全绿(tabbar-icons.mjs 位于 [19/23]);CDP 渲染实测 127 张图 0 破图, 5 个 tabBar 图标 naturalW=81 全部加载,选中色 rgb(233,107,107)、未选 rgb(153,153,153), 与 app.json selectedColor/color 一致。
118 lines
5.6 KiB
JavaScript
118 lines
5.6 KiB
JavaScript
import assert from 'node:assert/strict'
|
||
import { readFileSync, existsSync } from 'node:fs'
|
||
import { join } from 'node:path'
|
||
|
||
const repoRoot = process.cwd()
|
||
const root = join(repoRoot, 'miniapp', 'assets', 'tabbar')
|
||
const icons = {
|
||
home: '爱心小屋',
|
||
discover: '双人缘分',
|
||
message: '爱心对话',
|
||
activity: '公益日历',
|
||
mine: '个人守护',
|
||
}
|
||
|
||
// ── 1. SVG 源文件:主题化标题 + 两态配色 ──────────────────────────────
|
||
for (const [name, title] of Object.entries(icons)) {
|
||
const normal = readFileSync(join(root, 'svg', `${name}.svg`), 'utf8')
|
||
const active = readFileSync(join(root, 'svg', `${name}-active.svg`), 'utf8')
|
||
|
||
assert.match(normal, new RegExp(`<title>${title}</title>`), `${name} 缺少主题化标题`)
|
||
assert.match(normal, /#999999/i, `${name} 默认态颜色错误`)
|
||
assert.match(active, /#E96B6B/i, `${name} 选中态颜色错误`)
|
||
|
||
// ── 2. PNG 规格:正方形、1x/2x/3x、透明 RGBA ────────────────────────
|
||
for (const suffix of ['', '_2x', '_3x']) {
|
||
const expectedSize = suffix === '' ? 81 : suffix === '_2x' ? 162 : 243
|
||
for (const state of ['', '-active']) {
|
||
const png = readFileSync(join(root, `${name}${state}${suffix}.png`))
|
||
assert.equal(png.readUInt32BE(16), expectedSize)
|
||
assert.equal(png.readUInt32BE(20), expectedSize)
|
||
assert.equal(png[25], 6, `${name}${state}${suffix} 必须为透明 RGBA PNG`)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── 3. app.json ↔ 磁盘一致性 ─────────────────────────────────────────
|
||
// 历史缺陷:tabBar 图标只被 app.json 引用(不经 WXML/JS),
|
||
// 一旦配置漂移,不会有任何 WXML 侧症状,只会「底部导航整排变破图」。
|
||
const appJsonPath = join(repoRoot, 'miniapp', 'app.json')
|
||
const appJson = JSON.parse(readFileSync(appJsonPath, 'utf8'))
|
||
const tabList = appJson.tabBar?.list ?? []
|
||
assert.equal(tabList.length, 5, 'tabBar.list 必须为 5 项(微信支持 2~5)')
|
||
|
||
for (const item of tabList) {
|
||
assert.ok(appJson.pages.includes(item.pagePath), `tabBar pagePath 未在 pages 中注册: ${item.pagePath}`)
|
||
for (const key of ['iconPath', 'selectedIconPath']) {
|
||
const rel = item[key]
|
||
assert.ok(rel, `tabBar ${item.pagePath} 缺少 ${key}`)
|
||
assert.ok(!rel.startsWith('/'), `tabBar ${key} 不应以 / 开头(微信按小程序根解析): ${rel}`)
|
||
assert.ok(
|
||
existsSync(join(repoRoot, 'miniapp', rel)),
|
||
`tabBar ${key} 指向不存在的文件: ${rel}`,
|
||
)
|
||
}
|
||
}
|
||
|
||
// ── 4. 「无依赖文件过滤」必须关闭(tabBar 图标整排变破图的根因)────────
|
||
// 微信开发者工具自 1.05.2112301 起默认开启该能力:它按 WXML/JS 依赖分析,
|
||
// app.json 的 tabBar 图标不被视为依赖 → 被从开发包中剔除 → 图标全部加载失败。
|
||
// 官方缓解方式即 ignoreDevUnusedFiles / ignoreUploadUnusedFiles 置 false。
|
||
const CONFIGS = [
|
||
'project.config.json',
|
||
'miniapp/project.config.json',
|
||
'project.private.config.json',
|
||
'miniapp/project.private.config.json',
|
||
]
|
||
const requiredOff = ['project.config.json', 'miniapp/project.config.json']
|
||
|
||
for (const rel of CONFIGS) {
|
||
const p = join(repoRoot, rel)
|
||
if (!existsSync(p)) continue // 私有配置被 gitignore,缺失属正常
|
||
const setting = JSON.parse(readFileSync(p, 'utf8')).setting ?? {}
|
||
assert.notEqual(
|
||
setting.ignoreDevUnusedFiles, true,
|
||
`${rel} 开启了无依赖文件过滤(ignoreDevUnusedFiles),会剔除 app.json 独占引用的 tabBar 图标,导致底部导航整排破图`,
|
||
)
|
||
assert.notEqual(
|
||
setting.ignoreUploadUnusedFiles, true,
|
||
`${rel} 开启了上传期无依赖文件过滤(ignoreUploadUnusedFiles),体验版/正式版会丢失 tabBar 图标`,
|
||
)
|
||
}
|
||
|
||
for (const rel of requiredOff) {
|
||
const p = join(repoRoot, rel)
|
||
assert.ok(existsSync(p), `缺少共享配置 ${rel}`)
|
||
const setting = JSON.parse(readFileSync(p, 'utf8')).setting ?? {}
|
||
assert.equal(
|
||
setting.ignoreDevUnusedFiles, false,
|
||
`${rel} 必须显式关闭 ignoreDevUnusedFiles(新克隆默认值为 true 会复现破图)`,
|
||
)
|
||
assert.equal(
|
||
setting.ignoreUploadUnusedFiles, false,
|
||
`${rel} 必须显式关闭 ignoreUploadUnusedFiles`,
|
||
)
|
||
}
|
||
|
||
// ── 5. 静态预览必须改写资源路径 ──────────────────────────────────────
|
||
// WXML/WXSS 写的是小程序根绝对路径 /assets/...,浏览器 file:// 打开会解析到
|
||
// 文件系统根 → 全部 404(表现为破图)。预览产出必须改写为相对路径,
|
||
// 并复刻 app.json 原生 tabBar(它不在任何 WXML 里)。
|
||
const previewSrc = readFileSync(join(repoRoot, 'scripts', 'preview-miniapp.mjs'), 'utf8')
|
||
assert.match(previewSrc, /const rewriteAssetUrls\s*=/, '预览脚本缺少资源路径改写器')
|
||
assert.match(
|
||
previewSrc, /writeFileSync\(outFile,\s*rewriteAssetUrls\(html\)/,
|
||
'预览脚本写盘时未应用资源路径改写 → file:// 下图片会全部 404',
|
||
)
|
||
assert.match(previewSrc, /function renderTabbar\(/, '预览脚本缺少原生 tabBar 复刻')
|
||
|
||
const previewHtml = join(repoRoot, 'artifacts', 'ui-redesign', 'miniapp', 'preview.html')
|
||
if (existsSync(previewHtml)) {
|
||
const html = readFileSync(previewHtml, 'utf8')
|
||
const absoluteSrc = (html.match(/src=["']\/assets\//g) || []).length
|
||
assert.equal(absoluteSrc, 0, `preview.html 仍有 ${absoluteSrc} 处根绝对资源路径,浏览器打开会破图`)
|
||
assert.match(html, /class="mini-tabbar"/, 'preview.html 缺少复刻的底部 tabBar')
|
||
}
|
||
|
||
console.log('Tabbar icon contracts passed')
|