Single-Spa 实践-- 拆分子应用不同目录

2022-07-26,,,,

Single-Spa 实践(下)-- 拆分子应用不同目录

在上篇中讲到了,在同一个目录下创建主应用以及子应用,但是这样的话其实还是感觉像一个大应用,并且别的模块在维护的时候还需要把整个项目 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 那是没有配置的,这里根据我们自己的子应用做一个配置就行,上面就是我的配置,到这里算是完成了,我们切换路由就可以看到对应的子应用了。

本文地址:https://blog.csdn.net/F_Felix/article/details/110585819

《Single-Spa 实践-- 拆分子应用不同目录.doc》

下载本文的Word格式文档,以方便收藏与打印。