应用场景
项目概述我的这条业务线叫欢乐送(项目名为enjoy_given),是转转旗下一个免费的以物换物平台 因为我们这条业务线小程序是用mpvue构建的(整个项目也是通过mpvue的cli生成的),所以后面相关配置都是以mpvue为例,如果是wepy项目基本也大同小异。 下面就是我们的目录结构
src目录下的几个js文件需要专门介绍下: src/App.vue 是小程序的入口文件,里面定义的是小程序的生命周期 src/main.js 里面初始化通用业务、定义小程序页面路径和全局变量 src/vars.js 存放整个项目的全局变量 src/baseInstall.js 基础方法装配逻辑(如:给vue对象挂载登录、统计逻辑、识别渠道号等) 分包配置概述
分包接入需要注意的地方
一套代码,通过不同打包命令生成对应的程序包(独立包和分包)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配置替换全局变量,直接修改项目的全局参数。 为了分开配置,我们拷贝了一份build.js更名为build-subpkg.js "scripts": { ..., "build_subPkg": "node build/build-subpkg.js && sh ./scripts/path-replace.sh" }
build_subPkg命令就是读取的build-subpkg.js文件 var webpackConfig = require('./webpack.prod.conf') 变更为 var webpackConfig = require('./webpack.subpkg.prod.conf')
所以下一步就是创建webpack.subpkg.prod.conf文件 // 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 }), ... ] })
里面通过定义多个全局变量,实现打包时,通过不同的命令替换对应环境下的全局变量 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中的同名变量替换掉
这样整个替换全局变量的流程就跑完了 ==作为分包,接入主程序中,自己的main.js和App.vue都不会执行==这个是大坑,因为很多通用业务的初始化如登录、cookie、统计都是在这里完成的。 解决方案
把基础功能的装配业务(如在录、统计、识别渠道号等逻辑)从main.js中抽离到另一个文件,我这里叫baseInstall.js。 那这样的话,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) ... } }
因为主程序不会读取main.js,所以,所有的分包页面路径,都要统一在主程序中注册
页面路径在分包中,所有页面路径访问要加入前缀
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脚本对文件进行批量替换 #!/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 分享路径问题主程序和独立小程序分享出来的路径也是一样的,处理方式和跳转类似。 解决方案建议通过通用方法统一处理,我们的做法是,在页面的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 } } 这样整个分包业务就配置完成了。是不是很麻烦~ 当初和主程序融合时候确实踩了很多坑,这里我把解决方案和大家分享下 如果有更好的解决方案,也希望一起交流:) |