微前端是什么?
微前端是一种软件架构,可以将前端应用拆解成一些更小的能够独立开发部署的微型应用,然后再将这些微应用进行组合使其成为整体应用的架构模式。
微前端架构类似于组件架构,但不同的是,组件不能独立构建和发布,但是微前端中的应用是可以的。
微前端架构与框架无关,每个微应用都可以使用不同的框架。
微前端的价值
遗留系统迁移
迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用 AngularJS 开发维护已经有三年时
间,但是随时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS 已经不能
满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接
迁移是不可能的,在新的框架中完全重写也不太现实。
使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求,
然后再使用微前端架构将旧的项目和新的项目进行整合。这样既可以使产品得到更好的用户体验,
也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。
独立发布
在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。
在使用了微前端架构后,可以将不同的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。
技术栈无关
因为微前端构架与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。
微前端的应用场景
- 拆分巨型应用,使应用变得更加可维护
- 兼容历史应用,实现增量开发
实现微前端方式
多个微应用进行组合
- 微前端架构中,除了存在多个微应用以外,还存在一个容器应用,每个微应用都需要被注册到容器应用中。
- 微前端中的每个应用在浏览器中都是一个独立的 JavaScript 模块,通过模块化的方式被容器应用启动和运行。
- 使用模块化的方式运行应用可以防止不同的微应用在同时运行时发生冲突。
微应用中实现路由
- 在微前端架构中,当路由发生变化时,容器应用首先会拦截路由的变化,根据路由匹配微前端应用,当匹配到微应用以后,再启动微应用路由,匹配具体的页面组件。
微应用与微应用之间实现状态共享
- 微应用中可以通过发布订阅模式实现状态共享,比如使用 RxJS。
微应用与微应用之间实现框架和库的共享
- 通过 import-map 和 webpack 中的 externals 属性。
可用的模块化解决方案
systemjs
微前端架构中,微应用被打包为模块,但浏览器模块化支持不友好,需要使用 systemjs 实现浏览器中的模块化。
systemjs 是一个用于实现模块化的 JavaScript 库,有属于自己的模块化规范。
我们可以使用 ES 模块规范,然后使用 webpack 将其转换为 systemjs 支持的模块。
案例
通过 webpack 将 react 应用打包为 systemjs 模块,在浏览器中使用systemjs加载模块
项目初始化
npm init -y
// 安装包
npm install webpack@5.17.0 webpack-cli@4.4.0 webpack-dev-server@3.11.2 html-webpack-plugin@4.5.1 @babel/core@7.12.10 @babel/cli@7.12.10 @babel/preset-env@7.12.11 @babel/preset-react@7.12.10 babel-loader@8.2.2
代码
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js", // 入口文件
// 出口配置
output: {
filename: "index.js", // 文件名
path: path.join(__dirname, "build"),
libraryTarget: "system", // 输出system模块
},
mode: "development", // 模式
devtool: "source-map",
// 开发服务配置
devServer: {
port: 9000, // 开发服务端口号
contentBase: path.join(__dirname, "build"),
historyApiFallback: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
inject: false,
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
},
externals: ["react", "react-dom", "react-router-dom"], // 防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>systemJs</title>
<script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
}
}
</script>
<!-- 引入systemjs -->
<script src="https://cdn.bootcdn.net/ajax/libs/systemjs/6.13.0/system.min.js"></script>
</head>
<body>
<div id="root"></div>
<script>
System.import("./index.js");
</script>
</body>
</html>
如上图,可以看到在当前站点,使用的是cdn上的react包。