小程序多业务线融合【完整分包业务接入】

栏目: CSS · 发布时间: 6年前

内容简介:我的这条业务线叫欢乐送(项目名为enjoy_given),是转转旗下一个免费的以物换物平台因为我们这条业务线小程序是用mpvue构建的(整个项目也是通过mpvue的cli生成的),所以后面相关配置都是以mpvue为例,如果是wepy项目基本也大同小异。下面就是我们的目录结构
  • 同一个主体(公司、部门)下有多个小程序
  • 这些小程序,由一个主小程序和后来新建的多条业务线构成(每条业务线拥有独立的小程序)
  • 各业务线的小程序需要挂载到主程序下面,因为需要主程序导流
  • 同时各业务线自己的小程序也照常发布更新
  • == 一套代码,通过打包命令,来生成独立包和分包 ==(分包生成完需要拷贝到主程序的subPages目录下)

项目概述

我的这条业务线叫欢乐送(项目名为enjoy_given),是转转旗下一个免费的以物换物平台

因为我们这条业务线小程序是用mpvue构建的(整个项目也是通过mpvue的cli生成的),所以后面相关配置都是以mpvue为例,如果是wepy项目基本也大同小异。

下面就是我们的目录结构

小程序多业务线融合【完整分包业务接入】

src目录下的几个js文件需要专门介绍下:

src/App.vue 是小程序的入口文件,里面定义的是小程序的生命周期

src/main.js 里面初始化通用业务、定义小程序页面路径和全局变量

src/vars.js 存放整个项目的全局变量

src/baseInstall.js 基础方法装配逻辑(如:给vue对象挂载登录、统计逻辑、识别渠道号等)

分包配置概述

  • 首先要配置source和appid

    作为分包时,这两个参数都要统一采用主包参数(建议通过webpack配置来实现)

    source:是每条业务线登录、注册、和接口访问时用的标识,用来区分该用户来自于哪条业务线

    appid:微信分配的小程序appid

    为什么要配置这两参数:因为不配置没法登录

  • 页面路径问题

    作为分包时,所有页面的跳转路径都要加主包的跳转前缀(建议通过包装跳转方法navigateTo、redirectTo、reLaunch、navigateBack实现,建议配合webpack统一处理)

    当新业务线作为分包接入主程序时,页面跳转路径前需要统一加一个前缀

    如:独立小程序首页路径为 /pages/content/index/main

    作为分包时,主程序分配的包为/subPages/enjoy_given

    那么分包业务线首页路径为: /subPages/enjoy_given/pages/content/index/main

  • wxss引用路径问题

    不要用使根目录引入方式(建议采用webpack或者 shell 脚本来完成)

    因为在分包状态下,用根目录访问方式会直接访问主程序的根目录,文件是不存在的

  • 图片路径问题

    所有图片路径统一采用cdn资源访问方式,不要引用本地图片

  • 对于分包的main.js和App.vue入口文件不执行的问题

    可以通过抽离基础业务装配方法,对于每一个从主包跳到分包页面的入口分别引入,后面会细说

  • 对于小程序内的h5页面拉起小程序页面

    在打开webview时候,要加入一个标志位,或者prefix,告诉h5页面,当前处于分包当中,打开的小程序path要加前缀

  • 分享路径问题,在路径前面也要加入路径前缀

    可以通过一个通用的分享方法,进行统一处理,后面会细说

  • 小程序的所有页面都需要在主包入口文件(app.vue)注册,每新增页面都要注册

    这个是坑,尤其新增页面时,会很容易忽略这个问题,这里要特别强调下

分包接入需要注意的地方

  • storage命名问题,为了避免和主程序或者其他业务线小程序发生冲突(建议采用 zz_业务名_xxx, 我们业务名是enjoy_given,简称eg,如: zz_eg_address, zz指的就是转转)

  • 登录问题,推荐和主程序使用同样的cookie名称,这样可以通用一套用户信息,免得双方各维护一套,还能避免重复授权。

  • 支付问题,保证下单时和支付时,cookie中的参数保持一致

  • 调试,可以找主程序那边要个主程序的测试包,把生成的代码(dist目录下的内容)拷贝到主程序包的 subPages/业务名/ 下面

    例如我们的目录是 subPages/enjoy_given/(目录结构同上)

