一个和目标页面毫无关系、甚至不在同一文件里的
import(),能让你的页面报"未注册",而且怎么重启、清缓存、重新导入都修不好。本文记录一次真实的排查过程,以及背后小程序分包加载的"连坐"机制。
现象
uni-app(Vue3 + Vite)微信小程序项目,在分包 packageGoods 里新建了一个购物车页面 shopping-cart/shopping-cart。pages.json 已注册,路由常量已配置,从商品详情页点购物车图标跳转,却报:
erlang
Page "packageGoods/shopping-cart/shopping-cart" has not been registered yet.
更诡异的是:把购物车页设为编译模式的启动页,直接以它为首页启动,依然报"未注册"。
排查弯路(这些都是正常的,别浪费时间)
这个报错字面意思是"页面没注册",所以本能地往注册方向查。下面几条都查了,全部正常------如果你也在这一步,可以跳过:
1. app.json 里有没有注册? 有。用 Python 严格解析产物 app.json,shopping-cart/shopping-cart 确实在 subPackages 的 packageGoods 下:
python
import json
d = json.load(open('dist/build/mp-weixin/app.json', encoding='utf-8'))
for sp in d['subPackages']:
if sp['root'] == 'packageGoods':
print(sp['pages'])
# ['product-detail/detail', ..., 'shopping-cart/shopping-cart'] ✓
2. 页面文件齐不齐? 齐。shopping-cart.js / .json / .wxml / .wxss 四件套都在产物里。
3. 页面 js 有没有调用 wx.createPage? 有。和同分包能正常打开的 detail.js 对比,注册方式完全一致:
js
// shopping-cart.js 末尾
const MiniProgramPage = common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
4. require 的依赖存不存在? 都在。common/vendor.js、nav-bar.js、services/api/cart.js、utils/router.js、utils/toast.js、common/assets.js,以及 cart.js 间接依赖的 request.js,一个不缺。
5. 是不是产物没更新 / 开发者工具缓存? 不是。重跑 build:mp-weixin、删项目重新导入、清全部缓存,统统无效。
到这里,目标页面本身的所有检查都通过了,但就是"未注册"。这说明问题不在目标页面,而在别处。
转折:控制台里的另一个报错
在反复刷新、跳转的过程中,留意到控制台还刷出了另一条报错------来自同分包的另一个页面 order/list:
vbnet
页面【packageGoods/order/list】错误:
TypeError: "../../common/vendor.js".then is not a function
at list.js? [sm]:10
注意这个错误信息:"../../common/vendor.js".then is not a function。"../../common/vendor.js" 是一个字符串字面量 ,字符串没有 .then 方法,所以报错。
打开产物 order/list.js 第 10 行一看:
js
const dayjs = "../../common/vendor.js".then((n) => n.dayjs_min);
这一行在模块顶层 (不在任何函数里),意味着 list.js 一被加载就会立即执行这行,然后抛异常。
根因一:const dayjs = import('dayjs') 是动态 import,小程序不支持
回到源码 order/list.vue:
js
// ❌ 错误写法
const dayjs = import('dayjs');
import('dayjs') 是动态 import(Dynamic Import),它返回一个 Promise,按 ES 规范用于按需加载模块。但这有两层问题:
- 语义错误 :
import()返回的是 Promise,dayjs拿到的是 Promise 而不是 dayjs 函数。后面写dayjs(ms).format(...)把 Promise 当函数调用,即便在支持的环境里也会炸。 - 小程序不支持动态 import :微信小程序的模块系统是 CommonJS 风格的
require,没有原生的import()。uni-app 的 Vite 编译器在处理小程序产物时,试图把import('dayjs')转译成对打包后 vendor 的引用,但错误地生成了字符串字面量:
js
// 编译器错误产物
const dayjs = "../../common/vendor.js".then((n) => n.dayjs_min);
// ^^^^^^^^^^^^^^^^^^^^^^^^ 字符串,不是 require()
正确的编译结果应该是:
js
const dayjs = require("../../common/vendor.js").dayjs_min;
// 或
const dayjs = common_vendor.dayjs_min;
order/detail.vue 里也有同样的写法,order/detail.js 同样中招。
顺带一提:
detail.js(商品详情页)在同分包里却能正常打开,是因为它是用户进入分包时第一个加载的页面 ,加载它时还没有触发到order/list.js/order/detail.js的顶层错误。这极大地具有迷惑性------会让你以为"分包没问题,只是新页面有问题"。
根因二:分包加载的"连坐"机制
这是整个排查中最关键、也最反直觉的一点:
小程序加载一个分包时,如果分包内任意页面的顶层 js 抛异常,会导致整个分包的加载流程中断,分包内其他页面即使代码完全正确,也会报
Page "xxx" has not been registered yet。
换句话说,这是"连坐":
vbnet
用户访问 packageGoods 分包的 shopping-cart
→ 小程序加载 packageGoods 分包
→ 分包内 order/list.js 顶层执行 const dayjs = "...".then(...) 💥 抛异常
→ 分包加载中断
→ shopping-cart.js 没机会执行 wx.createPage()
→ 报 "shopping-cart has not been registered yet"
而 shopping-cart.js 本身的代码、注册、依赖全部是好的------这就是为什么前面五步排查全部通过、问题却依旧存在的原因。坑不在目标页面,在它的"邻居"那里。
解决方案
把动态 import 改成静态 import:
js
// order/list.vue & order/detail.vue
// ❌ 改前
const dayjs = import('dayjs');
// ✅ 改后
import dayjs from 'dayjs';
静态 import 在编译期就确定依赖,uni-app 会把 dayjs 正确打包进 vendor,产物里变成 common_vendor.dayjs_min 的引用,不再有字符串 .then。
重新 npm run build:mp-weixin,验证产物:
bash
# 修改前
$ grep dayjs dist/build/mp-weixin/packageGoods/order/list.js
const dayjs = "../../common/vendor.js".then((n) => n.dayjs_min); # 💥
# 修改后
$ grep dayjs dist/build/mp-weixin/packageGoods/order/list.js
... e.dayjs(t).format('YYYY-MM-DD HH:mm') ... # ✓ 从 vendor 正确引用
开发者工具重新编译,购物车页正常打开,订单页的 .then 报错也消失了。
排查方法论:遇到 "has not been registered yet" 该怎么办
把这个坑抽象成一套排查清单,按顺序走能少走弯路:
第一步:确认目标页面本身没问题
-
app.json(或pages.json)里注册了该页面,且在正确的subPackages下 - 产物的页面四件套(js/json/wxml/wxss)齐全
- 页面 js 末尾有
wx.createPage(...)(uni-app vue3)或Page(...)调用 - 页面 js 顶层所有
require()的依赖文件都存在
如果以上全过却仍报"未注册",立即停止在目标页面上死磕,进入第二步。
第二步:查同分包其他页面的顶层错误
- 打开开发者工具控制台,留意所有报错,尤其是同分包其他页面的
- 重点找
TypeError、xxx is not a function、Cannot read property等在模块顶层抛出的错误 - 用 grep 扫产物里同分包所有 js 的顶层异常模式:
bash
# 扫动态 import 被错误编译成字符串.then 的模式
grep -rn '"\.\./\.\./common/vendor\.js"\.then' dist/build/mp-weixin/<分包root>/
# 扫其他可能的顶层 require 失败
grep -rn 'require(' dist/build/mp-weixin/<分包root>/ | grep -v '"use strict"'
第三步:修掉邻居页面的错误,整包复活
几个容易踩的动态 import 误区
这次根因是误用了 import()。日常开发中,动态 import 的误用有几类常见来源:
- 把
import()当同步 import 用 :const x = import('mod')看起来像"导入并赋值",但import()返回 Promise,x是 Promise 不是模块。正确写法是import x from 'mod'(静态)或import('mod').then(m => ...)(动态,且小程序不支持)。 - 从别的项目复制代码时带过来的写法:有些 H5/Node 项目用动态 import 做代码分割,复制到小程序项目里就埋了雷。
- IDE 自动补全/重构误改 :某些重构工具会把
import x from 'mod'误改成const x = import('mod')。
小程序项目的铁律:只用静态 import,不要用 import()。 需要按需加载的场景,用条件 require 或组件层面的延迟渲染代替。
小结
Page "xxx" has not been registered yet不一定是目标页面的问题,可能是同分包其他页面顶层 js 报错导致分包加载中断。const x = import('mod')是错误的动态 import 用法,小程序不支持import(),uni-app 编译器会把它错误编译成字符串.then,运行即崩。- 排查"未注册"时,如果目标页面本身全部检查通过,立刻把视线转到同分包的其他页面,看控制台有没有其他 TypeError。
- 一个分包是一个"命运共同体"------一页出错,全包遭殃。写分包页面时,顶层代码要格外干净,避免任何可能在加载时抛异常的写法。
踩坑日期:2026-07-13 | 技术栈:uni-app 3 (Vue3 + Vite) + 微信小程序