拆分子应用不同目录
在上篇中讲到了,在同一个目录下创建主应用以及子应用,但是这样的话其实还是感觉像一个大应用,并且别的模块在维护的时候还需要把整个项目 clone 下来,感觉还是欠缺了点什么。
在这篇中大致上还是基于上看的结构,接着把上面的结构完全拆开来,拆分到单独的 repo,可以分为如下几个结构: 1. 根(主)应用: root-config 2. 子应用 1: app1 3. 子应用 2: app2
接着一步步来
Step 1
把 app1 和 app2 从 micro-front 文件下拆分出来,与 micro-front 同级,基本需要包含如下的文件
|—— micro-front
|——— index.ejs // 页面
|——— package.json
|——— webpack.config.config
|——— single-spa.config.js // 主应用的的 single-spa 配置
|—— app1
|——— src
|——— App.js // 子应用
|——— react-mf-app1.js // 子应用的 single-spa 配置
|——— set-public-path.js // 暴露子应用
|——— package.json
|——— webpack.config.config
|—— app2
|——— src
|——— App.js
|——— react-mf-app2.js
|——— set-public-path.js
|——— package.json
|——— webpack.config.config
Step 2
修改 mirco-front 的 webpack 配置
const path = require("path")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = () => {
const result = {
entry: path.resolve(__dirname, "./single-spa.config"),
output: {
filename: "single-spa.config.js",
libraryTarget: "system",
path: path.resolve(__dirname, "dist"),
},
devtool: "sourcemap",
module: {
rules: [
{ parser: { system: false } },
{
test: /\.js$/,
exclude: /node_modules/,
use: [{ loader: "babel-loader" }],
},
],
},
devServer: {
historyApiFallback: true,
disableHostCheck: true,
headers: {
"Access-Control-Allow-Origin": "*",
},
https: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.ejs",
}),
new CleanWebpackPlugin(),
],
externals: ["single-spa", /^@react-mf\/.+$/],
}
return result
}
修改 index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>React Microfrontends</title>
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap">
{
"imports": {
"@react-mf/root-config": "//localhost:9000/single-spa.config.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/import-map-overrides@2.1.0/dist/import-map-overrides.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.7.1/dist/system.min.js"></script>
<template id="single-spa-layout">
<single-spa-router>
<div class="main-content mt-16">
<route path="app1">
<application name="@react-mf/app1"></application>
</route>
<route path="app2">
<application name="@react-mf/app2"></application>
</route>
<route default>
<h1 class="flex flex-row justify-center p-16">
<p class="max-w-md">
This example project shows independently built
and deployed microfrontends that use React and
single-spa. Each nav link above takes you to a
different microfrontend.
</p>
</h1>
</route>
</div>
</single-spa-router>
</template>
</head>
<body>
<a href="/app1">app1</a>
<br />
<a href="/app2">app2</a>
<script>
System.import("@react-mf/root-config")
</script>
</body>
</html>
Single-sap.config.js
import {
constructRoutes,
constructLayoutEngine,
constructApplications,
} from "single-spa-layout"
import { registerApplication, start } from "single-spa"
console.log(registerApplication)
const routes = constructRoutes(document.querySelector("#single-spa-layout"), {
loaders: {
topNav: "<h1>Loading topnav</h1>",
},
errors: {
topNav: "<h1>Failed to load topNav</h1>",
},
})
const applications = constructApplications({
routes,
loadApp: ({ name }) => System.import(name),
})
const layoutEngine = constructLayoutEngine({
routes,
applications,
active: false,
})
applications.forEach(registerApplication)
layoutEngine.activate()
start()
到这里,主应用的改造就算完成了,你可以单独运行一下主应用,切换到子应用路由,页面控制台会报未找到子应用的错误,是因为子应用没有改造完且没有运行,所以是找不到
Step 3
改造完主应用接下来来改造一下子应用 首先改造 webpack.config.js,因为子应用是我们直接通过 react 的 CRA 脚手架创建的,懒得把内置的配置暴露出来,所以自己就增加一个 webpack 配置,同样的在 package.json 中增加一条命令 webpack.config.js
const webpackMerge = require("webpack-merge")
const singleSpaDefaults = require("webpack-config-single-spa-react")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = (webpackConfigEnv = {}) => {
// TODO: 这部分具体干嘛的还不怎么清楚,感觉像是对外暴露这个包名的
const defaultConfig = singleSpaDefaults({
orgName: "react-mf",
projectName: "app1",
webpackConfigEnv,
})
const config = webpackMerge.smart(defaultConfig, {
devServer: {
historyApiFallback: true,
https: true,
},
plugins: [new HtmlWebpackPlugin()],
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
externals: [/^rxjs\/?.*$/],
})
return config
}
Package.json 增加一条 script
"startDev": "webpack-dev-server --port 9001"
React-mf-app1.js
const webpackMerge = require("webpack-merge")
const singleSpaDefaults = require("webpack-config-single-spa-react")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = (webpackConfigEnv = {}) => {
const defaultConfig = singleSpaDefaults({
orgName: "react-mf",
projectName: "app1",
webpackConfigEnv,
})
const config = webpackMerge.smart(defaultConfig, {
// customizations go here
devServer: {
historyApiFallback: true,
https: true,
},
plugins: [new HtmlWebpackPlugin()],
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
externals: [/^rxjs\/?.*$/],
})
return config
}
Set-public-path.js
import { setPublicPath } from "systemjs-webpack-interop"
setPublicPath("@react-mf/app1")
自此,子应用的也算是改造完了,app2 参考着 app1 改造就行。
Step 4
运行,配置 importmap ,分别启动主应用和子应用
⚠️ 这里使用的是 https ,所以注意自己的 url
运行起来后,我们可以看到我们主应用的页面,切换的子应用的路由控制台会报错,这是因为我们还是实际配置 importmap
在这之前先推荐安装一下 single-spa 的一个 chrome 扩展 single-spa-inspector
,这个扩展可以在我们运行 single-spa 的微服务应用的时候帮助我进行一些配置,以及做一个子应用的切换
安装了扩展之后我们的 Chrome 控制台就会多这么一个 tab ,在这个 tab 下就能看到我们的子应用有哪些,初始 import override 那是没有配置的,这里根据我们自己的子应用做一个配置就行,上面就是我的配置,到这里算是完成了,我们切换路由就可以看到对应的子应用了。