zoukankan      html  css  js  c++  java
  • 微前端应用解决方案

    在前后台分离开发模式大行其道的今天,前端也形成了自己的一套工程体系,随着业务的不同,前端也诞生了很多相应的解决方案,那么我们在开发初期因该如何选择呢,我们来回顾常用应用有哪些。(本文只是自己得理解,

    有理解错得地方希望老鸟帮忙指点一二)

    SPA,单页面应用

    单页面应用做为各种解决方案得基础,不得不说得力于webpack大行其道,webpack通过入口将所有彼此依赖得文件打成一个网,最终输出到磁盘中,index.html只关心最终输出文件,当然这里涉及到更核心得概念就是模块化编程,

    比如amd,cmd,commonjs,es module等等这里就做阐述了。作为一个前端,我们很容易可以创建一个单页面应用。然而随着一个项目需求变得越来越多,项目体积变得越来越大得时候,单页面应用得弊端也渐渐得暴漏出来,

    最大直观问题就是文件加载过大导致页面性能下降,到这里你会说,我可以做按需加载,可以uglify,可以tree shaking,可以提取公共文件等等,当然这些都是解决方案,那么如何可以更好得解决这个问题,是不是可以从业务上

    进行拆分呢,各个模块单独使用各自得html呢,于是有了MPA(多页面应用)

    MPA,多页面应用

    通过webpack控制入口文件,打包出来多个最终文件同时提供多个html,可以实现模块之间项目独立从而达到解耦得目的,达到了我们得目的,但是也随之带来了一些弊端,MPA路由基于文档跳转,每一次跳转带来得负担就是需要重新加载

    公共资源文件,性能上对比SPA大大降低,切合到实际开发中当项目太大多个部门共同开发时,所有人共同开发一个独立工程,一旦一个模块代码出现问题会影响到整个前端工程,线上发布同样会遇到同样得问题,一个模块会影响整个工程。

    如何避免呢,答案就是微前端解决方案,那么什么是微前端设计方案呢

    MicroFrontend,微前端

    个人对于微前端的理解是基于对微服务的理解

    微服务将单体服务拆分成多个服务如图

     多个服务相互独立,通过聚合层对外暴露公共端口,每个服务实现独立部署,那么前端是不是也可以这么做呢,于是微前端就诞生了

    微前端架构解决了哪些SPA与MPA解决不了的问题呢?

    1)对前端拆分解决了MPA的资源重新加载的问题

    2)解决了SPA体积过大的问题

    3)解决开发过程中各个模块相互影响的问题,达到了模块独立开发。

    整体结构如图

     

     那么如何创建一个微前端的应用呢

    我们用两种方式实现,(核心思想都是single-spa)什么是single-spa自己查吧

    1)html嵌套

    核心:single-spa,htmlEntry

    注册中心

    import * as singleSpa from "single-spa";
    
    import GlobalInstance from "./globalInstance";
    
    import config from "./conf";
    
    import { importEntry } from "import-html-entry";
    
    var globalInstance = new GlobalInstance();
    
    var registeredModule = [];
    
    async function register(name, storeUrl, moduleUrl, path) {
      if (registeredModule.includes(name)) return;
    
      registeredModule.push(name);
    
      let storeModule = {},
        customProps = { globalInstance: globalInstance };
      // storeModule = await SystemJS.import(storeUrl);
    
      if (storeModule && globalInstance) {
        customProps.store = storeModule;
        // globalInstance.registerStore(storeModule);
      }
    
      singleSpa.registerApplication(
        name,
        () => {
          // return SystemJS.import(moduleUrl);
          return loadApp(moduleUrl);
        },
        () => {
          return location.pathname === path;
        },
        customProps
      );
    }
    async function loadApp(htmlPath) {
      const { template, execScripts, assetPublicPath } = await importEntry(
        htmlPath
      );
    
      const global = window;
    
      const appContent = template;
    
      let element = createElement(appContent);
    
      const execScriptsRes = await execScripts(global);
    
      var root = document.getElementById("root");
      root.appendChild(element);
    
      var appInstanceId = "test" + new Date().getTime();
    
      return {
        name: appInstanceId,
        bootstrap: execScriptsRes.bootstrap,
        mount: execScriptsRes.mount,
        unmount: execScriptsRes.unmount
      };
    }
    
    function createElement(htmlElement) {
      var container = document.createElement("div");
      container.innerHTML = htmlElement;
      return container;
    }
    
    config.forEach(c => {
      register(c.name, c.storeUrl, c.moduleUrl, c.path);
    });
    
    singleSpa.start();

    这里加载应用利用的是html嵌套

    子应用需要暴露三个钩子函数

    bootstrap,mount,unmount
    import singleSpaReact from 'single-spa-react';
    import RootComponent from './component/root.component';
    
    const reactLifecycles = singleSpaReact({
        React,
        ReactDOM,
        rootComponent: RootComponent,
        domElementGetter: () => document.getElementById('blog-root')
    })
    
    
    export const bootstrap = [
        reactLifecycles.bootstrap,
    ]
    
    export const mount = [
        reactLifecycles.mount,
    ]
    
    export const unmount = [
        reactLifecycles.unmount,
    ]

    打包时候,针对出口配置如下

    output: {
            path: path.resolve(__dirname, "./dist/"),
            filename: '[name]-[chunkhash].js',
            libraryTarget: "umd",
            library: "blog",
        },

    这里要注意打包输出采用umd形式以保证importEntry可以正确加载到

    2)js动态加载

    核心single-spa,systemjs

    import * as singleSpa from "single-spa";
    
    // import appJson from "./appConf/importmap.json";
    import confs from "./appConf/importConf.js";
    
    function loadApp(url) {
      return System.import(url)
        .then(module => {
          console.log(module);
          return module.default;
        })
        .then(manifest => {
          const { entrypoints, publicPath } = manifest;
          const assets = entrypoints["app"].assets;
          return System.import(publicPath + assets[0])
        });
    }
    
    confs.forEach(conf => {
      register(conf);
    });
    
    function register(target) {
      singleSpa.registerApplication(
        target.name,
        () => {
          return loadApp(target.url);
        },
        () => {
          return location.pathname === target.path;
        }
      );
    }
    
    singleSpa.start();

    子应用同样必须暴漏三个钩子函数

    bootstrap,mount,unmount
    import React from 'react'
    import ReactDOM from 'react-dom'
    import singleSpaReact from 'single-spa-react'
    import RootComponent from './root.component'
    
    const reactLifecycles = singleSpaReact({
      React,
      ReactDOM,
      rootComponent: RootComponent
      // domElementGetter: () => document.getElementById('common-root')
    })
    
    
    export const bootstrap = [
      reactLifecycles.bootstrap,
    ]
    
    export const mount = [
      reactLifecycles.mount,
    ]
    
    export const unmount = [
      reactLifecycles.unmount,
    ]

    该种方式利用system进行加载目标应用

    整个工程核心思想就这些,但是在实现过程中,我们如何正确加载到子应用

    路由匹配子应用时候如何解决跨域问题

    方案1

    跳过跨域问题,由server解决路由问题

    const express = require('express');
    const path = require('path');
    const { createProxyMiddleware } = require('http-proxy-middleware');
    
    const port = process.env.PORT || 3001;
    const app = express();
    
    app.use(express.static(__dirname))
    
    
    app.get('/blog', function (request, response) {
        response.sendFile(path.resolve(__dirname, 'index.html'))
    })
    
    app.get('/login', function (request, response) {
        response.sendFile(path.resolve(__dirname, 'index.html'))
    })
    var currentModule = '';
    
    const getTargetServer = function (req) {
        var conf;
        switch (req.path) {
            case '/common_module':
                currentModule = 'common_module';
                conf = {
                    protocol: 'http',
                    host: 'localhost',
                    port: 3002
                };
                break;
            case '/blog_module':
                currentModule = 'blog_module';
                conf = {
                    protocol: 'http',
                    host: 'localhost',
                    port: 3003
                };
                break;case '/login_module':
                currentModule = 'login_module';
                conf = {
                    protocol: 'http',
                    host: 'localhost',
                    port: 3005
                };
                break;default:
                switch (currentModule) {
                    case 'common_module':
                        conf = {
                            protocol: 'http',
                            host: 'localhost',
                            port: 3002
                        };
                        break;
                    case 'blog_module':
                        conf = {
                            protocol: 'http',
                            host: 'localhost',
                            port: 3003
                        };
                        break;case 'login_module':
                        conf = {
                            protocol: 'http',
                            host: 'localhost',
                            port: 3005
                        };
                        break;
                    case 'vedio_module':
                }
                break;
        }
        return conf;
    }
    
    const options = {
        target: 'http://localhost:3002',
        changeOrigin: true,
        pathRewrite: {
            '/common_module': '/',
            '/blog_module': '/','/login_module': '/',
        },
        router: function (req) {
            return getTargetServer(req);
        }
    }
    const filter = function (pathname, req) {
    
        var result;
        result = (pathname.match('/common_module') ||
            pathname.match('/blog_module') ||
            pathname.match('/login_module') ||
            pathname.match('/*.css') ||
            pathname.match('/*.js')) && req.method === 'GET';
        return result;
    }
    app.use(createProxyMiddleware(filter, options));
    
    
    app.listen(port, function () {
        console.log("server started on port " + port)
    })

    方案2

    前台通过cors解决跨域问题

    headers: {
      "Access-Control-Allow-Origin": "*"
    }

    以上就是微前端的基本知识点,之后会不停更新。

  • 相关阅读:
    GoldenGate配置(一)之单向复制配置
    Qt4.8.6+mingw+Qgis2.4.0基于QGis的二次开发
    Linux用户及用户组设置
    HDU1013_Digital Roots【大数】【水题】
    随意一条查询sql转换为查询结果集相应的数目
    对文件地址的几种概念的理解
    2014-10深圳全球架构师峰会
    有沃更精彩,沃课堂理想的移动学习平台
    自己动手写CPU之第九阶段(8)——MIPS32中的LL、SC指令说明
    Inno Setup入门(二)——修改安装过程中的图片
  • 原文地址:https://www.cnblogs.com/moran1992/p/14166056.html
Copyright © 2011-2022 走看看