原生小程序重构成 uni-app未开发完阶段能不能两个项目打包成一个小程序很多团队在做小程序重构时都会遇到一个比较现实的问题老项目是原生微信小程序业务已经在线上跑着新项目准备改成 uni-app但是不可能一次性全部重写完。这个时候就会产生一个需求能不能在重构没有完成之前把原生小程序和 uni-app 项目打包成一个小程序做到一部分页面用原生一部分页面用 uni-app答案是可以做过渡方案但不能简单理解成“两个项目直接打包成一个”。因为微信小程序最终上传的是一个完整的小程序工程页面路由、app.json、分包、组件路径、资源目录都需要统一管理。uni-app 编译后本质上也会生成微信小程序代码所以真正可行的思路是把其中一个项目作为主工程另一个项目的页面或编译产物合并进去。一、为什么不能直接把两个项目打包成一个原生微信小程序和 uni-app 的开发方式不同。原生小程序通常是app.js app.json app.wxss pages/ components/ utils/uni-app 项目通常是pages.json App.vue main.js pages/ components/ static/ manifest.jsonuni-app 在打包成微信小程序时会把 Vue 写法编译成微信小程序可以识别的结构生成类似下面的目录dist/build/mp-weixin/ app.js app.json app.wxss pages/ components/ common/ static/也就是说uni-app 项目不能直接和原生小程序源码放在一起打包。微信开发者工具只认最终的小程序工程而不是同时识别两个不同框架的项目。所以不能这样做native-miniapp/ uniapp-project/然后直接一起上传。正确思路应该是最终只保留一个可运行的小程序工程把另一个项目的页面、组件、资源、路由配置合并进去。二、方案一以原生小程序为主工程逐步接入 uni-app 编译后的页面这是比较常见的过渡方案。适合老项目已经上线原生小程序结构稳定短期内不想大规模调整入口、登录、支付、分享、统计等基础能力的情况。基本思路是继续保留原生小程序作为主工程uni-app 单独开发新页面每次将 uni-app 编译成微信小程序代码把编译后的页面、组件和静态资源复制到原生小程序工程中在原生小程序的 app.json 中手动注册 uni-app 编译出来的页面从原生页面通过 wx.navigateTo 跳转到 uni-app 页面。示例结构native-miniapp/ ├── app.js ├── app.json ├── app.wxss ├── pages/ │ ├── home/ │ ├── user/ │ └── uni-pages/ │ ├── course/ │ └── detail/ ├── components/ ├── common/ └── static/uni-app 编译后可能生成dist/build/mp-weixin/pages/course/index dist/build/mp-weixin/pages/detail/index dist/build/mp-weixin/common dist/build/mp-weixin/static然后把这些目录合并到原生小程序工程中。原生小程序 app.json 中添加页面{ pages: [ pages/home/index, pages/user/index, pages/uni-pages/course/index, pages/uni-pages/detail/index ] }跳转方式wx.navigateTo({ url: /pages/uni-pages/course/index })这个方案的优点是改动小风险低原生项目还能继续保持线上稳定。缺点是每次 uni-app 编译后都要做目录合并并且要注意路径、组件、样式、公共方法和资源引用问题。三、方案二以 uni-app 为主工程把原生页面迁移进 uni-app 编译目录另一种方案是反过来以 uni-app 作为主工程把原生小程序页面逐步迁移进去。这种方式适合项目准备彻底切到 uni-app并且新架构已经基本确定。后续所有页面都计划用 uni-app 开发只是短期内还有部分老页面没重写完。基本思路是uni-app 作为主工程新页面全部用 Vue 写老的原生小程序页面暂时保留把原生页面目录放到 uni-app 编译后的微信小程序目录中修改最终生成的 app.json把原生页面也注册进去。但这个方案有一个问题uni-app 每次重新编译都会覆盖 dist 目录。如果你手动把原生页面放到 dist/build/mp-weixin 里面下次编译可能就没了。所以如果采用这种方案通常需要写自动化脚本在 uni-app 编译完成后自动把原生页面复制到 dist 目录并自动合并 app.json。示例流程1. 执行 uni-app 编译 2. 生成 dist/build/mp-weixin 3. 脚本复制 native-pages 到 dist/build/mp-weixin/pages/native 4. 脚本合并 app.json 页面配置 5. 使用微信开发者工具打开 dist/build/mp-weixin 上传这个方案更适合重构后期使用。优点是最终方向统一后续迁移成本低。缺点是对自动化脚本要求更高如果处理不好每次编译后都容易丢配置。四、方案三使用分包做隔离降低混合项目的冲突无论是以原生为主还是以 uni-app 为主都建议使用分包隔离。比如老项目原生页面放主包新项目 uni-app 页面放分包{ pages: [ pages/index/index, pages/user/index ], subpackages: [ { root: uniPackage, pages: [ course/index, detail/index, mine/index ] } ] }对应目录miniapp/ ├── pages/ │ ├── index/ │ └── user/ ├── uniPackage/ │ ├── course/ │ ├── detail/ │ └── mine/分包的好处是原生页面和 uni-app 页面目录更清晰资源引用更容易隔离减少样式污染方便后续逐步替换主包体积压力更小。但要注意uni-app 编译出来的文件依赖 common、static、components 等公共目录不能只复制页面目录否则页面可能运行不起来。五、最推荐的过渡方案如果老项目已经上线推荐使用原生小程序作为主工程 uni-app 编译页面作为分包接入也就是老项目继续正常维护新页面用 uni-app 开发uni-app 编译成微信小程序代码将编译产物中的页面、组件、common、static 等依赖复制到原生项目在原生 app.json 中注册为分包页面使用原生页面跳转到 uni-app 页面等 uni-app 页面逐步替换完成后再考虑整体切换到 uni-app 主工程。这样做的核心优势是线上风险可控不需要一次性推翻老项目也不会因为重构没完成导致整个小程序无法发布。六、需要重点注意的问题1. app.json 只能有一个最终上传的小程序只能有一个 app.json。原生项目有 app.jsonuni-app 编译后也会生成 app.json不能两个同时存在。所以需要合并页面配置、window 配置、tabBar 配置、分包配置、权限配置等内容。尤其要注意pages subpackages tabBar permission usingComponents requiredBackgroundModes navigateToMiniProgramAppIdList如果直接覆盖 app.json很容易导致老页面丢失或新页面无法访问。2. app.js 入口逻辑要谨慎合并原生小程序有自己的 app.jsuni-app 编译后也会生成 app.js。两个入口不能直接共存。如果原生项目作为主工程一般不要使用 uni-app 编译出来的 app.js 覆盖原生 app.js。否则原来的登录初始化、全局状态、埋点、版本检测、更新提示等逻辑可能会被破坏。更稳妥的做法是保留原生 app.js把 uni-app 页面依赖的公共逻辑尽量放在页面内部或公共模块中。3. 公共样式可能冲突uni-app 编译后会生成 app.wxss 和页面 wxss。如果把 uni-app 的全局样式直接合并到原生 app.wxss可能会影响老页面。建议uni-app 页面样式尽量局部化不要写过多全局选择器避免直接修改 button、image、view 等基础标签样式分包页面单独维护样式。4. 资源路径要重新检查uni-app 中常见资源路径/static/logo.png /static/logo.png ../../static/logo.png编译后放进原生项目时路径层级可能发生变化。尤其是图片、字体、音频、视频资源合并后要逐个测试。5. 组件依赖不能漏uni-app 编译后的页面可能依赖components/ common/ wxcomponents/ uni_modules/ static/如果只复制 pages 目录页面很可能报错。常见错误包括module not found component is not found template not found Cannot read property xxx of undefined所以复制时不能只看页面文件要把编译产物中的依赖一起带上。6. 登录态和全局数据要统一原生项目和 uni-app 页面之间需要共享登录态。常见做法是统一使用微信小程序本地缓存wx.setStorageSync(token, token) wx.getStorageSync(token)不要在两个项目里各自维护一套 token、userInfo、role、tenantId否则会出现原生页面已登录uni-app 页面未登录 uni-app 页面刷新了用户信息原生页面拿不到 退出登录后另一个项目页面仍然保持登录状态。建议统一封装token key 用户信息 key 接口 baseURL 请求 header 登录失效处理 退出登录逻辑7. 接口请求方式要统一原生小程序可能使用 wx.requestuni-app 可能使用 uni.request。两者都能正常请求但错误处理、拦截器、header、token 续期逻辑可能不同。建议统一接口规范Authorization 字段一致 登录失效状态码一致 错误提示格式一致 loading 处理一致 接口域名一致 环境变量一致。8. tabBar 页面不建议混合得太复杂如果原生项目已经有 tabBar而 uni-app 新页面也设计了 tabBar要谨慎处理。微信小程序最终只能有一套 tabBar 配置。不建议在过渡期频繁改 tabBar 架构。更稳妥的做法是主 tabBar 仍由原生项目控制 uni-app 页面先作为普通页面或分包页面接入 等重构完成后再整体调整 tabBar。七、自动化脚本思路为了减少手动复制出错可以写一个 Node.js 脚本做编译后处理。大致流程1. 清理原生项目中的旧 uni 分包目录 2. 从 uni-app 编译目录复制新页面 3. 复制 common、static、components 等依赖 4. 读取 uni-app 编译后的 app.json 5. 提取需要的 pages 或 subpackages 6. 合并到原生项目 app.json 7. 输出最终可上传的小程序工程。伪代码示例const fs require(fs-extra) const path require(path) const uniDist path.resolve(__dirname, ../uniapp/dist/build/mp-weixin) const nativeProject path.resolve(__dirname, ../native-miniapp) async function copyUniBuild() { // 1. 清理旧目录 await fs.remove(path.join(nativeProject, uniPackage)) await fs.remove(path.join(nativeProject, common)) await fs.remove(path.join(nativeProject, static)) // 2. 复制 uni-app 编译产物中的必要目录 await fs.copy(path.join(uniDist, pages), path.join(nativeProject, uniPackage)) await fs.copy(path.join(uniDist, common), path.join(nativeProject, common)) await fs.copy(path.join(uniDist, static), path.join(nativeProject, static)) // 3. 合并 app.json const nativeAppJsonPath path.join(nativeProject, app.json) const nativeAppJson await fs.readJson(nativeAppJsonPath) nativeAppJson.subpackages nativeAppJson.subpackages || [] const uniPackageConfig { root: uniPackage, pages: [ course/index, detail/index, mine/index ] } nativeAppJson.subpackages nativeAppJson.subpackages.filter( item item.root ! uniPackage ) nativeAppJson.subpackages.push(uniPackageConfig) await fs.writeJson(nativeAppJsonPath, nativeAppJson, { spaces: 2 }) console.log(uni-app 页面已合并到原生小程序工程) } copyUniBuild()实际项目中脚本要根据自己的目录结构调整不能完全照搬。重点是让合并流程自动化避免每次手动复制文件导致遗漏。八、不推荐的做法1. 直接把 uni-app 源码放进原生小程序uni-app 的 Vue 文件、pages.json、manifest.json 不能被微信开发者工具直接当作原生小程序页面运行。必须先编译成微信小程序代码。2. 直接覆盖原生 app.json这样很容易导致原来的页面、权限、tabBar、分包配置丢失。正确做法是合并而不是覆盖。3. 两边各自维护登录态这会导致用户状态混乱。登录、退出、token 续期、用户信息更新必须统一。4. 未做完整测试就直接上传混合工程最容易出现路径错误、组件丢失、样式污染、页面跳转失败。每次合并后都要完整测试。九、什么时候应该彻底切到 uni-app如果满足下面几个条件就可以考虑不再混合而是整体切到 uni-app 主工程主要页面已经完成 uni-app 重构登录、支付、分享、订阅、推送等核心能力已经稳定老原生页面只剩少量低频页面测试覆盖完整小程序体积、分包、启动速度都符合要求线上灰度验证没有明显问题。在这之前不建议为了架构统一强行切换。重构项目最重要的是降低发布风险而不是追求一次性改完。十、总结原生小程序重构成 uni-app在未开发完成阶段可以采用混合过渡方案但不是把两个项目直接一起打包而是让最终产物变成一个完整的小程序工程。比较稳妥的方案是以原生小程序为主工程 uni-app 单独开发新页面 uni-app 编译成微信小程序代码 将编译产物作为分包合并进原生工程 统一 app.json、登录态、接口、资源和测试流程。这种方式可以让老功能继续稳定运行新功能逐步用 uni-app 重构。等所有核心页面迁移完成后再考虑整体切换到 uni-app 主工程。对于正在重构中的项目来说最重要的不是技术上能不能混合而是混合后的工程是否可维护、可测试、可发布。只要目录隔离清楚、配置合并规范、登录态统一、脚本自动化处理这种过渡方案是可行的。