简单聊聊微前端

165 阅读9分钟

微前端是一种将单体前端应用拆分成多个小型、松耦合的子应用的架构模式。它通过将前端应用拆分成多个独立的部分,可以使团队在开发、测试和部署等方面更加灵活和高效。同时,微前端架构还具有技术栈无关性、系统扩展性和可维护性等优点。在微前端架构中,不同的子应用可以使用不同的技术栈,通过消息通信等机制实现互相的调用和协作。通过微前端架构,可以有效地提高应用的可维护性、可扩展性和团队协作效率。

1. 微前端的实现方式

1.1 iframe 方式

使用 iframe 技术来实现微前端,每个子应用都可以独立部署和运行,子应用通过 iframe 嵌入到主应用中。主应用和子应用之间通过约定的协议进行通信,比如 postMessage、localStorage、cookie 等。

示例代码

主应用 index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主应用</title>
</head>
<body>
    <h1>主应用</h1>
    <div id="app"></div>
    <script>
        // 加载子应用
        function loadMicroApp(url) {
            const iframe = document.createElement('iframe');
            iframe.src = url;
            iframe.style.width = '100%';
            iframe.style.height = '500px';
            document.getElementById('app').appendChild(iframe);
        }
    </script>
</body>
</html>

子应用 subapp.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>子应用</title>
</head>
<body>
    <h1>子应用</h1>
    <script>
        // 发送消息到主应用
        window.parent.postMessage('Hello, 主应用!', '*');
        // 监听主应用发送的消息
        window.addEventListener('message', function(event) {
            console.log('收到来自主应用的消息:', event.data);
        });
    </script>
</body>
</html>

1.2 Web Components 方式

使用 Web Components 技术来实现微前端,每个子应用都打包为一个 Web Component,子应用通过自定义元素嵌入到主应用中。

示例:

主应用 index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Web Components Example</title>
  </head>
  <body>
    <h1>Main App</h1>
    <micro-app-one></micro-app-one>
    <micro-app-two></micro-app-two>
    <script src="index.js"></script>
  </body>
</html>

主应用 index.js:

// 定义子应用 URL
const microAppOneUrl = 'http://localhost:8081/micro-app-one.js';
const microAppTwoUrl = 'http://localhost:8082/micro-app-two.js';

// 加载子应用
async function loadMicroApp(url, tagName) {
  const { default: MicroApp } = await import(url);
  customElements.define(tagName, MicroApp);
}

loadMicroApp(microAppOneUrl, 'micro-app-one');
loadMicroApp(microAppTwoUrl, 'micro-app-two');

// 绑定事件监听器
window.addEventListener('message', (event) => {
  if (event.origin !== window.location.origin) {
    return;
  }

  if (event.data.type === 'message-from-micro-app-one') {
    console.log('Received message from micro-app-one:', event.data.message);
  }

  if (event.data.type === 'message-from-micro-app-two') {
    console.log('Received message from micro-app-two:', event.data.message);
  }
});

子应用 micro-app-one.js:

class MicroAppOne extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<h2>Micro App One</h2>';

    const button = document.createElement('button');
    button.textContent = 'Send message to main app';
    button.addEventListener('click', () => {
      window.parent.postMessage(
        {
          type: 'message-from-micro-app-one',
          message: 'Hello from micro-app-one!',
        },
        '*'
      );
    });

    this.appendChild(button);
  }
}

export default MicroAppOne;

子应用 micro-app-two.js:

class MicroAppTwo extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<h2>Micro App Two</h2>';

    const button = document.createElement('button');
    button.textContent = 'Send message to main app';
    button.addEventListener('click', () => {
      window.parent.postMessage(
        {
          type: 'message-from-micro-app-two',
          message: 'Hello from micro-app-two!',
        },
        '*'
      );
    });

    this.appendChild(button);
  }
}

export default MicroAppTwo;

在这个示例中,我们定义了两个子应用的 URL,并在主应用的 index.js 文件中加载并注册了这两个子应用。然后,在主应用的 index.html 文件中,我们使用自定义元素的方式引入了这两个子应用,并在主应用的 DOM 中展示它们。同时,在主应用的 index.js 文件中,我们监听了来自子应用的消息,并进行了相应的处理。

在每个子应用的 JavaScript 文件中,我们定义了一个自定义元素,并在其中添加了一个按钮。当用户点击按钮时,子应用会向主应用发送一条消息,并通过 postMessage() 方法将消息发送到主应用的窗口中。主应用在监听到这些消息后,根据消息的类型进行相应的处理,并将消息输出到浏览器的控制台中。

这个示例中的通信方式是基于 postMessage() 方法实现的。每个子应用可以向主应用发送一条消息,主应用则可以通过监听 message 事件来接收这些消息。通过这种方式,我们可以在微前端架构中实现子应用与主应用之间的通信。

除了 postMessage() 方法,还有许多其他的通信方式可以用于微前端架构。例如,我们可以使用事件总线、WebSocket 等技术来实现子应用与主应用之间的通信。

1.3 single-spa

single-spa 是一个用于实现微前端的 JavaScript 框架,它可以让我们在不同的框架和库之间无缝切换,同时也提供了很多开箱即用的功能,例如路由管理、应用生命周期管理、异步加载等。

使用 single-spa 可以非常方便地实现微前端架构,下面是一个简单的使用示例:

// 在主应用中注册微应用
singleSpa.registerApplication(
  'micro-app',
  () => import('./micro-app'),
  (location) => location.pathname.startsWith('/micro-app')
);

// 启动 single-spa
singleSpa.start();

