微前端系列(1)-从入门到实现基座应用与子应用

marvin

marvin

微前端系列(1)-从入门到实现基座应用与子应用

什么是微前端

假设我们有一个后台管理系统, 其中有两个页面, 一个是产品列表, 一个是购物车

在传统的组件化开发模式下, 我们可能会分别将这两个页面封装到一个组件里, 而且我们只有一套前端代码, 而有了微前端之后, 我们可以将这两个功能分离到两套代码库里

这里的问题可能是如何让产品列表和购物车通信, 两套代码库并不能够直接通信, 因此需要借助 API 去实现, 那么在产品列表页面, 这里会发送两个请求, 一个是获取产品列表, 另一个是将产品添加到购物车里面, 在购物车页面会发送一个请求, 用于获取购物车数据

为什么要使用微前端

将一个前端应用分离成多个前端应用的好处是什么呢? 我们不禁要问, 一个很大的好处是, 这些分离的应用能够交给不同的团队去维护, 而且每个团队都可以使用自己擅长的技术栈, 这对某一个团队来说, 应用更容易维护了, 另一个好处是,当一个应用出现故障并不会影响其他的应用

如何让两个应用在一个页面上展现

通常为了让两个应用中的组件在一个页面上展现, 我们会创建一个称为 container 的微前端应用,它来决定什么时候以及在哪里显示其他的微前端应用, 这个时候 container 必须要能访问其他微前端应用代码才能进行组件的展示, 实现代码访问控制这一点可以通过多种方式来实现, 下面就来看看

编译时集成

我们可以在 Container 这个应用加载到浏览器之前, 在编译的阶段将子应用的代码整合进来, 子应用会以 npm 包的形式被导入

  • 编译时集成的好处是, 容易配置和易于理解, 坏处是每次子应用需要重新发布的时候, Container 也必须同步更新, 另一个很大的坏处是, 因为 Container 对子应用拥有完全和直接的访问权限, 导致耦合性太强了

运行时集成

我们可以在 Container 这个应用加载到浏览器之后, 动态加载子应用的代码, 子应用可以通过静态 URL 的方式加载进来也可以在 Container 加载完成之后通过发送请求的方式加载进来, 通过 Webpack Module Federation, 我们可以实现运行时集成, 本文将以运行时集成为例子

  • 运行时集成的好处是子应用能够在任何时候单独发布而不必依赖 Container, 另一个好处是我们可以同时发布子应用的多个版本, Container 来决定使用哪一个, 坏处是配置和相应的工具比较复杂

服务端集成

我们可以在向服务器发送加载 Container 的代码时, 由服务端决定是否将子应用的代码包含进来

微前端实践

这里以一个 E-Commerce 项目为例子, 我们会创建以下3个项目:

  • container(基座)
  • cart(购物车子应用)
  • products(产品列表子应用)

项目的目录结构都是一样的, 如下:

项目名称
├── package.json
├── public
│   └── index.html
├── src
│   └── index.js
└── webpack.config.js

这里我们不会使用任何框架, 除了使用了 webpack 打包之外, 都是纯粹的前端 js 代码. 子应用彼此隔离, 并且除了能在基座应用中运行之外也能够独立运行

实现产品列表子应用

为了统一工作区,基座和子应用都会被放到一个目录下, 因此首先创建我们的应用文件夹:

mkdir e-commerce-app

接下来创建第一个子应用 products:

mkdir products
cd products

然后初始化项目, 安装必要的依赖:

yarn init -y
yarn add faker html-webpack-plugin nodemon webpack webpack-cli webpack-dev-server

这里我们使用 faker 来生成假数据, html-webpack-plugin 可以将编译的代码插入 html 文件里不用再手动 copy, nodemon 帮助我们监听文件变化并重启服务器, webpack 用来编译我们的代码, webpack-cli 是 webpack 的命令行工具, webpack-dev-server 用来启动webpack开发服务器, 可以实时预览编译结果

接下来创建 src 目录用于存放源代码文件:

mkdir src
touch src/index.js

创建 public 目录用来存放模板文件:

mkdir public
touch public/index.html

index.html 内容如下:

<!DOCTYPE html>
<html>
	<head></head>
	<body>
    <div id="dev-products"></div>
  </body>
</html>

src/index.js 内容如下:

import faker from 'faker';

const products = [...Array(3).keys()].map(i => {
	return `
		<div>${faker.commerce.productName()}</div>
	`  
}).join('');

document.querySelector('#dev-products').innerHTML = products;

这里通过 faker 生成了一个产品列表并显示在 dev-products 容器中, 其中有3个元素, 为了让代码能在浏览器中运行, 我们还需要用 webpack 编译代码

编译代码

编译之前, 需要编写 webpack 配置文件, 为此在 products 根目录下创建 webpack.config.js 文件:

touch webpack.config.js

配置 mode, html 插件以及开发服务器:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    devServer: {
        port: 8081,
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html'
        }),
    ]
}

最后在 package.json 中添加 start 命令, 以启动 webpack 执行编译:

// package.json
// ...
"scripts: {
  "start": "webpack serve"
}
// ...

当运行 yarn start 执行编译结束之后, webpack 开发服务器会在 8081 端口启动一个服务, 打开这个地址,在浏览器里看到一个随机的 products 列表. 需要注意的是, 这里并没有生成 dist 目录, 因为这一切都是在内存中发生的.

到此, 我们的产品列表子应用就完成了, 接下来就可以关注如何实现一个 container 基座应用

实现基座应用

在应用程序文件夹下,创建一个 container 文件夹作为基座应用的根目录:

mkdir container

然后初始化项目并安装一些依赖:

yarn init -y
yarn add html-webpack-plugin nodemon webpack webpack-cli webpack-dev-server

创建 public, src 目录:

mkdir public
mkdir src

新增 public/index.html:

<!DOCTYPE html>
<html>
  <head></head>
  <body></body>
</html>

新增 src/index.js:

console.log('Container');

新增 webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    devServer: {
        port: 8081,
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html'
        })
    ]
}

新增启动脚本, 修改 package.json:

// ...
"scripts": {
	"start": "webpack serve"
}
// ...

到此, 我们的基座应用就完成了, 接下来就可以关注如何实现基座应用与子应用的集成了, 这一部分放到下一篇文章

什么是微前端
为什么要使用微前端
如何让两个应用在一个页面上展现
编译时集成
运行时集成
服务端集成
微前端实践
实现产品列表子应用
编译代码
实现基座应用