一套代码,通过不同打包命令生成对应的程序包(独立包和分包)

package.json中scripts

"scripts": {
    "dev": "node build/dev-server.js",
    "start": "node build/dev-server.js",
    "build": "rimraf dist && node build/build.js",
    "lint": "eslint --ext .js,.vue src",
    "build_subPkg": "node build/build-subpkg.js && sh ./scripts/path-replace.sh"
}
复制代码
独立小程序(调试) npm run dev
独立小程序(构建) npm run build
主程序分包(构建) npm run build_subPkg
复制代码

为什么没有主程序分包(测试)

因为我们无论是构建测试分包还是构建正式分包,都要把生成dist下的代码拷贝到主程序的subPages/enjoy_given/目录下,成本基本是一样的,所以,就没有写构件分包的命令

分包webpack配置

因为需要兼容独立小程序和分包业务,webpack我们建议分开配置

我们对测试环境和正式环境分别配置了webpack, 通过对webpack配置替换全局变量,直接修改项目的全局参数 。 通过npm命令动态执行替换。

为了分开配置,我们拷贝了一份build.js更名为build-subpkg.js

"scripts": {
    ...,
    "build_subPkg": "node build/build-subpkg.js && sh ./scripts/path-replace.sh"
}
复制代码

build_subPkg命令就是读取的build-subpkg.js文件 build.js和build-subpkg.js中99%的内容都一样,只有一行不一样

var webpackConfig = require('./webpack.prod.conf')
变更为
var webpackConfig = require('./webpack.subpkg.prod.conf')
复制代码

所以下一步就是创建webpack.subpkg.prod.conf文件 webpack.subpkg.prod.conf由webpack.prod.conf拷贝而来,里面依旧99%的内容一致

// webpack.prod.conf

...
var config = require('../config')
var env = config.build.env
...
var webpackConfig = merge(baseWebpackConfig, {
    ...
    plugins: [
        new webpack.DefinePlugin({
            'process.env': env,
            'app.source': env.APP_SOURCE,
            'app.udeskDebug': env.UDESK_DEBUG,
            'app.id': env.APP_ID,
            'app.pathPrefix': env.APP_PATH_RREFIX,
            'app.isUseCrazyFormId': env.IS_USE_CRAZY_FORMD_ID
        }),
        ...
    ]
})
复制代码

// webpack.subpkg.prod.conf

...
var config = require('../config')
var env = config.build.env
...
var webpackConfig = merge(baseWebpackConfig, {
    ...
    plugins: [
        new webpack.DefinePlugin({
            'process.env': env,
            'app.source': env.APP_SUB_PKG_SOURCE,
            'app.udeskDebug': env.UDESK_DEBUG,
            'app.id': env.APP_SUB_PKG_ID,
            'app.pathPrefix': env.APP_SUB_PKG_PATH_RREFIX,
            'app.isUseCrazyFormId': env.IS_USE_CRAZY_FORMD_ID
        }),
        ...
    ]
})
复制代码

DefinePlugin插件是用来进行全局替换的 如:'process.env': '"hahaha"', 指的就是全局process.env替换为"hahaha"

里面通过定义多个全局变量,实现打包时,通过不同的命令替换对应环境下的全局变量 我们看一下../config/index.js中的文件

var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    ...
  },
  dev: {
    env: require('./dev.env'),
    ...
  }
}

复制代码

引入了dev.env.js和prod.env.js

以prod.env.js为例

module.exports = {
  // 环境
  NODE_ENV: '"production"',
  // 欢乐送独立小程序source
  APP_SOURCE: '114',
  // 欢乐送分包小程序source
  APP_SUB_PKG_SOURCE: '103',
  // 欢乐送独立程序appid
  APP_ID: '"wxaaaaaaaaaaaaaaa"',
  // 欢乐送分包程序appid
  APP_SUB_PKG_ID: '"wxbbbbbbbbbbbbbbbb"',
  // udesk测试标志位
  UDESK_DEBUG: false,
  // 欢乐送独立小程序页面路径前缀
  APP_PATH_RREFIX: '""',
  // 欢乐送分包小程序页面路径前缀
  APP_SUB_PKG_PATH_RREFIX: '"/subPages/enjoy_given"',
  // 是否启用crazyFormId
  IS_USE_CRAZY_FORMD_ID: true
}

