Application Scenarios
- Multiple small programs under the same subject (company, Department)
- These small programs, consisting of a master applet and later new lines of business (each line of business has a separate applet)
- Small programs for each line of business need to be mounted under the main program, as the main program diversion
- At the same time, the business lines of their own applet also released as usual update
- = = A set of code, to generate a separate package and sub-package = = (the subpages directory that needs to be copied to the main program by the package command)
Project Overview
My line of business is called Happy Delivery (project name Enjoy_given), is a free exchange of things platform
Because our line-of-business applet is built with Mpvue (the entire project is also generated through the Mpvue CLI), the subsequent configuration is Mpvue for example, and if the Wepy project is basically similar.
Here's our directory structure.
Several JS files under the SRC directory need to be specifically described below:
Src/app.vue is a small program's entry file, which defines the life cycle of the applet.
Src/main.js inside Initializes common services, defines applet page paths, and global variables
Src/vars.js storing global variables for the entire project
Src/baseinstall.js Basic Method assembly logic (e.g. mount Login to Vue object, statistical logic, identify channel number, etc.)
Overview of subcontracting Configurations
First, you configure the source and AppID
As a sub-package, both parameters should be unified with the main package parameters (recommended through the Webpack configuration to implement)
Source: Is the identity used by each line of business to log in, register, and interface access, to distinguish which line of business the user is from
AppID: Assigned applet AppID
Why do you configure these two parameters: because you cannot log on without configuring
Page path Issues
As a sub-package, all pages of the jump path will be added to the main package jump prefix (recommended through the packaging jump method NavigateTo, Redirectto, relaunch, Navigateback implementation, recommended with Webpack unified processing)
When a new line of business is used as a sub-package to access the main program, the page jump path needs to be preceded by a prefix
Such as: The Independent applet home path is/pages/content/index/main
As a subcontractor, the package assigned by the main program is/subpages/enjoy_given
Then the subcontract line of business lines home path:/subpages/enjoy_given/pages/content/index/main
WXSS Reference Path problem
Do not use the introduction of the root directory (recommended by Webpack or shell script to complete)
Because in the sub-state, the root directory is accessed directly from the root directory of the main program, the file does not exist
Picture path problem
All image paths uniformly use CDN resource access, do not reference local images
Issues that are not performed for subcontracting Main.js and App.vue entry files
It is possible to introduce each entry from the main package to the sub-package page by pulling out the basic business assembly method, which will be followed by
For h5 pages in small programs pull up applet page
When opening webview, to add a flag bit, or prefix, tell H5 page, currently in the sub-package, open the applet path to prefix
Share path problem, prefix path is also added before path
Can be handled uniformly through a common sharing method, followed by
All pages of the applet need to be registered in the main package entry file (App.vue), and each new page must be registered
This is a hole, especially when the new page, it will be easy to ignore this problem, here to highlight the next
Where to pay attention to sub-package access
Storage naming issues, in order to avoid conflicts with the main program or other Business Line applet (recommended zz_ business name _xxx, our business name is Enjoy_given, for short, eg, such as: Zz_eg_address, ZZ refers to the transfer)
Login problem, the recommendation and the main program use the same cookie name, this can be a common set of user information, so that both sides maintain a set, but also avoid duplication of authorization.
Payment issues to ensure that the parameters in the cookie remain the same when the order is placed and when the payment is made
debugging, you can brahma the program to a test package for the main program, copy the generated code (contents of the Dist directory) to the main package subpages/business name/below
For example, our directory is subpages/enjoy_given/(same as the directory structure)
A set of code that generates corresponding packages (separate packages and subcontracting) with different packaging commands
Package.json in 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
Why no main program subcontracting (test)
Because whether we build test subcontracting or build formal subcontracting, we have to copy the code under the generated dist to the subpages/enjoy_given/directory of the main program, the cost is basically the same, so there is no command to write the component sub-package
Subcontracting Webpack Configuration
Because of the need to be compatible with separate small programs and subcontracting services, Webpack we recommend separate configuration
We have configured Webpack for the test environment and the formal environment, and modify the global parameters of the project directly by replacing the global variables with the Webpack configuration .
Perform the replacement dynamically with the NPM command.
In order to separate the configuration, we copied a copy of build.js renamed Build-subpkg.js
"scripts": { ..., "build_subPkg": "node build/build-subpkg.js && sh ./scripts/path-replace.sh"}
The BUILD_SUBPKG command is the read Build-subpkg.js file
Build.js and Build-subpkg.js 99% of the same content, only one line is different
var webpackConfig = require('./webpack.prod.conf')变更为var webpackConfig = require('./webpack.subpkg.prod.conf')
So the next step is to create the webpack.subpkg.prod.conf file
webpack.subpkg.prod.conf by webpack.prod.conf Copy, inside still 99% content consistent
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 }), ... ]})
The Defineplugin plugin is used for global substitution
such as: ' Process.env ': ' hahaha ', refers to the global process.env replaced by "hahaha"
Inside, by defining multiple global variables, when packaging is implemented, the global variables in the corresponding environment are replaced by different commands.
Let's take a look. Files in/config/index.js
var path = require('path')module.exports = { build: { env: require('./prod.env'), ... }, dev: { env: require('./dev.env'), ... }}
Introduced Dev.env.js and Prod.env.js.
Taking Prod.env.js as an example
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}
And then we'll take a look at the file src/vars.js that holds the global variable (in the item above)
// 小程序常量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 }), ... ]})
After packaging is complete, "app.xxx" in the global variable file is replaced by the variable with the same name in the Webpack.
If the appId:app.id app.id in Vars.js is replaced, the value is "WXAAAAAAAAAAAAAAA" when the standalone applet is "WXBBBBBBBBBBBBBB" when it is a subcontracting business
So the whole process of replacing global variables runs out.
= = as a sub-package, access to the main program, their own main.js and App.vue will not execute = =
This is the big pit, because many of the common business initialization such as login, cookie, statistics are done here.
Solution Solutions
The basic functions of the Assembly business (such as recording, statistics, identification channel number and other logic) from Main.js to another file, I called baseinstall.js here.
I also joined in the processing of query, such as channel number and entrance scene.
In that case, the src/main.js will be very simple,
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: { ... } }}
The key is Baseinstall.init () This line of code
Now let's take a look at 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}
Why use the VARS.BASEINSTALLFLAG flag bit
Because, in the subcontracting time does not perform the main.js, the actual scene, will jump from the main package business directly to some sub-package of the page.
Because there is no fixed entrance, so in these pages to join the introduction of baseinstall.js, in order to avoid repeated assembly, will be set this flag bit.
Why do you want to pull these operations away?
The baseinstall.init contains all the services that need to be initialized when starting a small program
It is also mentioned that in the case of subcontracting, your own App.vue and main.js will not be executed.
So, on all pages, add the Baseinstall.init method to the OnLoad life cycle.
, so we have to pull away for a more convenient reuse.
Take home page for example (Pages/content/index/index.vue)
import baseInstall from '@/baseInstall'export default { ... async onLoad (options) { options = await baseInstall.init(options) ... }}
Async/await is used because some logic in Baseinstall.init uses asynchronous requests
Because the main program does not read main.js, all the subcontracting page paths must be registered in the main program uniformly.
Note: Each new page will be registered in the main program. That is, to add a page, notify the main program side, in their files unified registration
Page path
In subcontracting, all page path accesses are prefixed
such as: The original visit/pages/content/index/main can be
However, the access path for the subcontracting is:/subpages/enjoy_given/pages/content/index/main
Solution:
Take the navigateto of packing as an example
async navigateTo (route) { route.url = VARS.pathPrefix + (route.url.indexOf('/') === 0 ? '' : '/') + route.url // 这里做前缀处理 console.log('[Navigator] navigateTo:', route) ... wx.navigateTo(route)}
This requires no prefix, which is determined by the Pathprefix in the global variable VARs.
And Pathprefix is dynamically replaced by Webpack according to the packaging commands during the packaging process.
Picture Access Path Issues
Image access Path unifies the resource access path of the CDN, do not use the local access path, otherwise there is a problem in the subcontracting path, but also increase the volume of the package
WXSS path problem
The Wxss file generated by Mpvue will be introduced into the generic VENDOR.WXSS, but the introduction path is the root path, and as a sub-package, the root path is directly introduced and the path of the main package is accessed, resulting in the file not being found.
@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}...
Solution Solutions
Batch substitution of files with shell scripts
scripts/path-replace.sh
#!/bin/shsed -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`
The purpose of this shell script is to replace the/STATIC/CSS/VENDOR.WXSS in all WXSS files under the./dist/static/css/pages/with the/subpages/huanlesong/static/css\ Vendor.wxss
Path change OK when replace is complete
When generating a formal package, use NPM run build_subpkg to be OK.
Sharing path Issues
The same path is shared between the main program and the standalone applet, which is similar in handling and jumping.
Solution Solutions
It is recommended that the common approach be handled uniformly, and we do this by adding a common method to the onshareappmessage of the page share.getfinalshareinfo
Take home share as an example
import Share from '@/lib/share'export default { ... onShareAppMessage () { ... return Share.getFinalShareInfo({ title: 'xxx', path: `/pages/content/index/main`, imageUrl: 'xxxx' }) }}
Unified call Share.getfinalshareinfo method when sharing
Let's see Share.js again.
export default class Share { static getFinalShareInfo (shareInfo) { ... // 路径前缀处理 shareInfo.path = VARS.pathPrefix + (shareInfo.path.indexOf('/') === 0 ? '' : '/') + shareInfo.path ... return shareInfo }}
So the entire subcontracting business is configured to complete. is not very troublesome ~
When the original and the main process is really stepping on a lot of pits, here I put the solution to share with you
If there is a better solution, also want to communicate together:)