写在前面的话
此文主要解决的是:如何在不同服务器
上面部署时,打包编译不同的src文件夹(文件)
,达到根据一套代码根据项目需求部署不同内容。
使用的技术栈:Vue3
、Ant Design Vue、Python(主要部署时使用到,非必须)。
原理:利用python和shell命令按需复制文件,组合成我们项目部署时需要的文件结构。
本文不会对Vue3和Ant Design Vue做细节上面的阐述,如有需要,可以给作者留言。
本文默认你已经知道并了解Vue的相关基础知识。
用在哪里?
当你的项目需要根据不同的客户需求部署不同的子应用时,可能会用到此部署方式。
有个疑惑:为什么不是iframe
或者乾坤?
前端微服务
确实可以解决上诉需求,并且也有良好的用户体验,之所有没有使用微服务,主要原因如下:
- 乾坤文档明确说明:以
vue-cli 3+
生成的vue 2.x
项目为例,vue 3
版本等稳定后再补充。 - 子应用的高度UI集成和逻辑复用。
- 前端将占用大量的端口。
- 代码的统一管理。
如果大家对前端微服务
感兴趣,可以移步文档查阅。
目录结构
为了Python
在自动部署时能够更好的处理文件,对目录结构的统一性要求比较高。
比如我们项目中有3个子系统(下文所有的例子都围绕这三个子应用展开),分别为demo1,demo2,demo3,那我们的项目结构正确的是:
public
这里面有一个重要的配置文件
:
public/config/global.js
var globalConfig = {
/**
* 部署模式,默认full
*/
deployName: '',
}
window.globalConfig = globalConfig
然后把此文件在index.html中引入
把它挂载到window的意义在于每个页面都可以使用window拿到值,接下来会说明此文件的作用。
api
如上图,我们会把每个子应用用到的接口列表分别放置于不用的文件夹中,并且都有一个叫index.ts
的文件(稍后会说明为什么要这样命名)
api
文件夹根下面有一个index.ts是axios的相应拦截全局配置,这里就不做展开,这里有疑问的可以自行百度。
components
此文件夹主要是不同的子应用用到的一些公用的组件。这里的命名没有特别要求。
layouts
此文件夹主要是不同的子应用用到的一些布局方式。这里的命名没有特别要求。
router
如上图,我们会把每个子应用的路由分别放置于不同的文件夹中,并且都有一个叫index.ts
的文件(稍后会说明为什么要这样命名)
store
此文件夹主要是不同的子应用用到的vuex。这里的命名没有特别要求。
utils
此文件夹主要是不同的子应用用到的公共方法。这里的命名没有特别要求。
views
如上图,我们会把每个子应用的页面分别放置于不同的文件夹中,views下面的根文件夹的命名应该和api,router保持一致(稍后会说明为什么要这样命名)
以上就是所有文件夹的一些命名规则,之所有api,router下面都有一个文件夹叫做index.ts
,请看下面代码:
router/demo1/index.ts
export const demo1: any = [
{
path: '/demo1-index',
name: 'demo1-index',
redirect: '/base-index/index',
meta: {
title: 'demo1',
},
component: () => import('@/layouts/content.vue'),
children: [
{
path: '/demo1-index/index',
name: 'base-index-index',
meta: {},
component: () => import('@/views/demo1/index.vue'),
},
],
},
]
router/subImport.js
// 全应用部署
const fullFn = () => {
return {
demo1: require('./demo1').demo1,
demo2: require('./demo2').demo2,
demo3: require('./demo3').demo3,
}
}
// 部分子应用部署(比如部署demo1,demo2)
const bufenFn = () => {
return {
demo1: require('./demo1').demo1,
demo2: require('./demo2').demo2,
}
}
const result = []
switch (window.globalConfig.deployName) {
case 'bufen':
for (const value of Object.values(bufenFn())) {
if (value) {
result.push(...value)
}
}
break;
default:
for (const value of Object.values(fullFn())) {
if (value) {
result.push(...value)
}
}
break;
}
export default result
之所有文件名都统一命名成index.ts,是因为可以简写,如果命名不一致,那么接下来的python
动态新增文件会很难
处理
Python处理文件
下图是python下面所需要的py文件:
大体流程:在项目根创建一个export
文件夹,用来存在按需复制过后的所有文件,然后在export文件夹npm run build
输出dist文件(我们公司前端部署是全自动化,用的jenkins
和docker
,结合此流程可以使前端部署一键化)
具体流程:
- 先判断根是否有export文件夹,没有则创建,有则先执行删除在执行创建,保证export文件夹在复制文件之前是干净的
python/index.py
#!/usr/bin/python
import os
import utils
import func
def startCopy():
utils.del_file("export")
func.copyConfigs()
if os.path.exists("export"):
startCopy()
else:
os.makedirs("export")
startCopy()
del_file
方法是自定义的一个删除文件和文件夹的方法
copyConfigs
是具体执行复制文件的方法
2. 执行copyConfigs
方法,复制文件。最核心的一步
在复制之前,我们要根据我们的项目要求,配置copyConfigs
方法所需要的参数
python/configs.py
import sys
# 公共配置项
basecfg = {
"deployment": sys.argv[1]
}
# 公共文件
pub = {
"rootSrc": (
"App.vue",
"main.ts",
"shims-vue.d.ts"
),
"allRouter": (
"demo1",
"demo2",
"demo3","
)
}
# 部署模式1 比如只部署demo1应用
demo1 = {
"router": (
"index.ts",
"subImport.js"
"demo1",
),
"public": (
"config",
"index.html",
"favicon.ico",
),
"rootSrc": (
"App.vue",
"main.ts",
"shims-vue.d.ts"
),
"views": (
"demo1",
),
"rootFiles": (
"vue.config.js",
"yarn.lock",
"tsconfig.json",
"package.json",
"babel.config.js",
".prettierrc",
".gitignore",
".eslintrc.js",
".eslintignore",
".browserslistrc"
)
}
sys.argv[1]
允许执行python文件时传入固定的部署模式。比如:python3 python/index.py demo1
rootSrc
文件夹则是与src同级的根文件,一般而言不会发生变化,当我们准备好这个部署配置文件过后,我们就可以开始复制文件了:
python/func.py
#!/usr/bin/python
import os
import shutil
from configs import lsbz
from configs import dy_edz
from configs import basecfg
from configs import pub
import utils
# 以部署demo1应用为例:
def copyConfigs():
print("views复制开始")
os.makedirs("export/src/views")
for i in os.listdir("./src/views/"):
if i in demo1['views']:
os.system('cp -rf ./src/views/'+ i +' export/src/views')
print("views复制结束")
print("public复制开始")
os.makedirs("export/public")
for i in os.listdir("./public/"):
if i in demo1['public']:
os.system('cp -rf ./public/'+ i +' export/public')
print("public复制开始")
print("复制根目录文件开始")
ls = os.listdir("./")
for i in ls:
if i in demo1['rootFiles']:
shutil.copy('./' + i, "export")
print("复制根目录文件结束")
print("router复制开始")
os.makedirs("export/src/router")
for i in os.listdir("./src/router/"):
if i in demo1['router']:
os.system('cp -rf ./src/router/'+ i +' export/src/router')
for i in pub['allRouter']:
if i in demo1['router']:
print('忽略')
else:
os.makedirs("export/src/router/" + i)
open('export/src/router/' + i + '/index.ts', 'w')
print("router复制结束")
print("layouts复制开始")
os.makedirs("export/src/layouts")
os.system('cp -rf ./src/layouts/* ./export/src/layouts')
print("layouts复制结束")
print("api复制开始")
os.makedirs("export/src/api")
os.system('cp -rf ./src/api/* ./export/src/api')
print("api复制结束")
print("assets复制开始")
os.makedirs("export/src/assets")
os.system('cp -rf ./src/assets/* ./export/src/assets')
print("assets复制结束")
print("components复制开始")
os.makedirs("export/src/components")
os.system('cp -rf ./src/components/* ./export/src/components')
print("components复制结束")
print("hooks复制开始")
os.makedirs("export/src/hooks")
os.system('cp -rf ./src/hooks/* ./export/src/hooks')
print("hooks复制结束")
print("store复制开始")
os.makedirs("export/src/store")
os.system('cp -rf ./src/store/* ./export/src/store')
print("store复制结束")
print("utils复制开始")
os.makedirs("export/src/utils")
os.system('cp -rf ./src/utils/* ./export/src/utils')
print("utils复制结束")
print("src根文件复制开始")
for i in os.listdir("./src/"):
if i in pub['rootSrc']:
os.system('cp -rf ./src/'+ i +' export/src')
print("src根文件复制结束")
我们知道,在router/subImport.js中有这样一串代码:
// 全应用部署
const fullFn = () => {
return {
demo1: require('./demo1').demo1,
demo2: require('./demo2').demo2,
demo3: require('./demo3').demo3,
}
}
当我们只拷贝了demo1应用时,router下面只会有一个文件夹demo1
,其他子应用的路由不会被复制进来。这样的话上面代码在执行的时候会报错(require('./demo2')
文件找不到)
为了解决这个问题,我们就要利用configs.py中的allRouter
中给所有的路由加上一个默认的文件夹,并且有一个空文件index.ts
(现在知道为什么router的命名是这样了吧)
print("router复制开始")
os.makedirs("export/src/router")
for i in os.listdir("./src/router/"):
if i in demo1['router']:
os.system('cp -rf ./src/router/'+ i +' export/src/router')
for i in pub['allRouter']:
if i in demo1['router']:
print('忽略')
else:
os.makedirs("export/src/router/" + i)
open('export/src/router/' + i + '/index.ts', 'w')
print("router复制结束")
当所有的文件准备好过后我们可以让python帮我们执行一个shell脚本:
demo1.sh
#!/bin/bash
yarn install
yarn run build
# 这里还可以配置自动部署工具做相应的事,没有的话,执行完此文件过后会生成dist文件夹。手动拷贝出来即可。
if basecfg['deployment'] == 'demo1':
os.chdir('export')
os.system('sh ./demo1.sh')
让python进入到export文件夹,执行里面的demo1.sh
文件
总结
开源不易,转载请注明来源。