这段代码的意思是,在主应用中注册了一个名为 micro-app 的微应用,它的入口文件是 ./micro-app,当用户访问的路径以 /micro-app 开头时,就加载该微应用。最后,调用 singleSpa.start() 方法启动 single-spa。

在子应用中,我们需要对输出的 bundle 进行一些配置,例如将 bundle 暴露为一个全局变量,以便主应用加载和调用,下面是一个示例:

// webpack 配置
module.exports = {
  // ...
  output: {
    library: 'microApp',
    libraryTarget: 'window',
    // ...
  },
  // ...
};

这段代码的意思是,在 webpack 的配置文件中,将输出的 bundle 暴露为一个名为 microApp 的全局变量,并将其绑定到 window 对象上。

在子应用的代码中,我们可以定义一些生命周期函数,例如 bootstrapmountunmount,这些函数会在子应用被加载、挂载和卸载时自动调用,下面是一个示例:

export const bootstrap = async () => {
  console.log('micro-app bootstrap');
};

export const mount = async () => {
  console.log('micro-app mount');
};

export const unmount = async () => {
  console.log('micro-app unmount');
};

这段代码的意思是,在子应用中定义了三个生命周期函数,分别是 bootstrapmountunmount,它们会在不同的阶段被调用,我们可以在这些函数中执行一些初始化操作和清理操作。

1.4 qiankun

qiankun 是一个基于 single-spa 的微前端解决方案,它提供了更多的功能和优化,例如多实例支持、样式隔离、事件通信等。

下面是一个简单的使用示例:

// 在主应用中注册微应用
const microApp = {
  name: 'micro-app',
  entry: '//localhost:8081',
  container: '#micro-app',
  activeRule: '/micro-app',
};

qiankun.registerMicroApps([microApp]);

// 启动 qiankun
qiankun.start();

这段代码的意思是,在主应用中注册了一个名为 micro-app 的微应用,它的入口 URL 是 //localhost:8081,容器选择器为 #micro-app,当用户访问的路径以 /micro-app 开头时,就加载该微应用。最后,调用 qiankun.start() 方法启动 qiankun。

在子应用中,我们需要对输出的 bundle 进行一些配置,例如将 bundle 暴露为一个全局变量,以便主应用加载和调用,下面是一个示例:

// webpack 配置
module.exports = {
  // ...
  output: {
    library: 'microApp',
    libraryTarget: 'umd',
    // ...
  },
  // ...
};

这段代码的意思是,在 webpack 的配置文件中,将输出的 bundle 暴露为一个名为 microApp 的全局变量,并将其绑定到 umd 模块上。

在子应用的代码中,我们可以定义一些生命周期函数,例如 bootstrapmountunmount,这些函数会在子应用被加载、挂载和卸载时自动调用,下面是一个示例:

export const bootstrap = async () => {
  console.log('micro-app bootstrap');
};

export const mount = async () => {
  console.log('micro-app mount');
};

export const unmount = async () => {
  console.log('micro-app unmount');
};

这段代码的意思是,在子应用中定义了三个生命周期函数,分别是 bootstrapmountunmount,它们会在不同的阶段被调用,我们可以在这些函数中执行一些初始化操作和清理操作。

除了生命周期函数之外,qiankun 还提供了一些 API 用于实现跨应用通信和路由管理,例如 setGlobalStateonGlobalStateChangestart 等,具体用法可以参考官方文档。

2. 微前端的优化

  1. 按需加载:在微前端架构中,由于每个子应用都是独立的,因此可以采用按需加载的策略,根据需要加载和渲染子应用的页面组件,从而减少资源的重复请求和加载,提高页面的加载速度和用户体验。
  2. 缓存策略:对于一些静态资源,可以采用缓存策略来减少资源的重复请求和加载,从而优化页面的性能。可以使用浏览器缓存、CDN缓存等方式来缓存静态资源。
  3. 资源管理:在微前端架构中,不同的子应用之间可能会存在资源冲突和命名空间污染问题,因此需要采用资源管理的策略来避免这些问题。可以使用模块化的方式来管理每个子应用的资源,避免命名冲突和重复加载。
  4. 代码分割:由于每个子应用都是独立的,因此可以将每个子应用的代码进行分割,从而减小每个子应用的代码体积,提高应用的性能和用户体验。可以使用webpack等构建工具来进行代码分割。
  5. 监控和调试:在微前端架构中,需要对子应用和主应用的运行情况进行监控和调试,以便及时发现和解决问题,提高应用的稳定性和可靠性。可以使用浏览器的开发者工具、日志记录工具、性能监控工具等来进行监控和调试。
  6. 共享依赖:在微前端架构中,可能会存在多个子应用共用同一个依赖库的情况。为了避免重复加载和占用资源,可以将这些依赖库提取出来,形成一个公共库,并通过CDN等方式进行共享。
  7. 部署优化:在微前端架构中,需要进行多个子应用的部署和协调。为了减少部署的复杂性和时间,可以使用容器化技术(如Docker)来进行部署,从而实现快速部署和协调。

3 应用场景

微前端适用于以下场景:

  • 大型单页应用:当应用规模较大时,拆分为多个小型子应用可以提高应用的可维护性和可扩展性。

  • 多团队协作开发:当多个团队协作开发一个应用时,拆分为多个小型子应用可以提高团队间的合作效率和减少代码冲突。

  • 跨技术栈应用:当应用需要使用不同的技术栈时,可以将应用拆分为多个小型子应用,每个子应用使用不同的技术栈。

  • 应用复用:当多个应用需要共享相同的功能模块时,可以将功能模块拆分为独立的子应用,便于在不同的项目中重复使用。

  • 独立更新:当应用需要进行更新时,可以只更新其中一个子应用,而不影响其他子应用的运行。