复制代码

然后我们再来看一下存放全局变量的文件src/vars.js(上面项目截图中有)

// 小程序常量
export default {
  ...
  // 小程序版本号
  version: '1.3.5',
  // 小程序appid
  appId: app.id,
  // 小程序source(由webpack根据不同环境统一替换)
  source: app.source,
  // 路径前缀
  pathPrefix: app.pathPrefix,
  // 是否启用CrazyFormId
  isUseCrazyFormId: app.isUseCrazyFormId
}

复制代码
var webpackConfig = merge(baseWebpackConfig, {
    ...
    plugins: [
        new webpack.DefinePlugin({
            'process.env': env,
            'app.source': env.APP_SUB_PKG_SOURCE,
            'app.udeskDebug': env.UDESK_DEBUG,
            'app.id': env.APP_SUB_PKG_ID,
            'app.pathPrefix': env.APP_SUB_PKG_PATH_RREFIX,
            'app.isUseCrazyFormId': env.IS_USE_CRAZY_FORMD_ID
        }),
        ...
    ]
})
复制代码

在打包完成后,全局变量文件中的"app.xxx"会被webpack中的同名变量替换掉

如vars.js中 appId: app.id的app.id会被替换,独立小程序时该值为"wxaaaaaaaaaaaaaaa",作为分包业务时,该值为"wxbbbbbbbbbbbbbb"

这样整个替换全局变量的流程就跑完了

==作为分包,接入主程序中,自己的main.js和App.vue都不会执行==

这个是大坑,因为很多通用业务的初始化如登录、cookie、统计都是在这里完成的。

解决方案

把基础功能的装配业务(如在录、统计、识别渠道号等逻辑)从main.js中抽离到另一个文件,我这里叫baseInstall.js。 里面我还加入了对query的处理,比如渠道号channel和微信入口scene。

那这样的话,src/main.js就会变得非常简单,

import Vue from 'vue'
import App from './App'
import baseInstall from './baseInstall'
App.mpType = 'app'
baseInstall.init()  // !!!最关键就是这行代码!!!
const app = new Vue(App)
app.$mount()

export default {
  config: {
    pages: [
      '^pages/content/index/main',            // 首页
      ...
    ],
    window: {
      ...
    }
  }
}

复制代码

里面最关键的是baseInstall.init()这行代码

下面我们来看看baseInstall.js

// 通用业务装配初始化
...
async function init (opts) {
  let options = opts
  ...
  // 获取指定渠道号
  const channel = options.channel || options.c || ''
  // 设置渠道号
  if (channel) {
    VARS.channel = channel.indexOf('waeg_') === 0 ? channel : ('waeg_' + channel)
  }
  ...
  if (!VARS.baseInstallFlag) {
    // 为了避免重复装备,通过标志位进行区分
    VARS.baseInstallFlag = true
    ...
    // 登录配置
    ZZLogin.config({
      source: VARS.source
    })
    ZZLogin.install()
    Navigator.install()
    // 统计
    LeStatic.config({
      appid: VARS.source,
      pageTypePrefix (currentRoute) {
        return 'waeg_'
      }
    }).install()
    ...
  }
  // 写入cookie
  cookie.set({
    channelid: VARS.channel,
    fromShareUid: VARS.shareUid
  })

  return options
}

export default {
  init
}

复制代码

为什么要用VARS.baseInstallFlag标志位

因为,在分包时候是不执行main.js的,实际场景,会从主包的业务直接跳转到分包的一些页面。

由于没有固定入口,所以在这些页面中都要加入baseInstall.js的引入,为了避免重复装配,才会设置这个标志位。

为什么要把这些业务抽离

baseInstall.init里面涵盖了所有启动小程序时需要初始化的业务

前面也提到了在作为分包时,自己的App.vue和main.js是不会执行的。

那怎么办,这样,就在所有的页面中,在onLoad的生命周期中加入baseInstall.init方法。 ,所以我们抽离肯定是为了更方便的复用了。

以首页为例(pages/content/index/index.vue)

import baseInstall from '@/baseInstall'

export default {
    ...
    async onLoad (options) {
        options = await baseInstall.init(options)
        ...
    }
}
复制代码

