18 KiB
Kbone 常见问题排查指南
从 Next 项目迁移到小程序
先读:本技能 SKILL.md 中的「Next 项目 → 小程序 最优转化方式」章节。
要点:
- 以 newpp 为 Kbone 构建源,miniprogram 为壳,构建后合并产物。
- 必须做 适配层:
env、request、storage、router;功能开关与 Next 一致,用同一 API(如/api/db/config的features.matchEnabled)。 - UI:在 newpp 内复制并适配 Next 的页面/组件,或通过 webpack alias 引用根目录共享组件(无 Next 专属 API)。
- 配置:
miniprogram.config.js每页单独 router、不配原生 tabBar;webpack.mp.config.js中isOptimize = process.env.NODE_ENV === 'production',中小型项目建议splitChunks: false。 - 底部菜单:Web 端多为自定义底部菜单组件(主页面统一引入、有显隐逻辑),不要转为小程序原生 tabBar;保留「主页面引入统一菜单组件」,显隐与跳转与 Web 一致,跳转用
wx.reLaunch。 - 样式:Grid 改 Flex,补
boxSizing/lineHeight;不支持特性用兼容写法或替代。
遇到具体报错时,在下方「配置问题」「编译问题」「运行时问题」「样式问题」中按类型排查。
配置问题
1. redirect 用了不存在的页面名
现象:配置了 redirect.notFound: 'home'、redirect.accessDenied: 'home',但 router 里没有名为 home 的页面。
后果:404 或无权时,Kbone 可能无法正确跳转到首页。
解决:redirect 必须使用 router 里存在的 key。首页通常是 index,不要写 home。
// ❌ 错误
redirect: { notFound: 'home', accessDenied: 'home' }
// ✅ 正确
redirect: { notFound: 'index', accessDenied: 'index' }
编译问题
0. Node 17+ OpenSSL 错误(ERR_OSSL_EVP_UNSUPPORTED)
错误信息:
Error: error:0308010C:digital envelope routines::unsupported
code: 'ERR_OSSL_EVP_UNSUPPORTED'
原因:Node.js 17+ 使用 OpenSSL 3.0,旧版 webpack(如 4.x)依赖的 MD4 等算法被移出默认提供,导致构建时报错。
解决方案:
方案 1:在 newpp 的 package.json 脚本中加 NODE_OPTIONS(推荐)
"scripts": {
"web": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=development webpack-dev-server ...",
"mp": "rimraf dist/mp/common && cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=development webpack ...",
"build:mp": "rimraf dist/mp/common && cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack ..."
}
方案 2:当前终端临时设置后再运行
- Windows PowerShell:
$env:NODE_OPTIONS="--openssl-legacy-provider"; cd newpp; npm run web - Windows CMD:
set NODE_OPTIONS=--openssl-legacy-provider && cd newpp && npm run web - Linux/macOS:
NODE_OPTIONS=--openssl-legacy-provider npm run web
方案 3:使用 Node.js 16.x(LTS)运行 newpp 构建,避免 OpenSSL 3.0。
1. chunk 文件缺失错误
错误信息:
Error: ENOENT: no such file or directory, open 'E:/path/to/default~chapters~index~my.js'
原因:
- Webpack 代码分割(splitChunks)生成了动态命名的 chunk 文件
- 小程序环境下,动态 chunk 路径可能不稳定
解决方案:
方案 1:完全禁用代码分割(推荐中小型项目)
// webpack.mp.config.js
optimization: {
runtimeChunk: false,
splitChunks: false, // 完全禁用
}
方案 2:固定 chunk 命名(大型项目)
optimization: {
runtimeChunk: false,
splitChunks: {
chunks: 'all',
name: true, // 使用固定命名
cacheGroups: {
vendors: {
name: 'vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10,
},
default: {
name: 'common',
minChunks: 2,
priority: 5,
},
},
},
}
选择建议:
- 项目 <20 页面:方案 1(稳定性优先)
- 项目 >50 页面:方案 2(体积优化优先)
2. Babel 编译错误
错误信息:
SyntaxError: Unexpected token ...
或具体到某列:Unexpected token (45:64)(多为可选链位置)
原因:
- Babel 配置不正确
- 使用了 Babel 6(stage-3)不支持的语法:可选链
?.、空值合并??(属 ES2020) - 其他小程序不支持的 ES6+ 语法
解决方案:
若为可选链 / 空值合并(最常见):
// ❌ Babel 6 会报错
res.content?.length
x ?? 'default'
// ✅ 兼容写法
(res.content && res.content.length) || 0
x != null ? x : 'default'
Babel 配置(.babelrc 或 babel.config.js):
{
"presets": [
["env", {
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"react",
"stage-3"
],
"plugins": [
"transform-runtime"
]
}
3. 依赖包报错
错误信息:
Module not found: Can't resolve 'xxx'
排查步骤:
- 检查依赖是否安装
npm list [package-name]
- 重新安装依赖
rm -rf node_modules package-lock.json
npm install
- 检查 webpack resolve 配置
resolve: {
extensions: ['*', '.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
}
运行时问题
0. TypeError: eval is not a function
错误信息:
TypeError: eval is not a function
at Object../src/index.jsx (index.js?...)
原因:npm run mp 使用 development 模式时,webpack 默认 devtool: 'eval',会注入使用 eval() 的代码;小程序环境不支持 eval,因此报错。
解决方案:在 webpack.mp.config.js 中显式关闭 devtool:
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
devtool: false, // 小程序环境不支持 eval,必须关闭
entry: { ... },
// ...
}
重新执行 npm run mp 或 npm run build:mp 后,再在微信开发者工具中预览即可。
1. URLSearchParams 未定义
错误信息:
ReferenceError: URLSearchParams is not defined
原因:小程序环境不支持 URLSearchParams
解决方案:
// 自定义实现
function buildQueryString(params) {
const parts = []
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
}
}
return parts.join('&')
}
// 使用示例
const queryString = buildQueryString({
page: 1,
limit: 10,
keyword: '搜索'
})
// 结果: "page=1&limit=10&keyword=%E6%90%9C%E7%B4%A2"
2. localStorage 未定义
错误信息:
ReferenceError: localStorage is not defined
原因:小程序环境使用 wx.storage 而不是 localStorage
解决方案:
// adapters/storage.js
function isMiniProgram() {
return typeof wx !== 'undefined' && wx.getSystemInfoSync
}
export const storage = {
get(key) {
if (isMiniProgram()) {
return wx.getStorageSync(key)
} else {
const value = localStorage.getItem(key)
try {
return JSON.parse(value)
} catch {
return value
}
}
},
set(key, value) {
if (isMiniProgram()) {
wx.setStorageSync(key, value)
} else {
localStorage.setItem(key, JSON.stringify(value))
}
},
remove(key) {
if (isMiniProgram()) {
wx.removeStorageSync(key)
} else {
localStorage.removeItem(key)
}
},
clear() {
if (isMiniProgram()) {
wx.clearStorageSync()
} else {
localStorage.clear()
}
},
}
3. window 对象未定义
错误信息:
ReferenceError: window is not defined
原因:小程序环境没有 window 对象
解决方案:
// 环境判断
function isMiniProgram() {
return typeof wx !== 'undefined' && wx.getSystemInfoSync
}
// 使用示例
if (!isMiniProgram()) {
window.addEventListener('resize', handleResize)
}
// 或使用可选链
window?.addEventListener?.('resize', handleResize)
4. document 对象未定义
错误信息:
ReferenceError: document is not defined
原因:小程序环境没有 document 对象
解决方案:
// ❌ 不要直接使用 document
const title = document.title
// ✅ 使用 React 特性
import { useEffect } from 'react'
function MyComponent() {
useEffect(() => {
if (!isMiniProgram()) {
document.title = 'My Page'
} else {
// 小程序使用 wx.setNavigationBarTitle
wx.setNavigationBarTitle({
title: 'My Page'
})
}
}, [])
}
样式问题
1. CSS Grid 不生效
问题描述:使用 CSS Grid 布局在小程序中显示错乱
原因:小程序对 CSS Grid 支持不完整
解决方案:
// ❌ 避免使用 Grid
const badStyles = {
container: {
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '10px',
}
}
// ✅ 使用 Flexbox
const goodStyles = {
container: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
boxSizing: 'border-box',
},
item: {
flex: '0 0 25%', // 相当于 4 列
boxSizing: 'border-box',
padding: '5px',
}
}
2. 盒模型计算错误
问题描述:元素尺寸计算不正确,padding/border 撑大元素
原因:未设置 box-sizing: border-box
解决方案:
// ✅ 所有容器都添加 boxSizing
const styles = {
container: {
width: '100%',
padding: '10px',
boxSizing: 'border-box', // 重要!
},
item: {
width: '50%',
border: '1px solid #ccc',
boxSizing: 'border-box', // 重要!
}
}
3. 文字垂直居中问题
问题描述:文字没有垂直居中
原因:未设置 line-height
解决方案:
const styles = {
button: {
height: '44px',
lineHeight: '44px', // 与 height 相同
textAlign: 'center',
},
text: {
lineHeight: '1.5', // 或使用相对值
}
}
4. 标题/头部被胶囊或电池栏遮挡
问题描述:自定义导航栏时,标题、返回按钮被右上角胶囊或顶部状态栏遮挡。
原因:未预留顶部安全区高度和右侧胶囊留白。
解决方案:
- 顶部安全区:在
app.js的onLaunch中计算并写入globalData:statusBarHeight = wx.getSystemInfoSync().statusBarHeight || 44navBarHeight:用wx.getMenuButtonBoundingClientRect()计算menuButton.bottom + menuButton.top - statusBarHeight,无菜单时用statusBarHeight + 44capsulePaddingRight = systemInfo.windowWidth - menuButton.left + 10
- 页面占位:顶部占位条高度设为
{{ navBarHeight }}px,内容区padding-top: {{ statusBarHeight }}px;页面onLoad/onShow从getApp().globalData取上述值写入data。 - 标题右侧留白:头部容器加类
.safe-header-right { padding-right: 200rpx; box-sizing: border-box; },或内联padding-right: {{ capsulePaddingRight }}px,避免标题与胶囊重叠。
详见本目录 reference.md 第 7 节「安全区与标题/胶囊避让」。
5. Flexbox 间距问题
问题描述:Flexbox 子元素间距不均匀
解决方案:
// 方案 1:使用 gap(注意兼容性)
const styles = {
container: {
display: 'flex',
gap: '10px', // 可能不支持
}
}
// 方案 2:使用 margin(推荐)
const styles = {
container: {
display: 'flex',
margin: '-5px', // 负边距抵消子元素边距
},
item: {
margin: '5px',
}
}
// 方案 3:使用伪元素
// CSS: .item:not(:last-child) { margin-right: 10px; }
路由问题
1. 页面跳转无效
问题描述:点击导航无反应
排查步骤:
- 检查 router 配置
// miniprogram.config.js
router: {
index: ['/', '/index.html'],
my: ['/my', '/my.html'], // 确保配置了目标页面
}
- 检查路由适配器
// adapters/router.js
export function navigateTo(path) {
if (isMiniProgram()) {
wx.navigateTo({
url: toMpPath(path),
fail: (err) => console.error('导航失败:', err)
})
} else {
window.location.href = path
}
}
- 检查页面是否存在
# 确保入口文件存在
ls src/my.jsx
2. switchTab 报错
错误信息:
navigateTo:fail can not navigateTo a tabbar page
原因:使用 wx.navigateTo 跳转 tabBar 页面
解决方案:
// ❌ 错误
wx.navigateTo({ url: '/pages/index/index' })
// ✅ 正确
wx.switchTab({ url: '/pages/index/index' })
// 或使用 wx.reLaunch(不依赖 tabBar 配置)
wx.reLaunch({ url: '/pages/index/index' })
3. 动态路由参数丢失
问题描述:/read/:id 中的 id 获取不到
解决方案:
// 小程序环境
if (isMiniProgram()) {
// 从页面 options 获取
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const { id } = currentPage.options
}
// Web 环境
else {
// 从 URL 解析
const pathParts = window.location.pathname.split('/')
const id = pathParts[pathParts.length - 1]
// 或使用 React Router
import { useParams } from 'react-router-dom'
const { id } = useParams()
}
网络问题
1. API 请求失败
错误信息:
request:fail url not in domain list
原因:小程序要求配置服务器域名白名单
解决方案:
-
小程序管理后台配置
- 登录小程序管理后台
- 开发 > 开发设置 > 服务器域名
- 添加你的 API 域名
-
开发阶段临时方案
- 微信开发者工具 > 右上角详情
- 勾选"不校验合法域名"
-
使用代理
// miniprogram.config.js
module.exports = {
origin: 'https://soul.quwanzhi.com', // 本项目线上域名,见开发文档 8、部署
}
2. 跨域问题
错误信息:
Access to fetch at 'xxx' has been blocked by CORS policy
原因:Web 端开发时遇到跨域限制
解决方案:
方案 1:配置 webpack devServer 代理
// webpack.dev.config.js
devServer: {
proxy: {
'/api': {
target: 'https://your-api.com',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
}
方案 2:后端配置 CORS
// Express 示例
app.use(cors({
origin: ['http://localhost:8080', 'https://soul.quwanzhi.com'],
credentials: true,
}))
性能问题
1. 首屏加载慢
排查步骤:
- 检查包体积
# 查看构建产物大小
ls -lh dist/mp/common/
- 启用代码压缩
// webpack.mp.config.js
const isOptimize = process.env.NODE_ENV === 'production'
optimization: {
minimizer: isOptimize ? [
new TerserPlugin(),
new OptimizeCSSAssetsPlugin(),
] : [],
}
- 使用代码分割(大型项目)
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: 10,
},
},
},
}
2. 页面卡顿
常见原因:
- 渲染列表过长
// ✅ 使用虚拟列表
import VirtualList from 'react-virtual-list'
<VirtualList
items={items}
itemHeight={50}
renderItem={(item) => <Item {...item} />}
/>
- 频繁重渲染
// ✅ 使用 React.memo
const MyComponent = React.memo(({ data }) => {
return <div>{data}</div>
})
// ✅ 使用 useMemo
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data)
}, [data])
调试技巧
1. 查看小程序日志
微信开发者工具:
- 控制台 > Console:查看 console.log
- 控制台 > Network:查看网络请求
- 控制台 > Storage:查看本地存储
2. 条件断点
// 只在小程序环境下打印
if (isMiniProgram()) {
console.log('小程序环境:', data)
}
// 只在 Web 环境下打印
if (!isMiniProgram()) {
console.log('Web 环境:', data)
}
3. 真机调试
-
开启调试模式
- 微信开发者工具 > 预览
- 扫码在真机上打开
- 点击右上角 > 打开调试
-
查看 vConsole
- 小程序右下角会出现绿色悬浮按钮
- 点击查看日志、网络、存储等信息
检查清单
遇到问题时,按此清单逐项排查:
编译检查
- 所有依赖已安装(
npm install) - entry 配置正确
- output 配置未修改必需字段
- splitChunks 配置合理
兼容性检查
- 没有使用 URLSearchParams
- 没有使用 localStorage(改用适配器)
- 没有直接使用 window/document
- CSS 没有使用 Grid(改用 Flexbox)
配置检查
- router 每个页面单独配置
- pages 配置了所有页面
- 适配器正确处理平台差异
运行检查
- Web 端能正常运行
- 小程序能正常编译
- 微信开发者工具无错误
- 真机预览正常
获取帮助
官方资源
社区资源
调试工具
- 微信开发者工具:下载地址
- React DevTools:浏览器扩展
- vConsole:真机调试工具(小程序内置)