# 前言

前面我们已经针对微前端的概念及 single-spa 内的主要功能做了介绍,下面我们要针对开发好的项目进行部署,网上针对开发的过程讲解的文档还是很多的,但是没有一整套完整的达到线上运行效果的文档,对于微应用基础镜像的构建,这里不是讲解的重点,之前针对镜像构建我写过一篇文章,可以满足不同的微应用的镜像构建 镜像构建篇 - 我是如何实现 docker 镜像 2 分钟构建、部署 (opens new window),在基于 root-config 进行完整案例演示

# 针对官方案例进行优化

index.ejs 内使用了 CSP

在初学阶段我安装了 single-spa-reactroot-config 是可以正常运行的,但是安装了 single-spa-vue 发现在 root-config 内跑不起来,独立运行也是不可以的,独立运行的办法网上有,通过 window.singleSpaNavigate 来判断其是否是 single-spa 环境内

if (!window.singleSpaNavigate) {
  new Vue({
    render: h => h(App),
  }).$mount('#app');
} else {
  setPublicPath("@spa-vue/single-spa-vue", 2);
  vueLifecycles = singleSpaVue({
    Vue,
    appOptions: {
      render(h) {
        console.log(this)
        return h(App, {
          props: {
            // single -spa props are available on the "this" object. Forward them to your component as needed.
            // https://single-spa.js.org/docs/building-applications#lifecyle-props
            name: this.name,
            mountParcel: this.mountParcel,
            singleSpa: this.singleSpa,
          },
        });
      },
    },
  });
}

但是这在 root-config 内还是无法正常运行,也没有任何提示,插件也只会报 mount error,baidu、google 我都搜过了,也没找着解决的办法,后来一点一点的尝试,发现是官方的案例内添加了 csp(内容安全策略) (opens new window) ,而我在本地环境下,使用的是 http:

<!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>Root Config</title>
  ...
  <meta http-equiv="Content-Security-Policy" content="default-src 'self' https: localhost:*; script-src 'unsafe-inline' 'unsafe-eval' https: localhost:*; connect-src https: localhost:* ws://localhost:*; style-src 'unsafe-inline' https:; object-src 'none';">
  ...

在 head 头内 添加了 Content-Security-Policy 的 meta 标签,可以选择在调试阶段先注释掉,上线的时候在放开

# 修改 webpack 配置

官方的案例内只是做了一些简单的配置,但是对于打包在容器内运行,还是差点东西,在加上 importmap 的配置不利于优化,我就整体做了一个调整:

importmap

 <script type="systemjs-importmap">
    {
      "imports": {
        "@single-spa/welcome": "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js",
        "@spa/root-config": "//localhost:9000/spa-root-config.js"
      }
    }
 </script>

原来是在 index.ejs 内添加了一个 script 标签,所有配置都写在了页面里,我认为配置方面的东西应该是一个独立的配置环境,所以我把它独立了出来:

<script type="systemjs-importmap" src="./importmap.json"></script>
{
  "imports": {
    "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.5/lib/system/single-spa.min.js",
    "react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
    "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js",
    "@spa/react": "//localhost:8500/spa-react.js",
    "@spa-vue/single-spa-vue": "//localhost:8080/js/app.js"
  }
}

写到这里我发现还有一点要提醒大家,使用 single-spa-react 构建的 react 应用,想在 root-config 内运行是需要配置上面代码中的两个变量:reactreact-dom

@spa/react@spa-vue/single-spa-vue 的域名可以通过 dotenv 来设置环境变量

上面配置了 importmap ,但是想打包进去还需要对整体的结构做个优化,结合了 reactvue 项目的经验,我选择把 importmap.jsonindex.ejs 放在 public 目录中,在通过 webpack 配置把相应的文件 copy 到打包目录中,优化后的 webpack 配置如下:

const path = require("path");
const fs = require('fs-extra');
const webpack = require('webpack');
const webpackMerge = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const paths = {
  appPublic: path.join(__dirname, 'public'),
  appBuild: path.join(__dirname, 'dist'),
  appHtml:  path.join(__dirname, 'public/index.html'),
}
// const { CleanWebpackPlugin } = require("clean-webpack-plugin");

function copyPublicFolder() {
  fs.copySync(paths.appPublic, paths.appBuild, {
    dereference: true,
    filter: file => file !== paths.appHtml,
  });
}

module.exports = (webpackConfigEnv) => {
  const orgName = "spa";
  const defaultConfig = singleSpaDefaults({
    orgName,
    projectName: "root-config",
    webpackConfigEnv,
  });

  const config = webpackMerge.smart(defaultConfig, {
    devtool: 'soure-map',
    // modify the webpack config however you'd like to by adding to this object
    devServer: {
      historyApiFallback: true,
      // publicPath: '/src/',
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
      contentBase: paths.appPublic
    },
    module: {
      rules: [
        {
          use: [
            {
              test: /\.json$/,
              loader: 'json-loader'
            }
          ]
        }
      ]
    },
    plugins: [
      // new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        inject: false,
        template: paths.appHtml,
        templateParameters: {
          isLocal: webpackConfigEnv && webpackConfigEnv.isLocal === "true",
          orgName
        }
      })
    ],
  });

  const compiler = webpack(config)
  compiler.run((err, stats) => {
    // console.log('构建完成', stats)
    copyPublicFolder()
  })
  return config
};

构建的配置文章头部就有那篇我的文章,只要按照那篇文章的构建部署的方式去做,就可以完美的完成一个本地容器模拟线上环境的一个基于 single-spa 开发的微应用了

# 结束语

到这里代码部分就差不多了,基于前面的几篇文章和本章就可以从0到1的实现一个微前端的应用了,如果在使用的过程中出现了什么问题,可以随时评论区呼唤我,所有的案例都是我自己一个人跑了的,可能有一些没有遇见过的特殊情况,也还请大家见谅

下面的部分就是针对目前微前端市场存在的一些问题和争议的探讨,这里其实所提出的问题更能深刻的说明微前端的优缺点和未来的发展方向

阅读全文