微前端

微前端,前端这次词就不必多做解释了,这个概念的重点在于这个“微”字, 从字面意义上看,微是小的意思,小是绝对于大的一个用于比拟的形容词,所以通常是在我的项目宏大的状况下,才会思考将它变小,去思考将它拆分成若干个小我的项目。这就是做微前端所要达到的次要指标,将宏大的我的项目拆分成多个独立运行、独立部署和独立开发的小我的项目,使得我的项目利于保护和更新,而后在运行时,作为一个整体来出现。

我的项目过于宏大的可能存在一系列问题,比方构建速度慢、利用加载慢、定位问题麻烦、我的项目可维护性差等等。

晚期

过往的案例中,通常会应用iframe作为微前端的一种解决方案,但iframe有一些显著的毛病,比方浏览器的后退后退,因为iframe的url不会显示在浏览器的地址栏,就会使后退后退看上去有点奇怪,因为如果iframe存在历史记录,就会先对iframe的历史记录进行后退后退操作,但在地址栏上不会体现进去;并且咱们也不能通过地址栏在主利用中间接关上子利用中某个页面,并且刷新也存在问题(子利用的状态会失落);还有iframe是与内部窗口隔离开的,如果有弹窗须要遮罩层,就会使款式很奇怪,因为遮罩层只会遮住iframe的局部;而后iframe与内部窗口通信也不是很不便。当然iframe还是有长处的,比方资源隔离,款式之间不会相互影响,还能够加载内部页面,这在晚期也算比拟好用的一种计划。

