写点什么

uniapp vuecli 项目融合 [小记]:将多个项目融合,打包成一个小程序 /App,拆分多个 H5 应用

作者:达摩
  • 2024-01-25
    陕西
  • 本文字数:4566 字

    阅读完需:约 15 分钟

uniapp vuecli项目融合[小记]:将多个项目融合,打包成一个小程序/App,拆分多个H5应用

前言:

目前两个 uniapp vuecli 开发的项目【A、B】,新规划的项目 C:需要融合项目 B 80%的功能模块,同时也需要涵盖项目 A 的所有功能模块。

应用需求:

1、新项目 C【小程序】可支持切换到应用 A/C 界面【内部通过初始化、路由跳转实现切换】【因此新项目 C 考虑基于项目 A 的工程上开发, git 引入项目 B

2、工程 A 在 H5 中需要打包成两个应用:A 应用、C 应用;

实现思路:

1、A 项目工程上开发新应用 C,引入 B 工程的模块/代码:通过 git 地址,安装依赖的方式引入 B 项目;

2、A 工程:小程序打包为一个应用[A+C]、H5 拆分应用[A/C]:通过 pages.json 动态改写来实现,通过不同的打包命令来区分;


A 工程改造:基于 A 开发 C 应用、融合 B 项目,步骤梳理:

1. 通过 git 地址,安装依赖的方式引入 B 项目:package.json
"mobileB": "git+https://git.xxxx/mobileB.git#xxxxxxxxxx"
复制代码

注:git+<项目 B 仓库地址>#<git 提交 commit id>

