基于SingleSPA的微前端解决方案(一)

70 阅读2分钟

第一部分:创建host应用程序

我们的目标是基于 single spa 和模块联邦构建一个示例微前端解决方案。为了实现这一目标,我们将开发以下内容:

  1. single spa 根配置
  2. 用于微前端与主机应用程序以及其他微前端进行通信的发布/订阅消息代理。
  3. 基于 MobX 的全局存储,用于在微前端之间共享一些与 UI 相关的数据。

为了使解决方案更加可行,我们将为微前端创建一个 nx 管理的 monorepo。

在我开始code时,single spa 已经保持了其主要功能已经很长一段时间,包括创建主机应用程序,或者称之为“root-config”。

要创建一个 root 配置,只需执行:

npx create-single-spa

选择应用程序类型为 root-config,您可以选择使用其布局引擎与否。在我的项目中,我没有将其添加到项目中。

root-config.ts 文件是最重要的文件,因为您可以在此文件中添加大部分主机应用程序逻辑。

在我的案例中,在构建路由之前,需要进行基于 WIFI 的身份验证。主机应用程序将尝试向身份验证服务器发送请求并获取一组令牌,其中一个是所有即将进行的 ajax 请求的 access_token,另一个是在 access_token 过期时使用的 refresh_token,refresh_token 将用于重新验证用户身份并交换另一组令牌。关于身份验证流程,我们可以在另一系列的博客文章中进行扩展。好的,我们假设身份验证流程在一个名为 authenticate 的异步函数中完成:

const authenticate = async (authURL: string, requestBody): Promise<string | null> => {
  return new Promise((resolve) => {
    fetch(authURL, {
      method: "POST",
      body: requestBody,
    })
      .then((res) => {
        if (res.status !== 200) {
          throw new Error("401");
        }
        return res.json();
      })
      .then((res) => resolve(res.tokenGroup))
      .catch(() => resolve(null));
  });
};

当身份验证完成时,我们构建路由,否则显示一个简单的错误页面:

const url = ''; // 替换为您的 URL
const user = {
  username: '',
  password: ''
};
const tokenGroup = await authenticate(url, JSON.stringify(user));
if (!tokenGroup) {
  registerApplication({
    name: "error-app",
    app: {
      bootstrap: async () => {},
      mount: async () => {
        const template = `
      <div id="root-error">
        <h1 id="root-error_title">Error</h1>
        <p id="root-error_content">There is no valid token found, please check your network connection</p>
      </div>
      `;
        document.getElementById("root").innerHTML = template;
      },
      unmount: async () => {},
    },
    activeWhen: ["/"],
  });
  start({
    urlRerouteOnly: true,
  });
  return;
}
const { access_token } = tokenGroup;
// 处理access_token
// ...

然后,我们需要构建自己的路由:

const getRoutes = (loadAppMap) => {
  let mainApps = "";
  for (const app in loadAppMap) {
    const [url, appName] = app;
    mainApps += `<route path="/${url}"><application name="${appName}"></application></route>`;
  }
  return `<single-spa-router base="/" containerEl="#main">${mainApps}</single-spa-router>` as string;
};

在拥有路由之后,注册我们的应用程序:

jsxCopy code
const initApps = (authorizedApps) => {
  const template = await fetchConfigAndConstructTemplate(authorizedApps);
  const routes = constructRoutes(template);
  const applications = constructApplications({
    routes,
    loadApp({ name }) {
      if (loadAppMap[name]) return System.import(loadAppMap[name]);
      return System.import(name);
    },
  });
  const layoutEngine = constructLayoutEngine({ routes, applications });
  applications.forEach(registerApplication);
  layoutEngine.activate();
  addErrorHandler((err) => {
    const errorStatus = getAppStatus(err.appOrParcelName);
    if (errorStatus === LOAD_ERROR) {
      unloadApplication(err.appOrParcelName);
      System.delete(System.resolve(err.appOrParcelName));
    }
  });
  start({
    urlRerouteOnly: true,
  });
};

注意:上面的代码由于道义和公司政策的原因进行了更多或更少的修改,它只是帮助您了解设置的一种方式,而不是一个可以直接使用的应用程序。下一次,我们将在我们这次创建的应用程序中创建一个 nx monorepo。