微前端
微前端,前端这次词就不必多做解释了,这个概念的重点在于这个“微”字, 从字面意义上看,微是小的意思,小是绝对于大的一个用于比拟的形容词,所以通常是在我的项目宏大的状况下,才会思考将它变小,去思考将它拆分成若干个小我的项目。这就是做微前端所要达到的次要指标,将宏大的我的项目拆分成多个独立运行、独立部署和独立开发的小我的项目,使得我的项目利于保护和更新,而后在运行时,作为一个整体来出现。
我的项目过于宏大的可能存在一系列问题,比方构建速度慢、利用加载慢、定位问题麻烦、我的项目可维护性差等等。
晚期
过往的案例中,通常会应用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-spa
npx: 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.js
export 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.js
registerApplication({
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-spa
npx: 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.js
registerApplication({
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.js
registerApplication({
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.js
import {
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.js
import { BrowserRouter } from "react-router-dom";
import App from './components/App';
export default function Root(props) {
return (
<BrowserRouter basename={"/students"}>
<App />
</BrowserRouter>
);
}
// src/components/App.js
import 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.js
import React from 'react';
function Detail(props) {
return (
<h3>我是Detail的内容</h3>
);
}
export default Detail;
// src/components/List.js
import React from 'react';
function List(props) {
return (
<h3>我是List的内容</h3>
);
}
export default List;
// src/routes/index.js
import 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的话因为没有沙箱环境,可能会呈现款式相互烦扰的状况,须要去解决。因为我还没有具体的实践经验,那在理论应用中还可能呈现一些问题,比方如何去拆分一个大型项目。
对于是否应用微前端,通过何种形式、何种框架来实际微前端,能够借鉴别人教训,依据理论状况来整体考量。
发表回复