2. 解决问题:项目 B 使用了 TS,项目 A 未使用 TS,引入后需要转换支持:vue.config.js
module.exports = {  /**   * 某个依赖项是TypeScript编写的,但是目标环境中并不支持TypeScript,   * 那么我们需要在打包前先将其转换为JavaScript代码   */  transpileDependencies: ['mobileB'],  // ...其他配置}
复制代码
3. webpack 编译构建过程中,处理 mobileB 模块导入路径

引入 mobileB 后,因为 mobileB 中的别名等,无法正确被解析,所以需要在编译阶段,进行修改。

定义一个名为 CustomAliasResolverPlugin.js 的文件,实现 webpack 插件类,在 webpack 编译过程中处理 mobileB 模块导入路径,确保 mobileB 中的别名路径都能正确的指向

CustomAliasResolverPlugin.js:

class CustomAliasResolverPlugin {  constructor() {    this.path_mobileB = 'mobileB/src';  }
apply(compiler) { // 监听了normalModuleFactory事件,在每个普通模块被创建时执行回调函数 compiler.hooks.normalModuleFactory.tap( 'CustomAliasResolverPlugin', (factory) => { // 回调函数接收一个模块工厂实例factory作为参数,并在其上进一步监听 beforeResolve 事件。beforeResolve 是在模块解析之前触发的钩子,此时可以修改模块请求信息 factory.hooks.beforeResolve.tapAsync( 'CustomAliasResolverPlugin', (data, callback) => { // 在 beforeResolve 的回调函数内,从数据对象data中获取当前模块的请求路径request和解析上下文context。 const { request, context } = data; // 解析上下文包含指定的基础路径,确保是mobileB下的模块 if (context.includes(this.path_mobileB)) { if (request.startsWith('@src')) { data.request = request.replace('@src', `${this.path_mobileB}/src`); } else if (request.startsWith('@service')) { data.request = request.replace('@service', `${this.path_mobileB}/service`); } } callback(null, data); } ); } ); }}
module.exports = CustomAliasResolverPlugin;
复制代码

CustomAliasResolverPlugin 是一个自定义的 webpack 插件,要求它在项目构建时会参与到 webpack 的模块解析阶段,因此我们需要在添加到 plugins 中,以及解决可能存在的路径问题;vue.config.js:

const CustomAliasResolverPlugin = require('./config/CustomAliasResolverPlugin');
module.exports = { configureWebpack: { plugins: [new CustomAliasResolverPlugin()] } // ...其他配置}
复制代码


4. pages.json 动态改写:实现 H5 应用拆分、小程序整合

uniapp 页面文件的打包是基于 pages.json,所以我们需要根据需求,使用 nodeJs 动态生成对应的 pages.json 文件。

示例:



common.js:编写需要打包成 A、C 两个项目的公共页面路由及其他配置

// 小程序的分包const subPackages = []const pages = [  {    path: 'pages/home/home',    aliasPath: '/'  }]
// 其他配置相关const otherConfig = { globalStyle: { navigationStyle: 'custom', allowsBounceVertical: 'NO', renderingMode: 'seperated', pageOrientation: 'portrait', rpxCalcMaxDeviceWidth: 540, rpxCalcBaseDeviceWidth: 375 }, // 需要自动注册的自定义组件 easycom: { autoscan: true, custom: { '^ant-tree-(.*)': '@/components/businessComponent/ant-tree/ant-tree-$1.vue', '^ant-customize-(.*)': '@/components/businessComponent/ant-customize/ant-customize-$1.vue', '^u-(.*)': 'uview-ui/components/u-$1/u-$1.vue', '^anmc-(.*)': 'antm-ui/components/common/anmc-$1/anmc-$1.vue', '^anmb-(.*)': 'antm-ui/components/business/anmb-$1/anmb-$1.vue' } }};
module.exports = { subPackages, pages, otherConfig};
复制代码

ant4Pages.js/erpPages.js: A/C 应用的页面路由

module.exports = {  pages: [],  subPackages: []}
复制代码

main.js: 根据条件重写 pages.json


const common = require('./common')const subPackages = common.subPackages || []const otherConfig = common.otherConfig || {}const commonPages = common.pages || []
const fs = require('fs');const ant4pages = require('./ant4Pages');const erppages = require('./erpPages');const pagesPath = process.env.UNI_INPUT_DIR + '/pages.json';let pagesJson = {};// 判断是否H5if (process.env.UNI_PLATFORM === 'h5') { let subPackagesProxy = subPackages // 判断是否是ERP项目:对应文中说到的C应用【H5】 // VUE_APP_NODE_APP_TYPE是命令上自行添加的字段,用来区分 if (process.env.VUE_APP_NODE_APP_TYPE === 'erp') { const erpSubPackages = erppages.subPackages erpSubPackages.forEach(item => { const root = subPackagesProxy.find(sub => sub.root === item.root) if (root) { item.pages = [ ...root.pages, ...item.pages ] subPackagesProxy = subPackagesProxy.filter(sub => sub.root !== item.root) } }) pagesJson = { ...erppages, pages: [...commonPages, ...erppages.pages], subPackages: [...subPackagesProxy, ...erpSubPackages], ...otherConfig } } else { const antSubPackages = ant4pages.subPackages antSubPackages.forEach(item => { const root = subPackagesProxy.find(sub => sub.root === item.root) if (root) { item.pages = [ ...root.pages, ...item.pages ] subPackagesProxy = subPackagesProxy.filter(sub => sub.root !== item.root) } }) pagesJson = { ...ant4pages, pages: [...commonPages, ...ant4pages.pages], subPackages: [...subPackagesProxy, ...antSubPackages], ...otherConfig } }} else { // 小程序打包,整合所有的路由配置 const subPackagesProxy = subPackages let antSubPackages = ant4pages.subPackages let erpSubPackages = erppages.subPackages subPackagesProxy.forEach(item => { const antRoot = antSubPackages.find(sub => sub.root === item.root) const erpRoot = erpSubPackages.find(sub => sub.root === item.root) if (antRoot) { item.pages = [ ...antRoot.pages, ...item.pages ] antSubPackages = antSubPackages.filter(sub => sub.root !== item.root) } if (erpRoot) { item.pages = [ ...erpRoot.pages, ...item.pages ] erpSubPackages = erpSubPackages.filter(sub => sub.root !== item.root) } }) pagesJson = { pages: [...commonPages, ...ant4pages.pages, ...erppages.pages], subPackages: [...subPackagesProxy, ...antSubPackages, ...erpSubPackages], ...otherConfig };}
// 将最终的结果写入pages.json,在项目运行或构建时fs.writeFileSync(pagesPath, JSON.stringify(pagesJson), { flag: 'w'});
复制代码

将 src/config/appPages/main.js 文件引入 vue.config.js 中;



注:直接在 vue.congfig.js 顶部导入对于 pages.json 的重写,vue.config.js 文件是 Vue CLI 在构建项目时默认查找并加载的第一个用户自定义配置文件,目前测试没有出现什么异常,如果存在异常,可考虑调整至预处理脚本中,具体根据实际情况而定:

{  "scripts": {    "prebuild": "node ./src/config/main.js",    "build": "vue-cli-service build"  },}
复制代码


5. router 拦截处理【参考】


6. vuex 整合: mobileB 中 vuex 中的 modules,添加到当前主工程的 modules 中:store/index.js
import Vue from 'vue';import Vuex from 'vuex';import createPersistedState from 'vuex-persistedstate';import getters from './getters';import user from './modules/user.js';// 项目B的store集合,全部抛出来,添加到modules中import Bstore from 'mobileB/src/store/main
Vue.use(Vuex);export default new Vuex.Store({ plugins: [ createPersistedState({ storage: { // ... }, reducer(value) { // 选择性配置持久化 return { } } }) ], modules: { user, ...Bstore }, getters});
复制代码
7. 初始化 B 项目依赖的一些数据缓存等;

可以在 B 项目中提供一个应用初始化的方法,在引用项目中合适的时机调用;以下示例仅供参考:

import { libraryId } from "@service/user";import { setLocal } from "src/utils/utils";import { YB_MOBILE_LIBRARY_INFO } from "src/configs/constants";
/** * 对外git引用,初始化方法 */async function appInit(appType: string) { // 存储用户信息、token等 // 初始化一些业务数据等等 const res: any = await libraryId({}); getApp().globalData.appType = appType || 'anterp' setLocal(YB_MOBILE_LIBRARY_INFO, res.res.data.libraryId);}
export default appInit;
复制代码


8. 项目 A/C 工程中,使用项目 B 的组件或页面;

在路由中对应配置项目 B 中的路由,对应创建页面文件,将项目 B 中的文件当作组件引入:【注:目前项目统一使用uni-simple-router路由管理插件,项目 B 中的路由跳转,只要在项目 A/C 工程中同样存在该路由,及可跳转】

<script>import templateDetail from 'mobileB/src/pages/common/templateDetailInfo/index.vue';export default {  components: {    templateDetail  }};</script>
复制代码

备注

在实际的项目开发场景中,对需要融合项目架构的技术、实现方案统一性、代码规范等等有较高的要求,同时会遇到很多问题需要处理,如主题样式、不同平台的语法支持等等,本文仅对实现方案进行一个大致梳理。

发布于: 刚刚阅读数: 5
用户头像

达摩

关注

还未添加个人签名 2019-12-04 加入

还未添加个人简介

评论

发布
暂无评论
uniapp vuecli项目融合[小记]:将多个项目融合,打包成一个小程序/App,拆分多个H5应用_小程序_达摩_InfoQ写作社区