最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 微前端的生产实践和我的使用姿势

    正文概述 掘金(菜鸡H)   2021-01-16   742

    前言

    什么是微前端

    自己在服务器部署的一个微前端demo

    1、技术栈无关

    2、主框架不限制接入应用的技术栈,微应用具备完全自主权

    3、独立开发、独立部署

    4、微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

    需求分析

    • 除了这些、从项目的需求和系统的数量微前端非常适合我们、我们一共有七个系统、每个用户因为角色权限、所管理的系统也是不一样的、张三负责两个系统权限、李四负责一个系统、也可能王五负责一个系统的其中某几个菜单权限等...如果全部系统写到一个项目可想而至...代码量...项目维护..性能.都是非常难折腾!下面放一张我们的系统UI图

    微前端的生产实践和我的使用姿势 由于那啥所以打了码、顶部是所有系统、左侧是当前系统的菜单栏、从UI的设计图上看这个项目是很适合微前端!后面我会用基座(微前端环境)和子应用与主应用去介绍我的踩坑之路-?

    技术选型与项目的整体规划

    • vue、element、webpack、websocket、eslint、babel、qiankun2.0
    • 支持子应用独立运行和可运行在微前端基座方式
    • 主应用使用cdn统一管理公共静态资源,所有子应用运行在基座上时共享此静态资源,大幅减小子应用体积,减少带宽消耗,减少重复资源消耗,大幅加快项目加载速度
    • 应用与应用之前可进行通信和跳转
    • 应用独立维护、互不依赖不耦合
    • 项目拆分但和单体的开发模式应该是差不多的、比如启动、打包、安装、依赖、部署(一键模式)
    • 编写一键部署脚本、先部署至测试服务器、测试通过直接发行到生产环境
    • 项目状态、公共数据的维护

    主应用环境搭建(基座)

    • 主应用(vue脚手架搭建)、因为打算核心公共模块采用cdn方式、所以脚手架选择 : Default ([Vue 2] babel, eslint) 主应用需要做的事情是: 对qiankun框架单独模块化封装导出核心方法、配置cdn方式加载核心模块 、配置 eslint 忽略指定全局变量、配置webpack的externals排除某些依赖,使用 cdn 资源代替

    开始

    vue create main-app 
    

    脚手架好了之后我们需要安装 qiankun

    yarn add qiankun
    
    • 采用cdn方式去加载公共核心模块比如vue、vuex、vue-route..等、这样做的目的是让子应用去使用主应用加载好的公共模块、同时减少项目打包体积大小!所以我们需要在main.js里的 import Vue from 'vue' 进行删除、其他的vue-router、vuex、Axios也都是一样的操作统一不使用 node_modules的依赖,然后在主应用的 public的index.html、引入公共模块(下方的js) 下面是我自己玩demo的时候储存在我的对象服务器的常用公共文件、建议下载到自己本地玩
    <script src="https://gf-cdn.oss-cn-beijing.aliyuncs.com/vue/vue.js"></script>
    <script src="https://gf-cdn.oss-cn-beijing.aliyuncs.com/vue/vue-router.js"></script>
    <script src="https://gf-cdn.oss-cn-beijing.aliyuncs.com/vue/vuex.js"></script></head>
    <script src="https://gf-cdn.oss-cn-beijing.aliyuncs.com/axios/axios.min.js"></script>
    <link rel="stylesheet" href="https://gf-cdn.oss-cn-beijing.aliyuncs.com/element/index.css">
    <script src="https://gf-cdn.oss-cn-beijing.aliyuncs.com/element/index.js"></script>
    

    因为使用了 eslint 原因检测到没有引入vue、所以我们要全局配置忽略我们通过cdn方式引入的模块、在 .eslintrc.js 添加一个 globals 忽略检测的全局变量

    globals: {
       "Vue": true,
       "Vuex": true,
       "VueRouter": true,
       'axios':true
     }
    

    配置了这个只是把代码校验忽略检测某些变量、我们还需要配置下 webpack 的 externals externals介绍、简单来讲就是 打包的时候排除某些依赖,使用 cdn 资源代替 在vue.config.js里面配置

    module.exports = {
      publicPath: '/',
      outputDir: 'app',
      assetsDir: 'static', 
      ......
      configureWebpack: {
            externals: {
                'element-ui':'ELEMENT',
                'vue':'Vue',
                'vue-router':'VueRouter',
                'vuex': 'Vuex',
                'axios':'axios'
            }
        }
     }
    

    这样我们的cdn方式加载核心模块就好了、接下来就是配置 qiankun

    • quankun配置

      • src里面新建一个core文件夹、分别创建 app.config.js(管理子应用的注册信息)qiankun.js(这里统一导出启动qiankun的方法) 还有 app.store.js(管理qiankun的通信方法)

    app.config.js

    const apps = [
        {
          name: "subapp-sys", //微应用的名称
          defaultRegister: true, //默认注册
          devEntry: "http://localhost:6002",//开发环境地址
          depEntry: "http://108.54.70.48:6002",//生产环境地址
          routerBase: "/sys", //激活规则路径
          data: []  //传入给子应用的数据
        },
    ]
    export default apps;
    

    qiankun.js

    import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from "qiankun";
    const appContainer = "#subapp-viewport"; //加载子应用的dom
    import appStore from './app.store' 
    const quanKunStart = ( list ) =>{
        let apps = [];      //子应用数组盒子
        let defaultApp = null; // 默认注册应用路由前缀
        let isDev = process.env.NODE_ENV === 'development';
        list.forEach( i => { 
            apps.push({
                name: i.name, //微应用的名称
                entry: isDev ? i.devEntry : i.depEntry, //微应用的 entry 地址
                container: appContainer, //微应用的容器节点的选择器或者 Element 实例
                activeRule: i.routerBase, //微应用的激活规则路径 /login/xxx /sys/xxx
                props: { routes: i.data, routerBase: i.routerBase } //子应用初次挂载传入给子应用的数据
            })
            //初始化第一个加载的应用
            if (i.defaultRegister) defaultApp = i.routerBase;
        });
        //qiankun路由配置
        registerMicroApps(
            apps,
            {
                beforeLoad: [
                    app => {
                        console.log('[主应用生命周期] before', app.name);
                    },
                ],
                beforeMount: [
                    app => {
                        console.log('[主应用生命周期] before', app.name);
                    },
                ],
                afterUnmount: [
                    app => {
                        console.log('[主应用生命周期] after', app.name);
                    },
                ]
            },
        )
        //默认加载第一个子应用
        setDefaultMountApp( defaultApp );
        //启动微前端
        start();
        //第一个微应用 mount 后需要调用的方法
        runAfterFirstMounted(() => { console.log( defaultApp +'--->子应用开启成功' ) });
        //启动qiankun通信机制
        appStore( initGlobalState );
    }
    
    export default quanKunStart;
    
    

    app.store.js

    let DISPATCHAPPLYMESSAGE = null;
    let GETAPPLYMESSAGE = null;
    const appStore = ( initGlobalState ) => {
        //定义应用之间所接收的key、不然主应用不接收数据
        const initialState = {  
            data: '给子应用的测试数据',
            token: '',
            appsRefresh: false,
        };
        const { onGlobalStateChange, setGlobalState } = initGlobalState( initialState );
        dispatchApplyMessage = setGlobalState;
        getApplyMessage = onGlobalStateChange;
    }
    //导出应用通信方法
    export {
        DISPATCHAPPLYMESSAGE,
        GETAPPLYMESSAGE
    }
    export default appStore;
    
    • 在主应用的App.vue添加子应用的渲染区域
    <template>
        <div class="home-container">
            <p>主应用内容</p>
            <div class="page-conten">
                <!-- 子应用渲染区 -->
                <div id="subapp-viewport" class="app-view-box"></div>
            </div>
        </div>
    </template>
    

    main.js

    import App from './App.vue'
    Vue.config.productionTip = false
    
    import Apps from './core/app.config'
    import qianKunStart from './core/qiankun'
    qianKunStart(Apps)
    
    new Vue({
      render: h => h(App),
    }).$mount('#app')
    

    子应用环境

    • 第一步使用官方脚手架把项目创建好、和主应用同理选择默认的 Default ([Vue 2] babel 进行创建项目
     vue create subapp-sys
    
    • 第二步我们改造下脚手架默认的模块和打包后的格式配置、还有给qiankun导出对应的生命周期函数 修改打包配置 - vue.config.js
    const { name } = require('./package');
    module.exports = {
      devServer: {
        hot: true,
        disableHostCheck: true,
        port:6002,
        overlay: {
            warnings: false,
            errors: true,
        },
        headers: {
            'Access-Control-Allow-Origin': '*',
        },
        //防止单体项目刷新后404
        historyApiFallback:true,
    },
      configureWebpack: {
        output: {
          library: `${name}-[name]`,
          libraryTarget: 'umd',// 把微应用打包成 umd 库格式
          jsonpFunction: `webpackJsonp_${name}`,
        },
      },
    };
    
    • 第三步同src下创建一个导出qiankun的js文件、统一管理,名叫 life-cycle.js
    import App from "./App.vue";
    import store from "./store";
    import selfRoutes from "./router";
    //导入官方通信方法 和 主应用的一样把应用通信封装到一个js文件独立管理
    import appStore from "./utils/app-store";
    
    const __qiankun__ = window.__POWERED_BY_QIANKUN__;
    let router = null;
    let instance = null;
    
    /**
     * @name 导出qiankun生命周期函数
     */
    const lifeCycle = () => {
      return {
        async bootstrap() {},
        //应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
        async mount( props ) {
            // 注册应用间通信
            appStore(props);
            // 注册微应用实例化函数
            render(props);
        },
        //微应用卸载
        async unmount() {
            instance.$destroy?.();
            instance = null;
            router = null;
        },
      //主应用手动更新微应用
        async update(props) {
            console.log("update props", props);
        }
      };
    };
    
    //子应用实例化函数 routerBase container是通过主应用props传入过来的数据
    const render = ({ routerBase, container } = {}) => {
        Vue.config.productionTip = false;
        router = new VueRouter({
            base: __qiankun__ ? routerBase : "/",
            mode: "history",
            routes: selfRoutes
        });
        instance = new Vue({
            router,
            store,
            render: h => h(App)
        }).$mount(container ? container.querySelector("#sys") : "#sys");
    };
    
    export { lifeCycle, render };
    
    • 第四步 接下来src目录新增 public-path.js
    if (window.__POWERED_BY_QIANKUN__) {
        __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
    • 最后main.js 引入封装
    import "./public-path";
    import { lifeCycle, render } from "./life-cycle";
    /**
    * @name 导出微应用生命周期
    */
    const { bootstrap, mount, unmount } = lifeCycle();
    export { bootstrap, mount, unmount };
    
    /**
    * @name 不在微前端基座独立运行
    */
    const __qiankun__ = window.__POWERED_BY_QIANKUN__;
    __qiankun__ || render();
    

    我的整个项目结构(使用了vue + react + qiankun)

    微前端的生产实践和我的使用姿势

    遇到的问题

    qiankun 环境搭建好了,接下来 分别 进入主应用和子应用启动项目 yarn serve 然后访问主应用、没有问题的话应该两个项目的页面都出来了、接下来我说下我做集成时候遇到的问题

    微前端的生产实践和我的使用姿势

    微前端的生产实践和我的使用姿势 配置主应用vue.config.js的devServer为proxy添加一个函数绕过代理、浏览器请求,希望返回的是HTML页面 微前端的生产实践和我的使用姿势

    微前端的生产实践和我的使用姿势

    微前端的生产实践和我的使用姿势

    编写应用指令脚本一键式 [ 启动、依赖安装、打包 】

    • 第一步在整个应用项目下生成一个package.json配置scripts脚本文件
    yarn init //然后按提示执行下去
    
    • 添加scripts脚本配置 下面是定一个start指令然后去执行config下的start.js
    "scripts": {
      "start":"node config/start.js"
    }
    
    • 第二步在、我们需要在package.json同级下创建一个config文件夹、同时往文件里面添加一个start.js
    mkdir config
    cd config 
    touch start.js
    
    • 第三步往start.js随便输出一个console.log('yarn serve'),然后在整个项目启动终端执行一下 yarn start 正常输出 yarn serve 、然后我们需要开始编写一键启动脚本 需求就是执行脚本、脚本自动帮我们在每个项目中去执行 yarn serve

    • start.js

    const fs = require('fs');
    const path = require('path');
    const util = require('util');
    const sub_app_ath = path.resolve();
    const sub_apps = fs.readdirSync(sub_app_ath).filter(i => /^sub|main/.test(i));
    console.log('\033[42;30m 启动中 \033[40;32m 即将进入所有模块并启动服务:' + JSON.stringify(sub_apps) + 'ing...\033[0m')
    const exec = util.promisify( require('child_process').exec );
    async function start() {
    sub_apps.forEach( file_name => {
      exec('yarn serve', { cwd: path.resolve( file_name )});
    });
    };
    start();
    setTimeout( () =>{
    console.log('\033[42;30m 访问 \033[40;32m http://localhost:6001 \033[0m')
    },5000)
    
    

    先通过正则读取到主应用和子应用文件夹名称、然后使用 child_process模块异步创建子进程 通过这个返回的方法我们可以去执行一个 指令 并且传入一个在那执行的路径 util.promisify把方法封装成promise返回形式

    好啦、目前一键启动就写完了、其他的都是一样的操作、只是创建的文件夹和scripts的脚本命令改下、哦对还有exec下的指令换成对应的 剩下就是执行shell脚本进行服务器上传部署

    shell脚本完成自动打包和上传至服务器、关于shell语法大家可以看 菜鸟shell教程

    • 在整体项目下新建一个 deploy.sh 文件

    deploy.sh

    set -e
    shFilePath=$(cd `dirname $0`; pwd)
    # 系统列表名称
    sysList=('app' 'car' 'login' 'sys' 'user' 'all')
    IP="106.54.xx.xx"
    uploadPath="/gf_docker/nginx/web"
    #获取当前分支
    branch=$(git symbolic-ref --short HEAD)
    #开始
    echo "\033[35m 当前分支是:${branch} \033[0m"
    read -p $'\033[36m 准备进行自动化部署操作、是否继续 y or n  \033[0m ' isbuild
    if [ "$isbuild" != 'y' ];then
        exit
    fi
    echo "\033[36m 目前四个个系统 \033[0m \033[35m【 ${sysList[*]} 】 \033[0m "
    read -p $'\033[36m 请选择部署的项目 或 输入 all \033[0m' changeSysName
    isSys=$(echo "${sysList[@]}" | grep -wq "${changeSysName}" &&  echo "yes" || echo "no")
    #是否存在系统
    if [ "$isSys" == 'no' ];then
        echo "\033[31m 没有对应的系统、已退出 \033[0m"
        exit
    fi
    
    #没有buildFile文件夹的话就新建一个
    if [ -d "$shFilePath/buildFile" ]; then
        rm -rf './buildFile/'
        mkdir "buildFile"  
    else
        mkdir "buildFile" 
    fi;
    
    #项目文件夹名称
    fileName=""
    #打包
    function build() {
        cd $1
        echo "\033[32m $1准备打包... \033[0m" 
        yarn build 
        echo $1/$2
        mv $shFilePath/$1/$2 $shFilePath/buildFile
        echo "\033[32m $1打包成功、包移动至buildFile \033[0m" 
    }
    #上传服务器
    function uploadServe() {
        echo "\033[32m 准备上传服务器,地址:$uploadPath \033[0m"
        rsync -a -e "ssh -p 22" $shFilePath/buildFile*  root@$IP:$uploadPath
        echo "\033[32m 自动化部署成功! \033[0m"
    }
    #单个项目部署文件名转换
    function getFileName() {
        case $1 in
            'app')
                fileName="main-app";;
            'car')
                fileName="subapp-car";;
            'login')
                fileName="subapp-login";;
            'sys')
                fileName="subapp-sys";;
            'user')
                fileName="subapp-user";;
            *)
                echo "error"
        esac
    }
    #按需打包
    if [ "$changeSysName" == 'all' ];then
        for i in "${sysList[@]}"; do
            if [ "$i" != 'app' ];then
                cd ..
            fi
            if [ "$i" != 'all' ];then
                getFileName $i
                build $fileName $i
            fi
        done
    else
        getFileName $changeSysName
        build $fileName $changeSysName
    fi
    
    #部署
    uploadServe 
    

    语法和菜鸟现学的、也只是代替双手进行一系列的操作、上传服务器的时候需要输入下密码、如果不想输入可在服务端配置密钥、类似git一样!

    最后


    起源地下载网 » 微前端的生产实践和我的使用姿势

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元