用async/await是因为baseInstall.init中部分逻辑用到了异步请求

因为主程序不会读取main.js,所以,所有的分包页面路径,都要统一在主程序中注册

注:每新增一个页面,都要在主程序中注册。也就是新增一个页面,就要通知主程序那边,在他们的文件里统一注册

页面路径

在分包中,所有页面路径访问要加入前缀

如:原来访问/pages/content/index/main就可以了

但是分包的访问路径为: /subPages/enjoy_given /pages/content/index/main

解决方案:

以包装的navigateTo为例

async navigateTo (route) {
    route.url = VARS.pathPrefix + (route.url.indexOf('/') === 0 ? '' : '/') + route.url // 这里做前缀处理
    console.log('[Navigator] navigateTo:', route)
    ...
    wx.navigateTo(route)
}
复制代码

这里面需不需要加前缀,都是由全局变量VARS中的pathPrefix来决定

而pathPrefix是在打包过程中由webpack根据打包命令动态替换的

图片访问路径问题

图片访问路径统一采用cdn的资源访问路径,不要用本地访问路径,要不然在分包路径中是有问题的,同时也会增加程序包的体积

wxss路径问题

用mpvue生成的wxss文件,里面会把通用的vendor.wxss引入,但是引入路径是根路径,作为分包,直接引入根路径,会去访问主包的路径,导致文件无法找到。

@import "/static/css/vendor.wxss"; //在分包中用根路径是无法找到文件的
._button,._input[type=button],._input[type=reset],._input[type=submit],._textarea{-webkit-appearance:none}._button:after{border:none}page{background-color:#fff}...
复制代码

解决方案

通过shell脚本对文件进行批量替换 scripts/path-replace.sh

#!/bin/sh
sed -i "_bak" "s/\/static\/css\/vendor\.wxss/\/subPages\/enjoy_given\/static\/css\/vendor\.wxss/g" `grep "\/static\/css\/vendor\.wxss" -rl ./dist/static/css/pages/**/*.wxss ./dist/static/css/pages/*/*/*.wxss`
复制代码

这段shell脚本的目的就是把./dist/static/css/pages/下所有的wxss文件中的/static/css/vendor.wxss替换成/subPages/huanlesong/static/css\vendor.wxss

替换完成后,路径变更ok 生成正式包的时候,用npm run build_subPkg就ok了

分享路径问题

主程序和独立小程序分享出来的路径也是一样的,处理方式和跳转类似。

解决方案

建议通过通用方法统一处理,我们的做法是,在页面的onShareAppMessage中加入通用方法Share.getFinalShareInfo

以首页分享为例

import Share from '@/lib/share'
export default {
    ...
    onShareAppMessage () {
        ...
        return Share.getFinalShareInfo({
            title: 'xxx',
            path: `/pages/content/index/main`,
            imageUrl: 'xxxx'
        })
    }
}
复制代码

分享时统一调用Share.getFinalShareInfo方法

我们再来看下share.js

export default class Share {
    static getFinalShareInfo (shareInfo) {
        ...
        // 路径前缀处理
        shareInfo.path = VARS.pathPrefix + (shareInfo.path.indexOf('/') === 0 ? '' : '/') + shareInfo.path
        ...
        return shareInfo
    }
}
复制代码

这样整个分包业务就配置完成了。是不是很麻烦~

当初和主程序融合时候确实踩了很多坑,这里我把解决方案和大家分享下

如果有更好的解决方案,也希望一起交流:)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

码出高效:Java开发手册

码出高效:Java开发手册

杨冠宝、高海慧 / 电子工业出版社 / 2018-10 / 99.00元

《码出高效:Java 开发手册》源于影响了全球250万名开发工程师的《阿里巴巴Java开发手册》,作者静心沉淀,对Java规约的来龙去脉进行了全面而彻底的内容梳理。《码出高效:Java 开发手册》以实战为中心,以新颖的角度全面阐述面向对象理论,逐步深入地探索怎样成为一位优秀开发工程师。比如:如何驾轻就熟地使用各类集合框架;如何得心应手地处理高并发多线程问题;如何顺其自然地写出可读性强、可维护性好的......一起来看看 《码出高效:Java开发手册》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具