首先一个基础分包方案:
包不用区分渠道,只是通过文件名进行区分,公共代码逻辑可以通过mixins进行混入。
这样分包后就需要在打包时只针对编译的渠道包文件进行替换打包,其他渠道包的文件不打包进去,通过工具类实现替换。
项目主目录下的manifest.json 和 pages.json 可以是空内容,都是通过工具类将prebuild目录下不同渠道不同平台的对应文件内容复制然后写到项目主目录下去的。
- 先看主要配置文件:
//package.json
{
"name": "demo",
"config": {
"dev": "cross-env NODE_ENV=development uniapp-cli custom",
"pro": "cross-env NODE_ENV=production uniapp-cli custom"
},
"version": "0.1.0",
"private": true,
"scripts": {
"dev:ks-brandA": "cross-env-shell $npm_package_config_dev bd-brandA --minimize",
"dev:tt-brandA": "cross-env-shell $npm_package_config_dev tt-brandA --minimize",
"dev:bd-brandA": "cross-env-shell $npm_package_config_dev ks-brandA --minimize",
"build:bd-brandA": "cross-env-shell $npm_package_config_pro bd-brandA --minimize",
"build:tt-brandA": "cross-env-shell $npm_package_config_pro tt-brandA --minimize",
"build:ks-brandA": "cross-env-shell $npm_package_config_pro ks-brandA --minimize",
"dev:ks-brandB": "cross-env-shell $npm_package_config_dev bd-brandB --minimize",
"dev:tt-brandB": "cross-env-shell $npm_package_config_dev tt-brandB --minimize",
"dev:bd-brandB": "cross-env-shell $npm_package_config_dev ks-brandB --minimize",
"build:bd-brandB": "cross-env-shell $npm_package_config_pro bd-brandB --minimize",
"build:tt-brandB": "cross-env-shell $npm_package_config_pro tt-brandB --minimize",
"build:ks-brandB": "cross-env-shell $npm_package_config_pro ks-brandB --minimize",
},
"dependencies": {
"@dcloudio/uni-app": "^2.0.2-3081220230817001",
"@dcloudio/uni-app-plus": "^2.0.2-3081220230817001",
"@dcloudio/uni-h5": "^2.0.2-3081220230817001",
"@dcloudio/uni-i18n": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-360": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-alipay": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-baidu": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-jd": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-kuaishou": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-lark": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-qq": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-toutiao": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-vue": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-weixin": "^2.0.2-3081220230817001",
"@dcloudio/uni-mp-xhs": "^2.0.2-3081220230817001",
"@dcloudio/uni-quickapp-native": "^2.0.2-3081220230817001",
"@dcloudio/uni-quickapp-webview": "^2.0.2-3081220230817001",
"@dcloudio/uni-stacktracey": "^2.0.2-3081220230817001",
"@dcloudio/uni-stat": "^2.0.2-3081220230817001",
"@dcloudio/uni-ui": "^1.4.28",
"@vue/shared": "^3.0.0",
"core-js": "^3.8.3",
"crypto-js": "^3.1.9-1",
"flyio": "^0.6.2",
"vue": ">= 2.6.14 < 2.7",
"vuex": "^3.2.0"
},
"devDependencies": {
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "^2.0.2-3081220230817001",
"@dcloudio/uni-cli-i18n": "^2.0.2-3081220230817001",
"@dcloudio/uni-cli-shared": "^2.0.2-3081220230817001",
"@dcloudio/uni-helper-json": "*",
"@dcloudio/uni-migration": "^2.0.2-3081220230817001",
"@dcloudio/uni-template-compiler": "^2.0.2-3081220230817001",
"@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.2-3081220230817001",
"@dcloudio/vue-cli-plugin-uni": "^2.0.2-3081220230817001",
"@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.2-3081220230817001",
"@dcloudio/webpack-uni-mp-loader": "^2.0.2-3081220230817001",
"@dcloudio/webpack-uni-pages-loader": "^2.0.2-3081220230817001",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-import": "^1.11.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.2",
"jest": "^25.4.0",
"less": "^4.1.3",
"less-loader": "^7.3.0",
"mini-types": "*",
"miniprogram-api-typings": "*",
"postcss-comment": "^2.0.0",
"sass": "^1.49.8",
"sass-loader": "^8.0.2",
"vue-template-compiler": ">= 2.6.14 < 2.7",
"webpack-cli": "^5.1.4",
"vconsole": "^3.15.0"
},
"browserslist": [
"Android >= 4.4",
"ios >= 9"
],
"uni-app": {
"scripts": {
"tt-brandA": {
"env": {
"UNI_PLATFORM": "h5"
},
"define": {
"MP-BRANDA": true
}
},
"ks-brandA": {
"env": {
"UNI_PLATFORM": "mp-kuaishou"
},
"define": {
"MP-BRANDA": true
}
},
"tt-brandB": {
"env": {
"UNI_PLATFORM": "h5"
},
"define": {
"MP-BRANDB": true
}
},
"ks-brandB": {
"env": {
"UNI_PLATFORM": "mp-kuaishou"
},
"define": {
"MP-BRANDB": true
}
},
"bd-brandB": {
"env": {
"UNI_PLATFORM": "mp-baidu"
},
"define": {
"MP-BRANDB": true
}
},
}
}
}
//src/pages.js:: 直接复制 prebuild下对应brand和平台platform下的pages.js文件
const {getApp,getHostCode} = require("../prebuild/env");
const path = require('path')
module.exports = (pagesJson, loader) => {
let brandName = getApp();
let code = getHostCode()
const srcPath = path.join(__dirname, `../prebuild/${brandName}/pages-${code}.js`)
let pagesJsonNew = require(srcPath)
loader.addDependency(require.resolve(srcPath))
return pagesJsonNew
}
//vue.config.js
const fs = require("fs");
const CopyWebpackPlugin = require('copy-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const {getApp,getOS} = require("./prebuild/env");
/*
* 本函数重定向输出路径到下面路径 *
* */
function rebuildOutputPath(brand) {
let separator = getOS() == 'win' ? '\\' : '/'
const lastIndex = process.env.UNI_OUTPUT_DIR.lastIndexOf(separator)
let pre = process.env.UNI_OUTPUT_DIR.substring(0, lastIndex)
console.log("vue.config--buildOutputPath::",process.env.UNI_OUTPUT_DIR.substring(lastIndex))
process.env.UNI_OUTPUT_DIR = process.env.UNI_OUTPUT_DIR.substring(lastIndex) + separator + brand
}
const brandName = getApp()
rebuildOutputPath(brandName)
let clearDir = ['static/img-*']
module.exports = {
chainWebpack: (config) => {
//本插件将图片资源目录例如img-brandA, 拷贝到输出目录的static/img下面
config.plugin('copy').use(CopyWebpackPlugin, [{
patterns: [{
from: `src/static/img-${brandName}`,
to: 'static/imgs/'
}]
}])
//本插件将输出目录下,重复无效的/static/img-*目录删除掉
config.plugin('clean').use(CleanWebpackPlugin, [{
cleanAfterEveryBuildPatterns: clearDir
}])
//本插件进行预编译相关的拷贝工作,拷贝指定brand的manifest和生成build.less
config.plugin('CopyPlugin').use(require('./prebuild/CopyPlugin', [{
brand: brandName
}]))
//对目标brand相关的非公用源文件进行监控更新编译的loader
config.module.rule('module_replace').test(/\.vue$/).pre().use('./prebuild/module_replace')
.loader('./prebuild/module_replace').end()
}
}
// 本代码模块配合module_replace的loader完成【对目标brand相关的非公用源文件进行监控更新编译】
const chokidar = require('chokidar');
//监测改动时触发编译
const ignoreFiles = [];
const brandSign = `-${brandName}.`
const watcher = chokidar.watch('src/').on('ready', async () => {
await watcher.close()
chokidar.watch('src/', {ignored: ignoreFiles})
.on('change', (path, stats) => {
console.log('changed file', path);
let brandSignIndex = path.lastIndexOf(brandSign)
let endFix = path.substring(brandSignIndex + brandSign.length)
let preFix = path.substring(0, brandSignIndex)
let destPath = preFix + '.' + endFix
const destContent = fs.readFileSync(destPath, {encoding: "utf-8"})
fs.writeFileSync(destPath, destContent + '\n')
fs.writeFileSync(destPath, destContent)
})
}).on('add', (path) => {
if (path.indexOf(brandSign) <= 0) ignoreFiles.push(path)
})
vue.config.js 是项目打包入口,在里面使用了三个工具类,env.js CopyPlugin.js module_replace.js;通过不同的brand读取指定brand文件,重写到指定文件,实现分渠道打包
- 三个不可缺少工具类:
//env.js文件
module.exports = {
getApp() {
if (process.env.UNI_SCRIPT) {
const script = process.env.UNI_SCRIPT
let subs = script.split('-')
if (subs.length == 2) return subs[1]
}
console.error('错误!!未检测到有效的brand名称!!')
return 'default'
},
makeDateTimeString(date) {
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
if (month < 10) month = '0' + month
if (day < 10) day = '0' + day
let hour = date.getHours();
let min = date.getMinutes();
let sec = date.getSeconds()
if (min < 10) min = '0' + min
if (sec < 10) sec = '0' + sec
return `${year}-${month}-${day} ${hour}:${min}:${sec}`
},
getOS() {
let host = 'ios'
let arch = process.env.MSYSTEM_CARCH
if (arch == 'x86_64' || arch == 'x86_32') host = 'win'
return host
},
getHostCode() {
if (process.env.UNI_SCRIPT) {
const script = process.env.UNI_SCRIPT
let subs = script.split('-')
if (subs.length == 2) return subs[0]
}
console.error('错误!!未检测到有效的brand名称!!')
return ''
}
}
//CopyPlugin.js文件
const fs = require("fs");
const path = require('path');
const {
getApp, getHostCode
} = require("./env");
class CopyPlugin {
log(...param) {
console.log("CopyPlugin", ...param)
}
constructor() {
this.log('constructed')
}
apply(compiler) {
compiler.hooks.environment.tap('Manifest', params => {
let brandName = getApp()
const platform = process.env.UNI_PLATFORM
console.log('brandName=' + brandName, 'platform=' + platform)
// 构造build.less
let buildStr = '@brand: ' + brandName + ';'
fs.writeFileSync('src/build.less', buildStr)
//复制manifest.json
let manifestPath = `prebuild/${brandName}/manifest.json`
console.log('manifestPath=',manifestPath)
let destManifestPath = 'src/manifest.json'
const manifestContent = fs.readFileSync(manifestPath, {
encoding: "utf-8"
});
fs.writeFileSync(destManifestPath, manifestContent)
})
}
}
module.exports = CopyPlugin
//module_replace.js 文件
const {getApp} = require("./env");
const fs = require("fs");
const tag = 'loader:module-replace'
const brandName = getApp()
const brandSign = `-${brandName}.`
module.exports = function(content) {
let resourcePath = this.resourcePath
let dotIndex = resourcePath.lastIndexOf('.')
let endFix = resourcePath.substring(dotIndex)
let srcPath = resourcePath.substring(0, dotIndex) + '-' + brandName + endFix
if (srcPath.indexOf(brandSign) <= 0) return content
if (!fs.existsSync(srcPath)) return content
console.log(tag, 'replace from', srcPath)
const newContent = fs.readFileSync(srcPath, {
encoding: "utf-8"
});
return newContent
}
运行打包时只需要控制台中输入package.json中指定的脚本key值即可:
npm run dev:tt-barndA
npm run build:ks-barndB
按照上述方法分包后即使实现一个项目多平台多渠道打包,如有问题请多指教
资源包在下一篇文章