<!-- index.html --><!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>主利用</title></head><body><style>    body {        font-size: 22px;        color: #666;        background-color: #f4f5ff;    }</style><p>我是主页面的内容</p><iframe src="./p1.html"></iframe></body></html><!-- p1.html --><!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>P1利用</title></head><body><style>    body {        font-size: 20px;        color: orange;        background-color: #e4e5ff;    }</style><p>    我是p1的内容    <button onclick="window.location.href='./p2.html'">点击跳转p2</button>    <button onclick="window.location.href='https://126.com'">点击跳转126邮箱</button></p></body></html><!-- p2.html --><!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>P2利用</title></head><body><style>    body {        font-size: 20px;        color: blue;        background-color: #e4e5ff;    }</style><p>    我是p2的内容    <button onclick="window.location.href='./p1.html'">点击跳转p1</button></p></body></html>

尽管以前没有微前端的概念,但其实很多平台类的利用都存在对微前端的实际,他们会在主利用中提供入口,让用户能够进入其余利用去应用其余性能,而不是把所有的性能、所有的货色都放在一个利用外面。

single-spa

single-spa是近几年进去的微前端框架,带火了微前端的概念,我还没在我的项目中正式应用过,当初刚开始接触学习。它次要参考了近几年风行框架中路由的概念,来推出的一种计划。

借助single-spa脚手架,咱们能够搭建三类我的项目:

  • 一个是蕴含root config的我的项目,能够说它是将所有我的项目包裹起来的主我的项目,能够将它看作是一个利用容器,在root config我的项目中蕴含一个名为xxx-root-config的js文件和一个index.ejs文件,这个ejs文件就是所有子利用所共用的html页面,root config我的项目通常配合应用single-spa-layout来帮忙更不便地对利用布局、注册利用和定义路由
  • 第二类是一般的利用或者parcel组件,也就是一般的子利用,能够基于任意框架开发,parcel通常用于编写能够跨框架应用的组件,parcel和application的定义比拟类似,只是应用上有所不同,application能够通过路由激活主动挂载,而parcel须要通过mountParcel或者mountRootParcel办法来手动挂载
  • 第三类是utility module,工具包,用于提供一些通用性能,比方款式、api等,通常没有组件须要渲染。

相干的概念,比方:Root Config、Application、Parcel、Layout Engine,都能够在single-spa的官网上查阅。

晓得了这些,就能够开始入手搭建简略的demo我的项目了。

1. 先来创立一个root config类型的我的项目,临时先不应用single-spa-layout

# ying.ye @ xieyingdeMacBook-Pro in ~/CodeProjects/mfe-demo1 [10:38:13] $ npx create-single-spanpx: 393 装置胜利,用时 79.396 秒? Directory for new project platform? Select type to generate single-spa root config? Which package manager do you want to use? yarn? Will this project use Typescript? No? Would you like to use single-spa Layout Engine No? Organization name (can use letters, numbers, dash or underscore) becky
directory,就是我的项目创立在哪个目录下,默认是点,就是当前目录,为了方便管理所有微前端利用,我把这个我的项目创立在platform目录,这个目录它会主动创立,select type,利用的类型,抉择single-spa root config,接下来是包管理器,抉择yarn,当然选其余的也能够,是否应用ts,因为是简略的demo,就不必了,是否应用single-spa-layout,临时抉择不必,最初是organization name,就是组织名称,我用我的英文名becky。

我的项目初始化实现后,看一下我的项目的内容,整体构造的话和一般脚手架生成的我的项目并没有太大的不同,咱们能够先关注src目录下的两个文件,index.ejs和becky-root-config.js。index.ejs就是之前说的子利用所共用的html页面,becky-root-config.js就是后面所说的root config文件,它的名字是由organization name和root config组合,应用横杆拼接起来的。

咱们能够先运行一下这个我的项目,关上localhost:9000。页面上有一串welcome之类的文字,就是一个欢送页面。然而咱们刚刚看了,我的项目里就两个文件,那这些内容是哪里来的呢?咱们能够再认真看下两个文件的内容。

首先是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>Root Config</title>  <!--    Remove this if you only support browsers that support async/await.    This is needed by babel to share largeish helper code for compiling async/await in older    browsers. More information at https://github.com/single-spa/create-single-spa/issues/112  -->  <script src="https://cdn.jsdelivr.net/npm/[email protected]/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" />  <!-- If you wish to turn off import-map-overrides for specific environments (prod), uncomment the line below -->  <!-- More info at https://github.com/joeldenning/import-map-overrides/blob/master/docs/configuration.md#domain-list -->  <!-- <meta name="import-map-overrides-domains" content="denylist:prod.example.com" /> -->  <!-- Shared dependencies go into this import map. Your shared dependencies must be of one of the following formats:    1. System.register (preferred when possible) - https://github.com/systemjs/systemjs/blob/master/docs/system-register.md    2. UMD - https://github.com/umdjs/umd    3. Global variable    More information about shared dependencies can be found at https://single-spa.js.org/docs/recommended-setup#sharing-with-import-maps.  -->  <script type="systemjs-importmap">    {      "imports": {        "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js"           }    }  </script>  <link rel="preload" href="https://cdn.jsdelivr.net/npm/[email protected]/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": {        "@becky/root-config": "//localhost:9000/becky-root-config.js"      }    }  </script>  <% } %>  <!--    If you need to support Angular applications, uncomment the script tag below to ensure only one instance of ZoneJS is loaded    Learn more about why at https://single-spa.js.org/docs/ecosystem-angular/#zonejs  -->  <!-- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/zone.min.js"></script> -->  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>  <% if (isLocal) { %>  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.js"></script>  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.js"></script>  <% } else { %>  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.min.js"></script>  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.min.js"></script>  <% } %></head><body>  <noscript>    You need to enable JavaScript to run this app.  </noscript>  <main></main>  <script>    System.import('@becky/root-config');  </script>  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full></body></html>

能够看到,这里应用了一些在一般我的项目里很少有看到应用的货色,比方类型为systemjs-importmap的script标签,它外面的内容是json,而后在其余script标签中,还应用了System.import办法。这波及到两块内容:importmap和systemjs。

importmap

importmap直译过去是导入映射,与模块的应用无关,个别咱们在我的项目中导入模块,会调用require办法,或者应用import语句或办法,引入的模块通常须要应用npm之类的包管理器进行治理。然而import map提供了一种反对,让咱们能够间接在页面上治理模块,不须要通过打包构建。不过因为这个个性比拟新,很多浏览器不反对,能够看一个小的示例。

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>import maps demo</title>    <script type="importmap">        {            "imports": {                "react": "https://cdn.skypack.dev/[email protected]",                "react-dom": "https://cdn.skypack.dev/react-dom",                "moment": "https://cdn.skypack.dev/moment"            }        }    </script></head><body>    <div id="root"></div>    <script type="module">        import React from 'react';        import ReactDOM from 'react-dom';        import moment from 'moment';        ReactDOM.render(`Hello World: ${moment().format('YYYY-MM-DD HH:mm:ss')}`, document.getElementById('root'));    </script></body></html>

独自运行这个页面,能够看到不会报错,是失常运行显示的,阐明咱们齐全不须要构建就能够应用import语句导入模块。

Import maps 实质上是一个配置文件,能够让开发者将模块标识符映射到一到多个文件,形容了依赖的解析形式,某种程度上,Import maps 给浏览器端带来了包治理,然而目前反对 Import Maps 的浏览器还很少。

简略来说importmap的作用就是使浏览器端反对模块的解析,而不须要利用构建步骤,这使得前端开发更便捷了,然而import maps当初来应用的话存在一个毛病,就是须要所有模块都导出成 ESModule,以后社区当中的很多模块都没有导出成 ESModule,有些模块甚至没有通过编译,所以目前应用依然有肯定艰难。

systemjs

systemjs能够说是import maps的一种兼容计划,同样有模块治理的性能,在浏览器端实现了对 CommonJS、AMD、UMD 等各种模块的加载;它提供了一套本人的加载办法,咱们能够对方才的demo做一些改变,来看看成果。

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>import maps demo</title>    <script type="systemjs-importmap">        {            "imports": {                "react": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js",                "react-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js",                "moment": "https://cdn.jsdelivr.net/npm/[email protected]/moment.min.js"            }        }    </script>    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.js"></script>    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/use-default.js"></script></head><body><div id="root"></div><script type="module">    const React = await System.import('react');    const ReactDOM = await System.import('react-dom');    const moment = await System.import('moment');    // console.log(moment);    ReactDOM.render(`Hello World: ${moment().format('YYYY-MM-DD HH:mm:ss')}`, document.getElementById('root'));</script></body></html>
首先将script的type改为systemjs-importmap,而后咱们改为应用umd的模块,并在页面上应用script标签引入systemjs,在浏览器中引入system.js后,会去解析类型为systemjs-importmap的script标签里的import映射。最初对这个类型为module的script标签中的内容进行批改,将import语句都改为应用System.import办法。因为调用System.import失去的是一个promise,咱们间接应用await来获取最终的内容。
log一下变量,能够看到调用System.import办法间接获取的是整个模块,为了应用更方面,咱们能够在页面上引入systemjs的use-default脚本,将模块的default局部提取进去。

再独自运行一下这个页面,能够看到,出现的成果是一样的。

对于importmap和systemjs就先到这里不细讲了,因为具体的我也还没认真看。

回到index.ejs,咱们看到,应用System.import导入了一个@becky/root-config,再看下面的importmap,咱们能够看到,这个标识符对应的就是becky-root-config.js文件。也就是说页面上援用了root config打包构建后的文件。

index.ejs文件的最初是一个import-map-overrides-full的标签,它是single-spa提供的一个开发工具,show-when-local-storage="devtools",阐明只有将localstorage中的devtools设置为true,就能够看到页面的右下角有一个图标。

点击这个图标能够关上一个面板,下面展现了浏览器治理的两个模块,就是咱们在importmap中定义的,通过这个工具,咱们能够对利用中定义的importmap映射地址进行批改替换,不便本地调试。当然咱们也能够通过装置浏览器插件的形式来进行调试,对于chrome能够装置一个single-spa-inspector的插件来治理所有主利用下的子利用。

index.ejs看完了,再来看下root-config文件,这个文件很要害,咱们通过这个文件来注册利用。能够看到顶部导入了两个办法,一个是registerApplication,见名知意,就是注册利用的办法,一个是start,就是启动我的项目的办法。

import { registerApplication, start } from "single-spa";registerApplication({  name: "@single-spa/welcome",  app: () =>    System.import(      "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"    ),  activeWhen: ['/'],});//// registerApplication({//   name: '@becky/navbar',//   app: () => System.import("@becky/navbar"),//   activeWhen: "/"// })start({  urlRerouteOnly: true,});
能够看到,这里注册了一个子利用的示例,参数是一个对象,蕴含了三个属性,name、app和activeWhen,name就是名称,@single-spa/welcome ,app就是对应的利用,能够看到是导入了一个single-spa官网的用作示例的欢送页,最初是activeWhen,就是这个利用在什么条件下激活,默认是“/”,所以根路由被激活时就会显示,所以咱们刚刚拜访localhost:9000页面看到的内容就是它。
activeWhen能够是一个字符串、或者函数,也能够是一个数组,蕴含函数或字符串类型的元素,实质上是一个函数,如果是字符串或者数组中的字符串元素,理论会被解决成用location进行判断,判断location.pathname是否是这个字符串结尾的,如果是,路由就被激活,如果activeWhen是一个函数或者数组中蕴含的函数元素,则这个函数有一个默认传参是location,依据函数返回值判断路由对应的利用是否被激活。
所以activeWhen: ['/']相当于activeWhen: [(location) => location.pathname.startWith('/')]

能够看出single-spa是通过js文件接入子利用的。

查看元素看一下,能够看到页面上有一个div元素,id为single-spa-application:后边再跟一个利用名@single-spa/welcome,div里边就是欢送页的内容。

当初咱们在root-config文件中注册一个简略的利用,比方写一个app1,应用registerApplication注册。因为是我的项目中的文件,咱们间接调用require,或者()=>import,activeWhen咱们就用“/app1”。运行一下,拜访路由/app1,能够看到app1被挂载了,咱们写的内容也显示在页面上了。

// src/app1.jsexport const bootstrap = (props) => {    return Promise        .resolve()        .then(() => {            console.log('App1 bootstrapped!');        })}export const mount = (props) => {    console.log(props);    return Promise        .resolve()        .then(() => {            const ele = document.createElement('div');            ele.id="becky-app1";            ele.innerText='App1 mounted!!';            document.body.append(ele);            console.log('App1 mounted!');        })}export const unmount = (props) => {    return Promise        .resolve()        .then(() => {            console.log('App1 unmounted!');        })}// src/becky-root-config.jsregisterApplication({  name: '@becky/app11',  app: require('./app1'), // ()=>import('./app1)  activeWhen: '/app1'});

回到app1.js的内容,能够看到导出了几个办法,bootstrap、mount和unmount,通过single-spa官网对利用的定义,咱们理解到,导出利用必须要定义这三个生命周期办法,来对利用的启动、挂载和卸载的过程做一些解决。当然如果需要的话,咱们还能够定义unload生命周期办法。这些生命周期办法能够接管到一些属性,咱们能够通过log来打印查看。

2.子利用

当然理论我的项目必定没这么简略,咱们再来创立几个我的项目,来作为子利用。同样应用single-spa的脚手架来创立。

# ying.ye @ xieyingdeMacBook-Pro in ~/CodeProjects/mfe-demo1 [10:38:13] $ npx create-single-spanpx: 393 装置胜利,用时 44.124 秒? Directory for new project navbar? Select type to generate single-spa application / parcel? Which framework do you want to use? react? Which package manager do you want to use? yarn? Will this project use Typescript? No? Organization name (can use letters, numbers, dash or underscore) becky? Project name (can use letters, numbers, dash or underscore) navbar
同样的,须要抉择directory,就是我的项目创立在什么目录下,默认是点,我把这个我的项目创立在navbar目录,作为整个利用的导航栏,select type,利用的类型,抉择application or parcel,应用的ui框架,抉择react,接下来是包管理器,抉择yarn,是否应用ts,简略的demo就不必了,临时抉择不必,organization name,还是应用我的英文名becky,最初是project name,在root config我的项目创立的时候脚手架给设置了默认我的项目名root config,一般的子利用咱们能够本人设置我的项目名,还是设置navbar。

同样的步骤咱们再创立几个我的项目。

在子项目中执行yarn:start --port 8081命令,默认start用的端口是8080,因为要跑多个子项目,咱们通过--port来指定端口,通过8081咱们并不能间接拜访指标页面,关上localhost:8081,咱们能够看到一个页面,下面的文字提醒咱们须要在一个root config利用下来预览这个子利用页面,或者执行yarn start:standalone命令来预览,后面曾经跑起来一个root config我的项目了,咱们能够尝试间接把这个子利用注册到root config中。

先复制localhost:8081页面上提醒咱们复制的URL地址。

这次注册咱们间接应用System.import,因为这个地址蕴含的内容是打包后的模块。

// src/becky-root-config.jsregisterApplication({  name: '@becky/navbar',  app: () => System.import("http://localhost:8081/becky-navbar.js"),  activeWhen: "/"})

此时刷新页面后,会发现页面是空白的,控制台报了一个谬误,提醒无奈解析‘react’这个标识符,这是因为咱们的子利用应用了react框架,然而子利用应用webpack构建时是应用externals把react设置为内部模块,所以无奈解析,此时咱们能够抉择在root config我的项目下的index.ejs文件中,应用importmap来引入react。

<!-- index.ejs --><script type="systemjs-importmap">    {      "imports": {        "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/system/single-spa.min.js",        "react": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js",        "react-dom": "https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"             }    }</script>

再次刷新页面,咱们就能够看到页面有@becky/navbar is mounted!的字样,代表子利用挂载胜利了。

为了治理不便,咱们把navbar的模块也放到importmap中。

<!-- src/index.ejs --><% if (isLocal) { %>  <script type="systemjs-importmap">    {      "imports": {        "@becky/root-config": "//localhost:9000/becky-root-config.js",        "@becky/navbar": "http://localhost:8081/becky-navbar.js"      }    }  </script><% } %>
// src/becky-root-config.jsregisterApplication({  name: '@becky/navbar',  app: () => System.import("@becky/navbar"),  activeWhen: "/"})

3. 应用single-spa-layout

刚刚咱们创立的子利用能够都这样间接调用registerApplication来注册,然而这样一个个注册不太不便,页面布局也不太直观,而且刚刚间接拜访根门路时,咱们看到navbar是在顶部,然而拜访app1的时候,navbar又在app1的上面,为了不便布局和注册利用,咱们能够应用single-spa-layout这个库。通过应用这个库,咱们还能够定义利用加载时的过渡成果。

# 在root config我的项目下装置依赖yarn add single-spa-layout

而后对咱们的index.ejs做一些批改。咱们能够把官网的demo复制过去,而后做一点批改,把刚刚创立的几个子利用运行一下,并且把对应模块放到importmap中。

<!-- index.ejs --><% if (isLocal) { %>  <script type="systemjs-importmap">    {      "imports": {        "@becky/root-config": "//localhost:9000/becky-root-config.js",        "@becky/navbar": "http://localhost:8081/becky-navbar.js",        "@becky/settings": "http://localhost:8082/becky-settings.js",        "@becky/students": "http://localhost:8083/becky-students.js"      }    }  </script>  <% } %><!-- ... --><template id="single-spa-layout">    <single-spa-router>      <nav class="topnav">        <application name="@becky/navbar"></application>      </nav>      <div class="main-content">        <route path="settings">          <application name="@becky/settings"></application>        </route>        <route path="students">          <application name="@becky/students"></application>        </route>      </div>    </single-spa-router></template>

而后批改咱们的root config文件。同样的把官网中的demo代码复制过去。

// src/becky-root-config.jsimport {  constructApplications,  constructRoutes,  constructLayoutEngine,} from 'single-spa-layout';const routes = constructRoutes(document.querySelector('#single-spa-layout'));const applications = constructApplications({  routes,  loadApp({ name }) {    return System.import(name);  },});const layoutEngine = constructLayoutEngine({ routes, applications });applications.forEach(registerApplication);

刷新页面,通过拜访咱们应用route标签定义的路由,能够激活不同的子利用。查看元素时,能够看到子利用挂载的布局与咱们定义的single-spa-router中的内容是保持一致的。

此时一个简略的demo就实现了。

4. 减少简略的路由跳转

接下来咱们能够批改一下navbar,就不必手动去改地址栏了。

关上navbar我的项目,间接查看src上面的文件,这个test文件是用于测试的,能够先不论,becky-navbar.js就是定义利用的主文件,能够看到应用了single-spa-react这个库,来创立single-spa利用,要批改navbar的展现内容,咱们次要看rootComponent这个参数,它指定了利用的根组件,也就是说子利用的内容都是在这个组件外面,咱们看到根组件就是这个root.component.js。

关上这个文件,咱们做一点批改。首先咱们看这个根组件,它接管了一个props参数,外面蕴含了跟利用相干的一些信息,比方props.name,就是利用名称,刚刚显示在页面上了。

export default function Root(props) {  const onClick = (path) => {    window.singleSpaNavigate(path);  }  return (      <section>        <li>          <a href="" onClick={ () => onClick('/settings')}>Settings</a>        </li>        <li>          <a href="" onClick={ () => onClick('/students')}>Students</a>        </li>      </section>  );}

实现批改后就能够看到成果了,点击不同的链接能够跳转不同的路由。

咱们能够持续尝试在子利用中配置子利用本人的路由。比方students我的项目,咱们给他增加路由性能。

首先增加react-router-dom依赖,再对root.component进行革新。再增加几个react组件,并配置一张路由表。因为子利用的路由是基于主利用的路由,所以给BrowserRouter配置一个basename属性,/students。
// src/root.component.jsimport { BrowserRouter } from "react-router-dom";import App from './components/App';export default function Root(props) {  return (      <BrowserRouter basename={"/students"}>        <App />      </BrowserRouter>  );}// src/components/App.jsimport React from 'react';import {NavLink, useRoutes, useInRouterContext} from 'react-router-dom';import routes from "../routes";function App(props) {    console.log('xxx', useInRouterContext())    // 依据路由表生成对应的路由规定    const element = useRoutes(routes);    return (        <div>            <div className="row">                <div className="col-xs-2 col-xs-offset-2">                    <div className="list-group">                        {/* 路由链接 */}                        <NavLink className="list-group-item" to="/list">List</NavLink>                        <NavLink className="list-group-item" end to="/detail">Detail</NavLink>                    </div>                </div>                <div className="col-xs-6">                    <div className="panel">                        <div className="panel-body">                            {/*  在展现路由组件的地位注册路由  */}                            {element}                        </div>                    </div>                </div>            </div>        </div>    );}export default App;// src/components/Detail.jsimport React from 'react';function Detail(props) {    return (        <h3>我是Detail的内容</h3>    );}export default Detail;// src/components/List.jsimport React from 'react';function List(props) {    return (        <h3>我是List的内容</h3>    );}export default List;// src/routes/index.jsimport List from "../components/List";import Detail from "../components/Detail";export default [    {        path: '/list',        element: <List/>    },    {        path: '/detail',        element: <Detail/>,    }]

革新实现后,重启students我的项目。再持续看,能够失常运行并实现路由跳转。

简略的demo就到此为止了。

5. webpack的配置

最初咱们再看一下webpack的配置。文件很简略,因为single-spa这个框架做了一层封装,咱们能够在students我的项目把配置打印进去看一下。

次要看一下output和externals,externals配置的是援用内部的模块,能够看到single-spa、react、react-dom这些三方库都是援用的内部模块,也就是利用了咱们在importmap中配置的模块映射,还有@becky结尾的模块标识符也是援用了内部的模块,这些也是在importmap中配置了。

而后是output,看到输入的文件是becky-students.js,也就是importmap映射的子利用的文件。libraryTarget是system,他指定了应用system的模式解决模块,咱们能够从network再看一下页面加载的becky-student.js的内容,能够看到文件的结尾就调用了System.register注册了模块,这又波及到了systemjs的内容。

最初

简略说了下微前端、以及single-spa的根底应用。有趣味同学的能够持续查阅single-spa的官网文档、以及其余优良的文章。

最初再说些微前端的毛病,比方子利用如果保护在不同的代码库里,这可能会造成代码扩散,不利于整体的治理,对管理者要求更高,如果有新需要迭代,须要盘点确认波及哪些子利用,还可能呈现三方库版本不同所造成的保护问题。应用single-spa的话因为没有沙箱环境,可能会呈现款式相互烦扰的状况,须要去解决。因为我还没有具体的实践经验,那在理论应用中还可能呈现一些问题,比方如何去拆分一个大型项目。

对于是否应用微前端,通过何种形式、何种框架来实际微前端,能够借鉴别人教训,依据理论状况来整体考量。