浅谈微前端

687 阅读4分钟

一、微前端的概念

微前端(micro-frontends)并不是一项新的技术,而是一种浏览器端的架构理念.微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。

9fdf6adf-8448-42de-af40-07567e634534.jpg

二、微前端优点

1. 增量升级

  • 通过渐进式重构的手段和策略整合、升级、开发项目,解决不同技术栈对项目发展的限制,避免完全重写旧项目,采用逐个替换旧模块的方式按需升级。

2. 简单、解耦的应用代码库

  • 对于开发人员来说代码更容易解藕、维护,避免了组件间耦合所导致的复杂性,每个前端应用可以只关注自己所需要完成的功能。

3. 独立部署

  • 就像微服务一样,微前端的独立部署能力是关键,部署范围越小,带来的上线风险越低。

4. 业务自治

  • 遵循统一的接口规范进行系统的集成,相互之间不存在依赖关系,不受技术栈限制,每个团队围绕业务功能垂直组建。

三、微前端的多种实现方式

1. iframe

  • iframe 虽不是为微前端而生,但是是集成的最简单方式之一。本质上来说,iframe 里的页面是完全独立的,而且 iframe 还提供了很多的隔离机制,我们只需将单体的前端应用,按照业务模块进行拆分,分别部署,最后通过 iframe 进行动态加载即可。

<html>

    <head>

        <title>微前端实现方式-iframe</title>

    </head>

    <body>

        <h1>来学习!</h1>

        <iframe id="frontend-container"></iframe>

        <script type="text/javascript">

            const routes = {

                '/': 'https://app.com/index.html',

                '/app1': 'https://app1.com/index.html',

                '/app2': 'https://app2.com/index.html',

            };

            const iframe = document.getElementById('frontend-container');

            iframe.src = routes[window.location.pathname];

        </script>

    </body>

</html>

  • 优点:实现简单,运行稳定,天然具备隔离性。

  • 缺点:主页面和 iframe 共享最大允许的 HTTP 链接数,iframe 阻塞主页面加载,浏览器的后退按钮无效,性能低、通信复杂、双滚动条、弹窗无法全局覆盖,它的扩展性不高,只适合简单的页面渲染。

2. 后端模版方式

  • 常见的实现方式是,服务端根据路由动态渲染特定页面的模板文件,将多个模板渲染到服务器上的HTML里,有一个index.html,其中包含所有常见的页面元素,然后使用 include 来引入其他模板.

  • Html


<html lang="en" dir="ltr">

    <head>

        <meta charset="utf-8">

        <title>微前端实现方式-后端模版方式</title>

    </head>

    <body>

        <h1>来学习!</h1>

        <!--# include file="$PAGE.html" -->

    </body>

</html>

  • Nginx

server {

    listen 8080;

    server_name localhost;

    root /usr/share/nginx/html;

    index index.html;

    ssi on;


    # 将 / 重定向到 /app

    rewrite ^/$ http://localhost:8080/app redirect;


    # 根据路径访问 html

    location /app {

        set $PAGE 'app';

    }

    location /app1 {

        set $PAGE 'app1';

    }

    location /app2 {

        set $PAGE 'app2'

    }


    # 所有其他路径都渲染 /index.html

    error_page 404 /index.html;

}

  • 优点:实现简单,技术栈独立。

  • 缺点:需要额外配置 Nginx,前后端分离不彻底。

3. npm包方式

  • 将每个微前端封装发布为一个npm包,通过组件或者依赖方式引入。但是,这种方法意味着我们每次开发新功能都必须重新编译并发布每个微前端应用,进行版本更新,每次版本发布需要通知接入方同步更新。强烈不建议使用这种微前端方案。

  • npm包方式


{

    "name": "@app/container",

    "version": "1.0.0",

    "description": "A web app",

    "dependencies": {

        "@app/app": "^1.0.1",

        "@app/app1": "^1.0.2",

        "@app/app2": "^1.0.3"

    }

}

  • 优点:管理简单,性能兼容性好,部署开发可隔离。

  • 缺点:主应用和子应用之间版本更新存在强依赖关系,管理困难。

4. 引入JS方式

  • 这种方式是被采用频率最高的一种方法。每个微前端都对应一个 《script》 标签,并且在加载时导出一个全局变量。然后,容器应用程序确定应该安装哪些微应用,并调用相关函数以告知微应用何时以及在何处进行渲染。与 package 集成不同,我们可以用不同的bundle.js独立部署每个应用。与 iframe 集成不同的是,我们具有完全的灵活性,你可以用 JS 控制什么时候下载哪个应用,以及渲染应用时额外传参数。

  • 引入JS方式


<html>

    <head>

        <title>微前端实现方式-引入JS方式</title>

    </head>

    <body>

        <h1>来学习!</h1>

        <!-- 以下脚本不会马上渲染应用,而是分别暴露全局变量 -->

        <script src="https://app.com/bundle.js"></script>

        <script src="https://app1.com/bundle.js"></script>

        <script src="https://app2.com/bundle.js"></script>

        <div id="frontend-container"></div>

        <script type="text/javascript">

            // 以下全局函数是上面脚本暴露的

            const routes = {

            '/': window.renderApp,

            '/app1': window.renderApp1,

            '/app2': window.renderApp2,

            };

            const renderFunction = routes[window.location.pathname];

            // 渲染一个微应用

            renderFunction('frontend-container');

        </script>

    </body>

</html>

  • 优点:灵活部署,独立开发。

  • 缺点:多个js bundle脚本的下载和执行会阻塞浏览器的解析。

5. Web Component 方式

  • Web Component是JS方式的一种方法的变体,每个微应用对应一个 HTML 自定义元素,供容器实例化,而不是提供全局函数。主要区别在于使用 Web Component 代替全局变量。但是ShadowDom的兼容性非常不好,一些前端框架在ShadowDom环境下无法正常运行,尤其是react框架。

  • Web Component方式


<html>

    <head>

        <title>微前端实现方式-Web Component方式</title>

    </head>

    <body>

        <h1>来学习!</h1>

        <!-- 以下脚本不会马上渲染应用,而是分别暴露全局变量 -->

        <script src="https://app.com/bundle.js"></script>

        <script src="https://app1.com/bundle.js"></script>

        <script src="https://app2/bundle.js"></script>

        <div id="frontend-container"></div>

        <script type="text/javascript">

            // 以下标签名是上面代码定义的

            const routes = {

                '/': 'micro-frontend-app',

                '/app1': 'micro-frontend-app1',

                '/app2': 'micro-frontend-app2'

            };

            const renderFunction = routes[window.location.pathname];

            // 渲染一个微应用(自定义标签)

            const root = document.getElementById('frontend-container');

            const webComponent = document.createElement(renderFunction);

            root.appendChild(webComponent);

        </script>

    </body>

</html>

  • 优点:组件化开发,样式和元素隔离,灵活部署。
  • 缺点:多个js bundle脚本的下载和执行会阻塞浏览器的解析,ShadowDom兼容性差,一些前端框架在ShadowDom环境下无法正常运行。

小结

  • 微前端架构思想可以帮助开发者解决一些业务发展中的实际问题,不同的实现方式也各有利弊,对于每个业务来说,是否适合使用微前端?如何正确的使用微前端?还需要通过应用场景,兼容范围,系统集成目标,交互体验要求,部署能力,管理能力,开发能力等多方面综合分析之后做出决策,目前业界已经有不少框架来帮助开发者轻松的集成微前端架构,下次将从框架及其具体的设计思想方向来进行学习。