• 微前端框架 single-spa


    单体应用对比前端微服务化

    普通的前端单体应用

    微前端架构

    1.基本概念

    实现一套微前端架构,可以把其分成四部分(参考:https://alili.tech/archive/11052bf4/)

    加载器:也就是微前端架构的核心,主要用来调度子应用,决定何时展示哪个子应用, 可以把它理解成电源。

    包装器:有了加载器,可以把现有的应用包装,使得加载器可以使用它们,它相当于电源适配器。

    主应用:一般是包含所有子应用公共部分的项目—— 它相当于电器底座

    子应用:众多展示在主应用内容区的应用—— 它相当于你要使用的电器

    所以是这么个概念:电源(加载器)→电源适配器(包装器)→️电器底座(主应用)→️电器(子应用)️

    总的来说是这样一个流程:用户访问index.html后,浏览器运行加载器的js文件,加载器去配置文件,然后注册配置文件中配置的各个子应用后,首先加载主应用(菜单等),再通过路由判定,动态远程加载子应用。

    2.预备知识

    2.1 SystemJs

    SystemJS提供通用的模块导入途径,支持传统模块和ES6的模块。

    SystemJs有两个版本,6.x版本是在浏览器中使用的,0.21版本的是在浏览器和node环境中使用的,两者的使用方式不同。(参考:https://github.com/systemjs/systemjs)

    在微服务中主要充当加载器的角色。

    2.2 singleSpa

    single-spa是一个在前端应用程序中将多个javascript应用集合在一起的框架。主要充当包装器的角色。(参考:https://single-spa.js.org/docs/getting-started-overview.html

    3.微服务实践

    3.1 创建应用

    首先创建一个主应用iframe,这个主应用只需要简单的起一个服务访问静态资源即可。

    用npm init初始化,创建一个index.html,简单写个hello world,安装依赖 npm i serve --s。

    在package.json中的scripts中增加启动命令"serve": "serve -s -l 7000"。运行后可以看到hello world。

    然后在创建3个子应用,我用的是vue-cli2.0,分别创建navbar应用(用来写路由),program1(应用1),program2(应用2)。

    navbar应用中只放两个链接就好了,如图:

      

     子应用program1和program2的路由都相应的加上项目名称前缀,都增加一个about路由来作为子应用的路由切换,大致如下:

    3.2 改造子应用

    首先包装子应用,各个子应用都需要安装依赖 npm i single-spa-vue systemjs-webpack-interop,修改入口文件main.js如下:

    其中 single-spa-vue是针对vue项目的包装器,systemjs-webpack-interop是社区维护的npm库,它可以帮助您使webpack和systemjs一起正常工作。 

    除此之外还需要在webpack配置中的output中增加设置

     3.3 修改主应用文件

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <meta name="importmap-type" content="systemjs-importmap">
        <script type="systemjs-importmap">
            {
              "imports": {
                "navbar": "http://localhost:8080/app.js",
                "program1": "http://localhost:8081/app.js",
                "program2": "http://localhost:8082/app.js",
                "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js"
              }
            }
          </script>
        <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" />
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-register.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script>
    </head>
    <body>
        <script>
            (function(){
                System.import('single-spa')
                .then((res)=>{
                    var singleSpa=res;
                
                    singleSpa.registerApplication('navbar',()=>System.import('navbar'),location=>true);
    
                    singleSpa.registerApplication('program1',()=>System.import('program1'),(location)=>{
                        return location.hash.startsWith(`#/program1`);
                    });
    
                    singleSpa.registerApplication('program2',()=>System.import('program2'),(location)=>{
                        return location.hash.startsWith(`#/program2`);
                    });
    
                    singleSpa.start();
                })
            })()
        </script>
    </body>
    </html>
    复制代码
    registerApplication函数包含四个参数,
    appName: 注册的应用名称;
    applicationOrLoadingFn:应用入口文件(必须是一个函数,返回一个函数或者一个promise);
    activityFn:控制应用是否激活的函数(必须是一个纯函数,接受window.location作为参数,返回一个boolean);
    customProps:在包装器生命周期函数中传递给子应用的props(应该是一个对象,可选)。
     
    start函数必须在子应用加载完后才能调用。在调用之前,子应用已经加载了只是未被渲染。
     
    3.4 运行项目
    分别运行navbar,program1,progarm2项目,然后运行iframe项目。iframe项目运行在7000端口,其他子应用分别运行在8080,8081,8082端口。从7000端口去请求其他端口的入口文件会跨域,所以在子应用中增加跨域设置。
    在子应用的webpack.dev.config.js中找到devSever配置项,增加headers:{"Access-Control-Allow-Origin":"*"}配置
    然后重新运行项目即可。
     

     4.项目整合

    以上只是在开发环境中使用,接下来尝试不分别启动服务,只启用一个服务来跑项目。大体思路是使用express搭建一个服务,将子应用全部打包到项目上作为静态资源访问,入口html使用ejs模板来实现项目配置,而不再写死。

    4.1 使用express生成器生成项目

    4.2 修改子应用打包配置

     这样子应用就全部打包到express应用中作为静态资源使用了。

    4.3 增加应用配置文件

    将iframe的index.html的内容复制到express的入口ejs中。增加配置文件apps.config.json和apps.config.js。

     apps.config.js作用就是根据apps.config.json在静态资源文件夹下生成一份新的配置文件,将配置文件中的资源名称通过正则匹配成完整的资源路径。并且监听文件变化来更新静态资源文件夹下的配置文件。

    生成的配置文件

    4.4根据这个生成的配置文件去修改ejs,将项目注册过程循环出来,而不再是写死的。 

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <meta name="importmap-type" content="systemjs-importmap">
        <script type="systemjs-importmap">
            {
              "imports": {
                <%for(var i=0;i<apps.length;i++){%>
                  "<%= apps[i].name %>":"<%= apps[i].server %><%=apps[i].resourceEntryUrl %>",
                <%}%>
                "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js"
              }
            }
          </script>
        <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" />
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-register.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script>
    </head>
    <body>
        <script>
            (function(){
                Promise.all([
                    System.import('single-spa'),
                    System.import('./apps.config.json')
                ])
                .then((res)=>{
                    var singleSpa=res[0];
                    var configs=res[1].default;
                
                    configs.apps.forEach( project => {
                      if(project.resource.length>0){
                        Promise.all(project.resource.map(i=>{
                          return System.import(project.server+i)
                        })).then(function(){
                          singleSpa.registerApplication(project.name,()=>System.import(project.name),(location)=>{
                              return project.base?true:location.hash.startsWith(`#/${project.name}`);
                          });
                        })
                      }else{
                        singleSpa.registerApplication(project.name,()=>System.import(project.name),(location)=>{
                            return project.base?true:location.hash.startsWith(`#/${project.name}`);
                        });
                      }
                    });
    
                    singleSpa.start();
                })
            })()
        </script>
    </body>
    </html>
    复制代码

    实际渲染出来是

     4.5 优化打包配置

    因为三个子项目都用到了相同的一部分依赖,可以考虑将公用的依赖不打包进去,改为在主项目主引入来提高打包效率

    修改子应用的webpack.base.config.js,增加配置项

     在主应用中引入依赖

     4.5 增加react应用

    同样是使用webpack打包,不同是包装器不一样。其他基本上是一样的思路即可。

     最终demo

    转载 https://www.cnblogs.com/scdisplay/p/11648701.html#4391495

  • 相关阅读:
    2020-05-12 Linux基本操作
    SpringBoot项目设置能访问静态资源,resource/static目录下文件
    2020-04-25 Hadoop框架学习
    2020-05-24 vue简单语法
    2020-04-25 elasticsearch
    2020-04-25 kafka
    2020-04-11 函数式数据处理(Java8)
    2020-03-29 分布式事务解决方案(RocketMQ)
    配置文件示例
    Spring-data-redis实现消息队列的demo(附源码)
  • 原文地址:https://www.cnblogs.com/Tohold/p/11850853.html
Copyright © 2020-2023  润新知