导语
有时业务中遇到这样的场景,多个项目有很多共用的逻辑,后面又是独立部署。例如PC端和移动端,UI完全不同,但逻辑有很多能复用,但是这里我们计划是分开部署。这里仅说有这种场景,大致痛点如下。
- 分两个项目,则代码维护两套,复用逻辑要copy
- 合成一个项目:路由区分或者多页配置。这种上线部署的时候又会互相影响,部署则都刷新。且目录在一起,可扒取到另外一个项目的资源。
为此,需要一种方式,能代码共仓以复用逻辑,但是打包部署可以独立。
实现
实现该需求,有如下考虑:
- 既然可独立打包,那这里考虑打包的时候传参
- 多项目在打包配置上可能存在差异,可针对不同项目的灵活配置是必要的
- 涉及到部署,那部署的情况也是要考虑到的
约定
为方便表述,这里做个约定,我们的项目分为mobile、pc两个。
打包传参的处理
命令行打包,做以下区分:
1 2 3 4 5 6 7 8 9 10 11
|
npm run dev -- --page=mobile
npm run build -- --page=mobile
npm run dev -- --page=pc
npm run build -- --page=pc
|
以上是参数的传入,接着解析参数,做成通用的,形如 --key=value
的都可以被解析。
这里我们将参数写成 VUE_APP_PAGE
的形式,这样项目代码逻辑中也可以使用这个参数,以便处理复用代码要区分不同项目的场景。
同时,这里实现getEnv
函数,以便在其他打包逻辑中可以获取到自己想要的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function handleEnv() { const reg = /--(\w+)=(.+)/; const keysArr = process.argv.filter( e => reg.test(e)); keysArr.forEach( varStr => { const varArr = varStr.match(reg); process.env[`VUE_APP_${varArr[1].toLocaleUpperCase()}`] = varArr[2]; }); }
const getEnv = (() => { let isHandleEnv = false; return (key) => { if (!isHandleEnv) { handleEnv(); isHandleEnv = true; } return key ? process.env[key] :process.env; } })();
|
以上已经拿到了参数,接下来是参数用起来
处理打包配置
这里总的逻辑还是使用了pages配置项,不过每次只打包了一个page。首先说明文件,为了方便自定义配置,这里我们拆分文件:
common.config.js
用来存储多个项目公用的配置
custom.config.js
用来存储不同项目自定义的配置
vue.config.js
合并common、custom的配置,生成最终的打包配置
common.config.js
的配置举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| module.exports = { publicPath: './', assetsDir: 'static', productionSourceMap: false, lintOnSave: true, devServer: { host: '0.0.0.0', port: 3000, hotOnly: true, proxy: { '/': { ws: true, target: 'http://test.myproject.com/', changeOrigin: true, secure: false, }, } }, configureWebpack: config => { }, chainWebpack(config) { config.resolve.alias .set('@pc', resolve('./src/pc')) .set('@mobile', resolve('./src/mobile')) .set('@src', resolve('./src')) } }
|
custom.config.js
的配置举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| module.exports = { mobile: { page: { title: 'mobile title' } }, pc: { page: { title: 'pc title' }, chainWebpack(config) { config.module .rule('svg') .exclude.add(resolve('src/platform/icons')) .end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/platform/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() } }, }
|
vue.config.js
的配置,这块是重点,会结合上面两个配置生成最终的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| const path = require('path'); const fs = require('fs');
const commonConfig = require('./common.config') const customConfig = require('./custom.config') const { getEnv } = require('./utils');
let pageName = getEnv('VUE_APP_PAGE');
const dirs = fs.readdirSync(path.join(__dirname, 'src/pages'))
if (!dirs.includes(pageName)) { console.warn(`${pageName} is not exist`); pageName = 'pc'; }
const { page, ...pageCustomConfig } = customConfig[pageName] || { page: {} };
const defaultPageConfig = { pages: { [pageName]: { entry: `src/${pageName}/main.js`, template: `public/${pageName}.html`, filename: 'index.html', ...page } }, outputDir: `dist/${pageName}` }
const config = { ...baseConfig, ...defaultPageConfig, }
for (const key in pageCustomConfig) { if (Object.hasOwnProperty.call(pageCustomConfig, key)) { const valueOrFunction = pageCustomConfig[key];
if (valueOrFunction instanceof Function) {
const baseFunction = config[key] || function() {}; config[key] = function() { const isReplaceBase = valueOrFunction.apply(null, arguments); !isReplaceBase && baseFunction.apply(null, arguments); } } else { config[key] = valueOrFunction; } } }
module.exports = config;
|
另外,有些情况可能仅在上面的自定义配置中无法完成,例如这里要修改postcss
的配置完成移动端rem的配置,可以修改postcss.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const { getEnv } = require('./utils')
const plugins = { autoprefixer: {}, }
if (getEnv('VUE_APP_PAGE') === 'mobile') { plugins['postcss-pxtorem'] = { rootValue: 16, propList: ['*'], unitPrecision: 5, } }
module.exports = { plugins }
|
package.json的修改
以上就完成了自定义的打包,但是本地运行还要每次传参就比较麻烦,改下package.json
让项目更好用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "scripts": { "dev": "vue-cli-service serve", "dev:collection": "npm run dev -- --page=mobile", "build": "vue-cli-service build", "build:all": "npm run build && npm run build:mobile", "build:pc": "vue-cli-service build", "build:mobile": "npm run build -- --page=mobile", } }
|
部署
完成以上打包之后,考虑下部署的事情。之前可能部署的方式是直接删除旧的前端包,部署新的。那现在情况不太一样了,pc
、mobile
都要部署则可以这么干,但是如果只是部署mobile
,那之前前端包中的pc
就不能删除,这里我们改写下部署的 bash
脚本。可能你不是bash
脚本,但是原理一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #!/bin/bash
PROJECT_DIR=my-test-project
BASE_DIR=/home/www
PROJECT_DIR_TEMP=${BASE_DIR}/${PROJECT_DIR}-temp
PROJECT_ROOT_DIR=${BASE_DIR}/${PROJECT_DIR}
if [ ! -d ${PROJECT_ROOT_DIR} ];then mkdir -p ${PROJECT_ROOT_DIR} fi
if [ ! -d ${PROJECT_DIR_TEMP} ];then mkdir -p ${PROJECT_DIR_TEMP} fi
cp ${PROJECT_DIR}.zip ${PROJECT_DIR_TEMP} cd ${PROJECT_DIR_TEMP} unzip ${PROJECT_DIR}.zip rm ${PROJECT_DIR}.zip
for file in $PROJECT_DIR_TEMP/* do if [ -d "$file" ] then page_name=${file##*/} echo $page_name
if [ -d "$PROJECT_ROOT_DIR/$page_name" ] then rm -r "$PROJECT_ROOT_DIR/$page_name" fi fi done
mv $PROJECT_DIR_TEMP/* $PROJECT_ROOT_DIR
rm -r $PROJECT_DIR_TEMP
|