前言:

        目前两个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仓库地址>#

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中的别名路径都能正确的指向。

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 = {};

// 判断是否H5

if (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中:

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工程中同样存在该路由,及可跳转】

备注

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

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: