>[success] # 简单上手 -- single-spa ~~~ 1.在 'single-spa' 方案中,应用被分为两类:基座应用和子应用。基座应用是整体父级,内部子应用在基座应用中 和单页应用的实现原理类似,'single-spa' 会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启 动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页 面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面。 ~~~ >[info] ## 创建过程 ~~~ 1.'npm install create-single-spa -g' -- 安装sigle-spa 脚手架 2.'create-single-spa 基座项目文件名' -- 举个例子'create-single-spa c' 自动创建一个c 文件夹内部是生成的,如果不是 全局安装需要通过'npx' 执行脚本命令,基座代码,当然执行'create-single-spa' 会出现一下选择 2.1. 应用文件夹填写 container 2.2. 应用选择 single-spa root config 2.3. 组织名称填写 study 组织名称可以理解为团队名称,微前端架构允许多团队共同开发应用,组织名称可以标识应用 由哪个团队开发。应用名称的命名规则为 @组织名称/应用名称 ,比如 @study/todos 3.'create-single-spa 文件名' -- 这时候选择.'single-spa-application / parcel':微前端架构中的微应用,可以使用 vue、 react、angular 等框架 注关于几个选项详细解释: 'single-spa root config':创建微前端容器应用。 'single-spa applications':为一组特定路由渲染组件的微前端。 'single-spa parcels': 不受路由控制,渲染组件的微前端。 'utility modules': 非渲染组件,用于暴露共享javascript逻辑的微前端。 ~~~ ![](https://img.kancloud.cn/2c/7c/2c7c6fb016bec291b8148bf85e15b1f8_1057x175.png) >[info] ## 生成的基座文件介绍 ~~~js 1.生成的目录结构 base-root ├─ src │ ├─ index.ejs │ └─ study-root-config.js ├─ package-lock.json ├─ package.json └─ webpack.config.js 2.创建基座后发现自动生成以下几个关键文件 2.1.'index.ejs' -- 所有微前端应用共享的根 HTML 页面,主要利用systemjs 实现'importmap' 做到按需导入 2.2.'*-root-config.js' -- 这是注册微前端应用的配置文件 2.3.'webpack.config.js' -- webpack配置文件 ~~~ >[danger] ##### webpack.config.js ~~~ 1.这个文件主要导入了 "webpack-config-single-spa",一个可共享的、可定制的 webpack 配置,用于实用程序模块 和 single-spa 应用程序,就是已经帮忙做好的关于'single-spa的webpack 文件' 2.对下面'disableHtmlGeneration' 变量做一个说明,默认为false,禁用html-webpack-plugin (and standalone-single-spa-webpack-plugin)。下面案例设置为true 是因为自己配置了'html-webpack-plugin' ~~~ ~~~ const { merge } = require("webpack-merge"); const singleSpaDefaults = require("webpack-config-single-spa"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = (webpackConfigEnv, argv) => { const orgName = "study"; const defaultConfig = singleSpaDefaults({ orgName, projectName: "root-config", webpackConfigEnv, argv, disableHtmlGeneration: true, }); return merge(defaultConfig, { // modify the webpack config however you'd like to by adding to this object plugins: [ new HtmlWebpackPlugin({ inject: false, template: "src/index.ejs", // 模版需要的参数,这里也就是为什么在'index.ejs' 会看到'isLocal' 变量名 templateParameters: { isLocal: webpackConfigEnv && webpackConfigEnv.isLocal, orgName, }, }), ], }); }; ~~~ [官方介绍](https://single-spa.js.org/docs/create-single-spa/#create-single-spa) >[danger] ##### study-root-config.js -- 文件 ~~~ 1.创建完基座后会出现一个'study-root-config.js' 文件,这是注册微前端应用的配置文件,可以理解成路由文件 不过这个路由控制的是对应微前端展现对应项目 2.这个文件的名字来源,其实来自'webpack.config.js'文件中的'webpack-config-single-spa'配置 中,也就是其对webpack 的入口文件命名,点开'webpack-config-single-spa'就可以看到。 entry: path.resolve( process.cwd(), `src/${opts.orgName}-${opts.projectName}` ), 其中打包入口地址可以看到是'orgName' 和'projectName' 拼接得到的,这两个属性不难发现其实 在'webpack.config.js' 中设置的。到此这里变明白了'study-root-config.js'文件名字是如何生成的 ~~~ ~~~ import { registerApplication, start } from "single-spa"; /* 注册微前端应用 1.name: 字符串类型, 微前端应用名称 "@组织名称/应用名称" 2.app: 函数类型, 返回 Promise, 通过 systemjs 引用打包好的微前端应用模块代码 (umd) 3.activeWhen: 路由匹配时激活应用 */ registerApplication({ name: "@study/root-config", app: () => System.import( "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js" ), activeWhen: ["/react"], }); // start 方法必须在 single spa 的配置文件中调用 // 在调用 start 之前, 应用会被加载, 但不会初始化, 挂载或卸载. start({ // 是否可以通过 history.pushState() 和 history.replaceState() 更改触发 single-spa 路由 // true 不允许 false 允许 urlRerouteOnly: true, }); ~~~ >[danger] ##### index.ejs ~~~ 1.所有微前端应用共享的根 HTML 页面,主要利用systemjs 实现'importmap' 做到按需导入 ~~~ ~~~html <!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> <!-- async/await 解析包--> <script src="https://cdn.jsdelivr.net/npm/regenerator-runtime@0.13.7/runtime.min.js"></script> <!-- This CSP allows any SSL-enabled host and for arbitrary eval(), but you should limit these directives further to increase your app's security. Learn more about CSP policies at https://content-security-policy.com/#directive --> <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';" /> <meta name="importmap-type" content="systemjs-importmap" /> <!-- 当加载基座时,需要导入single-spa --> <script type="systemjs-importmap"> { "imports": { "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js" } } </script> <link rel="preload" href="https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js" as="script" /> <!-- Add your organization's prod import map URL to this script's src --> <!-- <script type="systemjs-importmap" src="/importmap.json"></script> --> <% if (isLocal) { %> <script type="systemjs-importmap"> { "imports": { "@study/root-config": "//localhost:9000/study-root-config.js" } } </script> <% } %> <!-- 需要systemJS模块化和amd解析 --> <script src="https://cdn.jsdelivr.net/npm/import-map-overrides@2.2.0/dist/import-map-overrides.js"></script> <% if (isLocal) { %> <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.js"></script> <% } else { %> <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.min.js"></script> <% } %> </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <main></main> <script> // 加载基座应用 System.import('@study/root-config') </script> <import-map-overrides-full show-when-local-storage="devtools" dev-libs ></import-map-overrides-full> </body> </html> ~~~ * [Configuring single-spa官网对参数的介绍](https://zh-hans.single-spa.js.org/docs/configuration) >[danger] ##### 不通过脚手架创建基座步骤 ~~~ 1.通过脚手架创建的基座基本已经明确了一个基座创建主要几个解析文件 1.1.'webpack-config.js'作为使用webpack打包配置文件,因此相对的主要需要的几个包'wepabck' 'webpack-cli',还用html作为模板相对的也需要' html-webpack-plugin',通过官方提供的将一些基础 配置已经配置好的'webpack-config-single-spa' 1.2.到了webpack入口文件,'src/*-*.js' 上面分析过这两个'*' 具体名字是根据.'webpack-config.js' 中'orgName' 和'projectName' 拼接得到的,也可以覆盖自定义,入口文件主要是为了作为 各个路由调用微前端的映射文件这里需要"single-spa" 这个包来帮助 2.分析所需要的包和各个文件之间的关系,如果没有脚手架,想自己生成基座文件相对的第一步 安装上面提到的文件,按照对应规则创建文件即可 ~~~ >[info] ## 创建微应用 -- 子应用的构建 ~~~ 1.已经明确了'single-spa' 基座应用是类似路由映射,当我们创建好了子应用的时候,需要做的 是在基座应用的入口做好路由映射举个例子和基座联动,注册微应用在'*-*-config.js' 中 registerApplication({ name: "@study/vue", app: () => System.import("@study/vue-app"), activeWhen: location => location.pathname.startsWith('/vue'), }); 下一步需要在'index.ejs' 声明打包后的文件,例如下面vue项目启动的端口是3000,并且vuecli打包后的默认文件都是 在'js/app.js' <% if (isLocal) { %> <script type="systemjs-importmap"> { "imports": { "@study/root-config": "//localhost:9000/study-root-config.js", "@study/vue-app":"//localhost:3000/js/app.js" } } </script> <% } %> 2.'single-spa' 微应用的解决实现方案,对主流框架依旧提供了对应的封装配置包例如 'webpack-config-single-spa-react'、'single-spa-vue' ,只要按照规则依旧可以对进行配置, 非主流框架可以按照下面步骤来做整体和基座的解决思路类似 3.子应用对应的需要对外暴露'bootstrap' 、'mount'、'unmount' 声明周期 4.关于'app: System.import()'中值名字,其实这个是'System.js' 导入的用法,当你将 一个包打包成'system.js'规范的时候,注册名字以spa-vue 的脚手架为例,当你通过打印 'npx vue-cli-service inspect --mode production > aa.js' 就可以发现'SystemJSPublicPathWebpackPlugin' 这里做了对应的映射,如果在细看会在'node_modules'找'vue-cli-plugin-single-spa',跟具体的配置可以发现这个名字 来源的代码如下 const packageJsonPath = api.resolve("package.json"); const { name } = require(packageJsonPath); 即也就是你注册对应项目的'package.json' 文件中的name ~~~ >[danger] ##### 非框架的微应用 ~~~ 1.只需要将你的webpack 配置和提供的webpack配置做合并即可 ~~~ ~~~js const { merge } = require("webpack-merge") const singleSpaDefaults = require("webpack-config-single-spa") module.exports = () => { const defaultConfig = singleSpaDefaults({ // 组织名称 orgName: "study", // 项目名称 projectName: "root" }) return merge(defaultConfig, { devServer: { port: 9001 } }) } ~~~ >[danger] ##### vue 创建的微前端主要配置 -- main.js ~~~js import Vue from "vue"; import singleSpaVue from "single-spa-vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; Vue.config.productionTip = false; const vueLifecycles = singleSpaVue({ Vue, appOptions: { render (h) { 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 // if you uncomment these, remember to add matching prop definitions for them in your App.vue file. /* name: this.name, mountParcel: this.mountParcel, singleSpa: this.singleSpa, */ }, }); }, router, store, }, }); export const bootstrap = vueLifecycles.bootstrap; export const mount = vueLifecycles.mount; export const unmount = vueLifecycles.unmount; ~~~ >[info] ## 总结 ~~~ 1.在使用就像名字应'single-spa' 做的一种单页面微前端的形式,通过一个基座,要创建一个关于子应用的路由注册 表,然后根据路由注册表使用 single-spa 提供的 registerApplication 方法注册子应用,最后在基座应用挂载完成 ,执行 single-spa 提供的 start 方法即可,各个微前端子应用配合官方提供的配置,并暴露出'bootstrap' 'mount'、'unmount' 这些暴露的属性最终和基座配合控制各个微前端交互 ~~~