这是我参与更文挑战的第2天,活动详情查看:更文挑战
1. 微前端概述
1.1 什么是微前端
微前端是一种软件架构,可以将前端应用拆解成一些更小的能够独立开发部署的微型应用,然后再将这些微应用进行组合使其成为整体应用的架构模式。
微前端架构类似组件架构,但不同的是,组件不能独立构建和发布,微前端中的应用是可以的。
微前端架构与框架无关,每个微应用都可以使用不同的框架。
1.2 微前端的价值
- 增量迁移
迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用 AngularJS 开发维护已经有三年时间,但是随时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS 已经不能满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接迁移是不可能的,在新的框架中完全重写也不太现实。
使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求,然后再使用微前端架构将旧的项目和新的项目进行整合。这样既可以使产品得到更好的用户体验,也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。
- 独立发布
在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。
在使用了微前端架构后,可以将不能的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。
- 允许单个团队做出技术决策
因为微前端构架与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。
微前端的使用场景:
- 拆分巨型应用,使应用变得更加可维护
- 兼容历史应用,实现增量开发
1.3 如何实现微前端
- 多个微应用如何进行组合?
在微前端架构中,除了存在多个微应用以外,还存在一个容器应用
,每个微应用都需要被注册到容
器应用中。微前端中的每个应用在浏览器中都是一个独立的JavaScript模块,通过模块化的方式被容器应用启动和运行。
使用模块化的方式运行应用可以防止不同的微应用在同时运行时发生冲突。
- 在微应用中如何实现路由?
在微前端架构中,当路由发生变化时,容器应用首先会拦截路由的变化,根据路由匹配微前端应用,当匹配到微应用以后,再启动微应用路由,匹配具体的页面组件。
- 微应用与微应用之间如何实现状态共享?
在微应用中可以通过发布订阅模式
实现状态共享,比如使用RxJS
。
- 微应用与微应用之间如何实现框架和库的共享?
通过import-map
和webpack中的externals
属性。
2. Systemjs 模块化解决方案
2.1 概述
在微前端架构中,微应用被打包为模块,但浏览器不支持模块化,需要使用systemjs实现浏览器中的模块化。systemjs在微服务中主要充当加载器
的角色。
systemjs是一个用于实现模块化的JavaScript库,有属于自己的模块化规范。
在开发阶段我们可以使用ES模块规范,然后使用webpack将其转换为systemjs支持的模块。
2.2 基础案例
代码示例: 通过webpack将react应用打包为systemjs模块,再通过systemjs在浏览器中加载模块
npm install webpack@5.17.0 webpack-cli@4.4.0 webpack-dev-server@3.11.2 html-webpackplugin@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
package.json
// 不安装react相关依赖,通过systemjs中来加载
{
"name": "systemjs-react",
"scripts": {
"start": "webpack serve"
},
"dependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"babel-loader": "^8.2.2",
"html-webpack-plugin": "^4.5.1",
"webpack": "^5.17.0",
"webpack-cli": "^4.4.0",
"webpack-dev-server": "^3.11.2"
}
}
复制代码
webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.join(__dirname, "build"),
filename: "index.js",
libraryTarget: "system" // 关键?
},
devtool: "source-map",
devServer: {
port: 9000,
contentBase: path.join(__dirname, "build"),
historyApiFallback: true
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/react"]
}
}
}]
},
plugins: [
new HtmlWebpackPlugin({
inject: false, // 不自动加载打包的js
template: "./src/index.html"
})
],
externals: ["react", "react-dom", "react-router-dom"] // 不通过webpack打包
}
复制代码
src/index.html
在主应用中引入需要的react,react-dom,react-router-dom
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react micro for systemjs</title>
// 通过imports导入需要的模块
<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/reactdom.production.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-routerdom@5.2.0/umd/react-router-dom.min.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.0/dist/system.min.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// 手动加载打包的js
System.import("./index.js")
</script>
</body>
</html>
复制代码
src/index.js
import React from "react"
import ReactDom from "react-dom"
import App from './App.js'
ReactDom.render(<App />, document.getElementById("root"))
复制代码
src/App.js
import React from "react"
export default function App(){
return <div>React 微服务 for systemjs</div>
}
复制代码