Vue3+Ant Design Vue+Python实现一套代码多个子应用的组合部署

682 阅读7分钟

写在前面的话 

此文主要解决的是:如何在不同服务器上面部署时,打包编译不同的src文件夹(文件),达到根据一套代码根据项目需求部署不同内容。

使用的技术栈:Vue3、Ant Design Vue、Python(主要部署时使用到,非必须)。

原理:利用python和shell命令按需复制文件,组合成我们项目部署时需要的文件结构。

本文不会对Vue3和Ant Design Vue做细节上面的阐述,如有需要,可以给作者留言。

本文默认你已经知道并了解Vue的相关基础知识。

用在哪里?

当你的项目需要根据不同的客户需求部署不同的子应用时,可能会用到此部署方式。

有个疑惑:为什么不是iframe或者乾坤?

前端微服务确实可以解决上诉需求,并且也有良好的用户体验,之所有没有使用微服务,主要原因如下:

  1. 乾坤文档明确说明:以 vue-cli 3+ 生成的 vue 2.x 项目为例,vue 3 版本等稳定后再补充。
  2. 子应用的高度UI集成和逻辑复用。
  3. 前端将占用大量的端口。
  4. 代码的统一管理。

如果大家对前端微服务感兴趣,可以移步文档查阅。

目录结构

为了Python在自动部署时能够更好的处理文件,对目录结构的统一性要求比较高。

比如我们项目中有3个子系统(下文所有的例子都围绕这三个子应用展开),分别为demo1,demo2,demo3,那我们的项目结构正确的是:

目录1.png

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文件: 微信截图_20211215145911.png

大体流程:在项目根创建一个export文件夹,用来存在按需复制过后的所有文件,然后在export文件夹npm run build 输出dist文件(我们公司前端部署是全自动化,用的jenkinsdocker,结合此流程可以使前端部署一键化)

具体流程:

  1. 先判断根是否有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文件

总结

开源不易,转载请注明来源。