mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-05 19:41:57 +08:00
fix: conflict
This commit is contained in:
commit
02231d3335
@ -19,6 +19,7 @@ const headPkgs = [
|
||||
"fes-plugin-jest",
|
||||
"fes-plugin-vuex",
|
||||
"create-fes-app",
|
||||
"fes-plugin-qiankun"
|
||||
];
|
||||
const tailPkgs = [];
|
||||
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(
|
||||
|
@ -60,6 +60,7 @@ export const en: SidebarConfig = {
|
||||
'/reference/plugin/plugins/model.md',
|
||||
'/reference/plugin/plugins/request.md',
|
||||
'/reference/plugin/plugins/vuex.md',
|
||||
'/reference/plugin/plugins/qiankun.md',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -60,6 +60,7 @@ export const zh: SidebarConfig = {
|
||||
'/zh/reference/plugin/plugins/model.md',
|
||||
'/zh/reference/plugin/plugins/request.md',
|
||||
'/zh/reference/plugin/plugins/vuex.md',
|
||||
'/zh/reference/plugin/plugins/qiankun.md',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -20,10 +20,12 @@ export default {
|
||||
|
||||
### mode
|
||||
创建历史记录的类型:
|
||||
- **h5**,对应 [createWebHistory](https://next.router.vuejs.org/zh/api/#createwebhistory)
|
||||
- **history**,对应 [createWebHistory](https://next.router.vuejs.org/zh/api/#createwebhistory)
|
||||
- **hash**,对应 [createWebHashHistory](https://next.router.vuejs.org/zh/api/#createWebHashHistory)
|
||||
- **memory**,对应 [createMemoryHistory](https://next.router.vuejs.org/zh/api/#createWebHashHistory)
|
||||
|
||||
默认是`hash`模式。
|
||||
|
||||
## 约定式路由
|
||||
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
|
||||
|
||||
|
@ -1,8 +1,67 @@
|
||||
# @fesjs/plugin-model
|
||||
|
||||
|
||||
## 启用方式
|
||||
在 package.json 中引入依赖:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-model": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
## 介绍
|
||||
一种简易的数据管理方案。我们知道 Vue 的理念是用响应式数据驱动UI更新,提供 `reactive` 、 `ref` 等API把数据变成响应式的。我们使用`Provide / Inject`特性,在应用实例中共享响应式数据。
|
||||
|
||||
## 配置
|
||||
我们约定`src/models` 目录下的文件为项目定义的 `model` 文件。每个文件需要默认导出一个 `function`。
|
||||
|
||||
文件名则对应最终 `model` 的 `name`,你可以通过插件提供的 `API` 来消费 `model` 中的数据。
|
||||
|
||||
### Model 文件
|
||||
**src/models/useAuthModel.js**
|
||||
```js
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export default function useAuthModel() {
|
||||
const user = reactive({});
|
||||
|
||||
const signin = ()=>{
|
||||
// todo
|
||||
}
|
||||
|
||||
const signout = ()=>{
|
||||
// todo
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
signin,
|
||||
signout
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在组件中使用 Model
|
||||
```vue
|
||||
<script>
|
||||
import { useModel } from "@fesjs/fes"
|
||||
export default {
|
||||
setup(){
|
||||
const { user, signin, signout } = useModel("useAuthModel")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### useModel
|
||||
|
||||
**useModel(name)**
|
||||
- **类型**:函数
|
||||
|
||||
- **详情**: 获取 Model 数据,也就是 Model 文件默认导出函数执行的结果。
|
||||
- **参数**:
|
||||
- name,传入 Model 文件名
|
||||
|
||||
## API
|
247
docs/reference/plugin/plugins/qiankun.md
Normal file
247
docs/reference/plugin/plugins/qiankun.md
Normal file
@ -0,0 +1,247 @@
|
||||
# @fesjs/plugin-qiankun
|
||||
|
||||
Fes.js plugin for [qiankun](https://qiankun.umijs.org/),参考[@umijs/plugin-qiankun](https://umijs.org/zh-CN/plugins/plugin-qiankun#MicroApp) 实现,喜欢 React 的同学推荐直接用 Umi。
|
||||
|
||||
## 启用方式
|
||||
在 `package.json` 中引入依赖:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-qiankun": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 介绍
|
||||
有一种痛叫接手老项目,技术栈老旧,内容多,还要继续维护~
|
||||
|
||||
可能目前迁移、升级老项目最好的解决方案就是微前端。`plugin-qiankun` 是基于 `qiankun` 实现的 Fes.js 微前端解决方案。
|
||||
|
||||
## 主应用配置
|
||||
|
||||
### 第一步:注册子应用
|
||||
```js
|
||||
export default {
|
||||
qiankun: {
|
||||
main: {
|
||||
// 注册子应用信息
|
||||
apps: [
|
||||
{
|
||||
name: 'app1', // 唯一 id
|
||||
entry: '//localhost:8001', // html entry
|
||||
props: {} // 传递给子应用的数据
|
||||
},
|
||||
{
|
||||
name: 'app2', // 唯一 id
|
||||
entry: '//localhost:8002', // html entry
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 第二步:装载子应用
|
||||
|
||||
#### 使用路由绑定的方式
|
||||
:::warning
|
||||
主应用和子应用需要自行适配路由路径!!!待完善...
|
||||
:::
|
||||
|
||||
假设我们的系统之前有这样的一些路由:
|
||||
```js
|
||||
export default {
|
||||
router: {
|
||||
routes: [{
|
||||
"path": "/",
|
||||
"component": () => import('@/src/.fes/plugin-layout/index.js'),
|
||||
"children": [
|
||||
{
|
||||
"path": "/onepiece",
|
||||
"component": () => import('@/pages/onepiece'),
|
||||
"name": "onepiece",
|
||||
"meta": {
|
||||
"name": "onepiece",
|
||||
"title": "onepiece"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
我们现在想在 `/son` 加载子应用 `app1`,只需要增加这样一些配置即可:
|
||||
```js {16-23}
|
||||
export default {
|
||||
router: {
|
||||
routes: [{
|
||||
"path": "/",
|
||||
"component": () => import('@/src/.fes/plugin-layout/index.js'),
|
||||
"children": [
|
||||
{
|
||||
"path": "/onepiece",
|
||||
"component": () => import('@/pages/onepiece'),
|
||||
"name": "onepiece",
|
||||
"meta": {
|
||||
"name": "onepiece",
|
||||
"title": "onepiece"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/son",
|
||||
"meta": {
|
||||
"name": "son",
|
||||
"title": "子应用",
|
||||
"microApp": "app1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
当前我们依然提倡约定路由的方式,在`src/pages` 目录新建 `son.vue`:
|
||||
```vue
|
||||
<config>
|
||||
{
|
||||
"name": "son",
|
||||
"title": "子应用",
|
||||
"microApp": "app1"
|
||||
}
|
||||
</config>
|
||||
```
|
||||
|
||||
|
||||
#### 使用 `<MicroApp />` 组件的方式
|
||||
:::tip
|
||||
建议使用这种方式来引入不带路由的子应用。 否则请自行关注子应用依赖的路由跟当前浏览器 url 是否能正确匹配上,否则很容易出现子应用加载了,但是页面没有渲染出来的情况。
|
||||
:::
|
||||
```vue
|
||||
<template>
|
||||
<MicroApp :name="name" />
|
||||
</template>
|
||||
<script>
|
||||
import { MicroApp } from '@fesjs/fes';
|
||||
|
||||
export default {
|
||||
components: { MicroApp },
|
||||
setup(){
|
||||
const name = "app1"
|
||||
return {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## 子应用配置
|
||||
|
||||
### 第一步:插件注册
|
||||
```js
|
||||
export default {
|
||||
qiankun: {
|
||||
micro: {},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 第二步:配置运行时生命周期钩子(可选)
|
||||
插件会自动为你创建好 `qiankun` 子应用需要的生命周期钩子,但是如果你想在生命周期期间加一些自定义逻辑,可以在子应用的 `src/app.js` 里导出 `qiankun` 对象,并实现每一个生命周期钩子,其中钩子函数的入参 `props` 由主应用自动注入。
|
||||
```js
|
||||
export const qiankun = {
|
||||
// 应用加载之前
|
||||
async bootstrap(props) {
|
||||
console.log('app1 bootstrap', props);
|
||||
},
|
||||
// 应用 render 之前触发
|
||||
async mount(props) {
|
||||
console.log('app1 mount', props);
|
||||
},
|
||||
// 当 props 更新时触发
|
||||
async update(props){
|
||||
console.log('app1 update,' props);
|
||||
},
|
||||
// 应用卸载之后触发
|
||||
async unmount(props) {
|
||||
console.log('app1 unmount', props);
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 父子应用通讯
|
||||
|
||||
有两种方式实现
|
||||
|
||||
### 配合 [useModel](./model.md) 使用
|
||||
|
||||
确保已经安装了 `@fesjs/plugin-model`:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-model": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 主应用传递 props
|
||||
|
||||
- 如果使用 `MicroApp` 组件模式消费子应用,直接通过 props 传递即可:
|
||||
```vue
|
||||
<template>
|
||||
<MicroApp :name="name" :user="user" />
|
||||
</template>
|
||||
<script>
|
||||
import { MicroApp } from '@fesjs/fes';
|
||||
|
||||
export default {
|
||||
components: { MicroApp },
|
||||
setup(){
|
||||
const name = "app1"
|
||||
const user = ref("")
|
||||
return {
|
||||
name,
|
||||
user
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- 如果使用路由绑定式消费子应用,那么约定`src/models/qiankunStateForMicro.js` 的模型数据将作为 `props` 船体给子应用,如:
|
||||
```js
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export default () => {
|
||||
const state = reactive({ c: 1 });
|
||||
return {
|
||||
state
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### 子应用消费 props
|
||||
|
||||
子应用中会自动生成一个全局名为 `qiankunStateFromMain` 的 `model`, 可以在任意组件中获取主应用透传的 `props` 的值。
|
||||
|
||||
```vue
|
||||
<script>
|
||||
export default {
|
||||
setup(){
|
||||
const mainState = useModel('qiankunStateFromMain');
|
||||
return {
|
||||
mainState
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 基于 props 传递
|
||||
|
||||
- 主应用使用 props 的模式传递数据(参考主应用装载子应用配置一节)
|
||||
- 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)
|
@ -20,10 +20,12 @@ export default {
|
||||
|
||||
### mode
|
||||
创建历史记录的类型:
|
||||
- **h5**,对应 [createWebHistory](https://next.router.vuejs.org/zh/api/#createwebhistory)
|
||||
- **history**,对应 [createWebHistory](https://next.router.vuejs.org/zh/api/#createwebhistory)
|
||||
- **hash**,对应 [createWebHashHistory](https://next.router.vuejs.org/zh/api/#createWebHashHistory)
|
||||
- **memory**,对应 [createMemoryHistory](https://next.router.vuejs.org/zh/api/#createWebHashHistory)
|
||||
|
||||
默认是`hash`模式。
|
||||
|
||||
## 约定式路由
|
||||
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
|
||||
|
||||
|
@ -1,8 +1,67 @@
|
||||
# @fesjs/plugin-model
|
||||
|
||||
|
||||
## 启用方式
|
||||
在 package.json 中引入依赖:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-model": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
## 介绍
|
||||
一种简易的数据管理方案。我们知道 Vue 的理念是用响应式数据驱动UI更新,提供 `reactive` 、 `ref` 等API把数据变成响应式的。我们使用`Provide / Inject`特性,在应用实例中共享响应式数据。
|
||||
|
||||
## 配置
|
||||
我们约定`src/models` 目录下的文件为项目定义的 `model` 文件。每个文件需要默认导出一个 `function`。
|
||||
|
||||
文件名则对应最终 `model` 的 `name`,你可以通过插件提供的 `API` 来消费 `model` 中的数据。
|
||||
|
||||
### Model 文件
|
||||
**src/models/useAuthModel.js**
|
||||
```js
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export default function useAuthModel() {
|
||||
const user = reactive({});
|
||||
|
||||
const signin = ()=>{
|
||||
// todo
|
||||
}
|
||||
|
||||
const signout = ()=>{
|
||||
// todo
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
signin,
|
||||
signout
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在组件中使用 Model
|
||||
```vue
|
||||
<script>
|
||||
import { useModel } from "@fesjs/fes"
|
||||
export default {
|
||||
setup(){
|
||||
const { user, signin, signout } = useModel("useAuthModel")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### useModel
|
||||
|
||||
**useModel(name)**
|
||||
- **类型**:函数
|
||||
|
||||
- **详情**: 获取 Model 数据,也就是 Model 文件默认导出函数执行的结果。
|
||||
- **参数**:
|
||||
- name,传入 Model 文件名
|
||||
|
||||
## API
|
247
docs/zh/reference/plugin/plugins/qiankun.md
Normal file
247
docs/zh/reference/plugin/plugins/qiankun.md
Normal file
@ -0,0 +1,247 @@
|
||||
# @fesjs/plugin-qiankun
|
||||
|
||||
Fes.js plugin for [qiankun](https://qiankun.umijs.org/),参考[@umijs/plugin-qiankun](https://umijs.org/zh-CN/plugins/plugin-qiankun#MicroApp) 实现,喜欢 React 的同学推荐直接用 Umi。
|
||||
|
||||
## 启用方式
|
||||
在 `package.json` 中引入依赖:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-qiankun": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 介绍
|
||||
有一种痛叫接手老项目,技术栈老旧,内容多,还要继续维护~
|
||||
|
||||
可能目前迁移、升级老项目最好的解决方案就是微前端。`plugin-qiankun` 是基于 `qiankun` 实现的 Fes.js 微前端解决方案。
|
||||
|
||||
## 主应用配置
|
||||
|
||||
### 第一步:注册子应用
|
||||
```js
|
||||
export default {
|
||||
qiankun: {
|
||||
main: {
|
||||
// 注册子应用信息
|
||||
apps: [
|
||||
{
|
||||
name: 'app1', // 唯一 id
|
||||
entry: '//localhost:8001', // html entry
|
||||
props: {} // 传递给子应用的数据
|
||||
},
|
||||
{
|
||||
name: 'app2', // 唯一 id
|
||||
entry: '//localhost:8002', // html entry
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 第二步:装载子应用
|
||||
|
||||
#### 使用路由绑定的方式
|
||||
:::warning
|
||||
主应用和子应用需要自行适配路由路径!!!待完善...
|
||||
:::
|
||||
|
||||
假设我们的系统之前有这样的一些路由:
|
||||
```js
|
||||
export default {
|
||||
router: {
|
||||
routes: [{
|
||||
"path": "/",
|
||||
"component": () => import('@/src/.fes/plugin-layout/index.js'),
|
||||
"children": [
|
||||
{
|
||||
"path": "/onepiece",
|
||||
"component": () => import('@/pages/onepiece'),
|
||||
"name": "onepiece",
|
||||
"meta": {
|
||||
"name": "onepiece",
|
||||
"title": "onepiece"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
我们现在想在 `/son` 加载子应用 `app1`,只需要增加这样一些配置即可:
|
||||
```js {16-23}
|
||||
export default {
|
||||
router: {
|
||||
routes: [{
|
||||
"path": "/",
|
||||
"component": () => import('@/src/.fes/plugin-layout/index.js'),
|
||||
"children": [
|
||||
{
|
||||
"path": "/onepiece",
|
||||
"component": () => import('@/pages/onepiece'),
|
||||
"name": "onepiece",
|
||||
"meta": {
|
||||
"name": "onepiece",
|
||||
"title": "onepiece"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/son",
|
||||
"meta": {
|
||||
"name": "son",
|
||||
"title": "子应用",
|
||||
"microApp": "app1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
当前我们依然提倡约定路由的方式,在`src/pages` 目录新建 `son.vue`:
|
||||
```vue
|
||||
<config>
|
||||
{
|
||||
"name": "son",
|
||||
"title": "子应用",
|
||||
"microApp": "app1"
|
||||
}
|
||||
</config>
|
||||
```
|
||||
|
||||
|
||||
#### 使用 `<MicroApp />` 组件的方式
|
||||
:::tip
|
||||
建议使用这种方式来引入不带路由的子应用。 否则请自行关注子应用依赖的路由跟当前浏览器 url 是否能正确匹配上,否则很容易出现子应用加载了,但是页面没有渲染出来的情况。
|
||||
:::
|
||||
```vue
|
||||
<template>
|
||||
<MicroApp :name="name" />
|
||||
</template>
|
||||
<script>
|
||||
import { MicroApp } from '@fesjs/fes';
|
||||
|
||||
export default {
|
||||
components: { MicroApp },
|
||||
setup(){
|
||||
const name = "app1"
|
||||
return {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## 子应用配置
|
||||
|
||||
### 第一步:插件注册
|
||||
```js
|
||||
export default {
|
||||
qiankun: {
|
||||
micro: {},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 第二步:配置运行时生命周期钩子(可选)
|
||||
插件会自动为你创建好 `qiankun` 子应用需要的生命周期钩子,但是如果你想在生命周期期间加一些自定义逻辑,可以在子应用的 `src/app.js` 里导出 `qiankun` 对象,并实现每一个生命周期钩子,其中钩子函数的入参 `props` 由主应用自动注入。
|
||||
```js
|
||||
export const qiankun = {
|
||||
// 应用加载之前
|
||||
async bootstrap(props) {
|
||||
console.log('app1 bootstrap', props);
|
||||
},
|
||||
// 应用 render 之前触发
|
||||
async mount(props) {
|
||||
console.log('app1 mount', props);
|
||||
},
|
||||
// 当 props 更新时触发
|
||||
async update(props){
|
||||
console.log('app1 update,' props);
|
||||
},
|
||||
// 应用卸载之后触发
|
||||
async unmount(props) {
|
||||
console.log('app1 unmount', props);
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 父子应用通讯
|
||||
|
||||
有两种方式实现
|
||||
|
||||
### 配合 [useModel](./model.md) 使用
|
||||
|
||||
确保已经安装了 `@fesjs/plugin-model`:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"@fesjs/plugin-model": "^2.0.0"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 主应用传递 props
|
||||
|
||||
- 如果使用 `MicroApp` 组件模式消费子应用,直接通过 props 传递即可:
|
||||
```vue
|
||||
<template>
|
||||
<MicroApp :name="name" :user="user" />
|
||||
</template>
|
||||
<script>
|
||||
import { MicroApp } from '@fesjs/fes';
|
||||
|
||||
export default {
|
||||
components: { MicroApp },
|
||||
setup(){
|
||||
const name = "app1"
|
||||
const user = ref("")
|
||||
return {
|
||||
name,
|
||||
user
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- 如果使用路由绑定式消费子应用,那么约定`src/models/qiankunStateForMicro.js` 的模型数据将作为 `props` 船体给子应用,如:
|
||||
```js
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export default () => {
|
||||
const state = reactive({ c: 1 });
|
||||
return {
|
||||
state
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### 子应用消费 props
|
||||
|
||||
子应用中会自动生成一个全局名为 `qiankunStateFromMain` 的 `model`, 可以在任意组件中获取主应用透传的 `props` 的值。
|
||||
|
||||
```vue
|
||||
<script>
|
||||
export default {
|
||||
setup(){
|
||||
const mainState = useModel('qiankunStateFromMain');
|
||||
return {
|
||||
mainState
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 基于 props 传递
|
||||
|
||||
- 主应用使用 props 的模式传递数据(参考主应用装载子应用配置一节)
|
||||
- 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)
|
@ -40,7 +40,7 @@ export const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
|
||||
if (menu.icon) {
|
||||
const icon = menu.icon;
|
||||
const urlReg = /^((https?|ftp|file):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
||||
if (!(urlReg.test(icon) || icon.includes('.svg'))) {
|
||||
if (typeof icon === 'string' && !((urlReg.test(icon) || icon.includes('.svg')))) {
|
||||
if (!allIcons[icon]) {
|
||||
menu.icon = {
|
||||
type: 'icon',
|
||||
|
@ -24,12 +24,12 @@ export default (api) => {
|
||||
function getAllModels() {
|
||||
const srcModelsPath = getModelsPath();
|
||||
return lodash.uniq([
|
||||
...getModels(srcModelsPath),
|
||||
...getModels(
|
||||
paths.absPagesPath,
|
||||
`**/${getModelDir()}/**/*.{js,jsx}`
|
||||
),
|
||||
...getModels(paths.absPagesPath, '**/*.model.{js,jsx}')
|
||||
...getModels(srcModelsPath)
|
||||
// ...getModels(
|
||||
// paths.absPagesPath,
|
||||
// `**/${getModelDir()}/**/*.{js,jsx}`
|
||||
// ),
|
||||
// ...getModels(paths.absPagesPath, '**/*.model.{js,jsx}')
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,11 @@ function getExtraImports(models = [], absSrcPath) {
|
||||
.map((ele) => {
|
||||
if (ele.exportName) {
|
||||
return `import { ${ele.exportName} } from '${winPath(
|
||||
ele.importPath.replace(/'/g, "\\'"),
|
||||
ele.importPath.replace(/'/g, "\\'")
|
||||
)}';`;
|
||||
}
|
||||
return `import ${ele.importName} from '${winPath(
|
||||
ele.importPath.replace(/'/g, "\\'"),
|
||||
ele.importPath.replace(/'/g, "\\'")
|
||||
)}';`;
|
||||
})
|
||||
.join(EOL);
|
||||
@ -37,7 +37,7 @@ function getExtraImports(models = [], absSrcPath) {
|
||||
export const getTmpFile = (
|
||||
files,
|
||||
extra = [],
|
||||
absSrcPath,
|
||||
absSrcPath
|
||||
) => {
|
||||
const userImports = genImports(files);
|
||||
const userModels = getModels(files, absSrcPath);
|
||||
|
21
packages/fes-plugin-qiankun/LICENSE
Normal file
21
packages/fes-plugin-qiankun/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-present webank
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
39
packages/fes-plugin-qiankun/package.json
Normal file
39
packages/fes-plugin-qiankun/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@fesjs/plugin-qiankun",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"description": "@fesjs/plugin-qiankun",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/WeBankFinTech/fes.js.git",
|
||||
"directory": "packages/fes-plugin-qiankun"
|
||||
},
|
||||
"keywords": [
|
||||
"fes"
|
||||
],
|
||||
"author": "michaelxxie",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/WeBankFinTech/fes.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/WeBankFinTech/fes.js#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umijs/utils": "3.3.3",
|
||||
"address": "^1.1.2",
|
||||
"lodash": "^4.17.15",
|
||||
"qiankun": "2.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@webank/fes": "^2.0.0-rc.0",
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
}
|
4
packages/fes-plugin-qiankun/src/constants.js
Normal file
4
packages/fes-plugin-qiankun/src/constants.js
Normal file
@ -0,0 +1,4 @@
|
||||
export const defaultMainRootId = 'root-master';
|
||||
export const defaultHistoryType = 'hash';
|
||||
export const qiankunStateForMicroModelNamespace = 'qiankunStateForMicro';
|
||||
export const qiankunStateFromMainModelNamespace = 'qiankunStateFromMain';
|
20
packages/fes-plugin-qiankun/src/index.js
Normal file
20
packages/fes-plugin-qiankun/src/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'qiankun',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object().keys({
|
||||
micro: joi.object(),
|
||||
main: joi.object()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.addRuntimePluginKey(() => 'qiankun');
|
||||
|
||||
api.registerPlugins([
|
||||
require.resolve('./main'),
|
||||
require.resolve('./micro')
|
||||
]);
|
||||
};
|
111
packages/fes-plugin-qiankun/src/main/index.js
Normal file
111
packages/fes-plugin-qiankun/src/main/index.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
defaultMainRootId,
|
||||
defaultHistoryType,
|
||||
qiankunStateForMicroModelNamespace
|
||||
} from '../constants';
|
||||
import modifyRoutes from './modifyRoutes';
|
||||
|
||||
const namespace = 'plugin-qiankun/main';
|
||||
|
||||
export function isMasterEnable(api) {
|
||||
return (
|
||||
!!api.userConfig?.qiankun?.main
|
||||
|| !!process.env.INITIAL_QIANKUN_MAIN_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
export default function (api) {
|
||||
const {
|
||||
utils: { Mustache, winPath }
|
||||
} = api;
|
||||
|
||||
api.describe({
|
||||
enableBy: () => isMasterEnable(api)
|
||||
});
|
||||
|
||||
api.modifyDefaultConfig(config => ({
|
||||
...config,
|
||||
mountElementId: defaultMainRootId
|
||||
}));
|
||||
|
||||
modifyRoutes({ api, namespace });
|
||||
|
||||
const absMicroAppPath = join(namespace, 'MicroApp.js');
|
||||
const absRuntimePath = join(namespace, 'runtime.js');
|
||||
const absMasterOptionsPath = join(namespace, 'masterOptions.js');
|
||||
const absGetMicroAppRouteCompPath = join(
|
||||
namespace,
|
||||
'getMicroAppRouteComponent.js'
|
||||
);
|
||||
|
||||
api.onGenerateFiles(() => {
|
||||
const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']);
|
||||
api.writeTmpFile({
|
||||
path: absMicroAppPath,
|
||||
content: Mustache.render(
|
||||
readFileSync(join(__dirname, 'runtime/MicroApp.tpl'), 'utf-8'),
|
||||
{
|
||||
qiankunStateForMicroModelNamespace,
|
||||
HAS_PLUGIN_MODEL:
|
||||
HAS_PLUGIN_MODEL
|
||||
&& existsSync(
|
||||
winPath(
|
||||
join(
|
||||
api.paths.absSrcPath,
|
||||
'models/qiankunStateForMicro.js'
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absRuntimePath,
|
||||
content: readFileSync(
|
||||
join(__dirname, 'runtime/runtime.tpl'),
|
||||
'utf-8'
|
||||
)
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absGetMicroAppRouteCompPath,
|
||||
content: readFileSync(
|
||||
join(__dirname, 'runtime/getMicroAppRouteComponent.tpl'),
|
||||
'utf-8'
|
||||
)
|
||||
});
|
||||
|
||||
const { main: options } = api.config?.qiankun || {};
|
||||
const masterHistoryType = api.config?.router?.mode || defaultHistoryType;
|
||||
const base = api.config.base || '/';
|
||||
api.writeTmpFile({
|
||||
path: absMasterOptionsPath,
|
||||
content: `
|
||||
let options = ${JSON.stringify({
|
||||
masterHistoryType,
|
||||
base,
|
||||
...options
|
||||
})};
|
||||
export const getMasterOptions = () => options;
|
||||
export const setMasterOptions = (newOpts) => options = ({ ...options, ...newOpts });
|
||||
`
|
||||
});
|
||||
});
|
||||
|
||||
api.addPluginExports(() => [
|
||||
{
|
||||
specifiers: ['MicroApp'],
|
||||
source: absMicroAppPath
|
||||
}
|
||||
]);
|
||||
|
||||
api.addPluginExports(() => [
|
||||
{
|
||||
specifiers: ['getMicroAppRouteComponent'],
|
||||
source: absGetMicroAppRouteCompPath
|
||||
}
|
||||
]);
|
||||
}
|
58
packages/fes-plugin-qiankun/src/main/modifyRoutes.js
Normal file
58
packages/fes-plugin-qiankun/src/main/modifyRoutes.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { defaultHistoryType } from '../constants';
|
||||
|
||||
function getMicroApp(options) {
|
||||
const {
|
||||
microAppName, masterHistoryType, base, namespace, ...normalizedRouteProps
|
||||
} = options;
|
||||
return `(() => {
|
||||
const { getMicroAppRouteComponent } = require('@@/${namespace}/getMicroAppRouteComponent');
|
||||
return getMicroAppRouteComponent({ appName: '${microAppName}', base: '${base}', masterHistoryType: '${masterHistoryType}', routeProps: ${JSON.stringify(normalizedRouteProps)} })
|
||||
})()`;
|
||||
}
|
||||
|
||||
function modifyRoutesWithAttachMode({
|
||||
routes, masterHistoryType, base, namespace
|
||||
}) {
|
||||
const patchRoutes = (_routes) => {
|
||||
if (_routes.length) {
|
||||
_routes.forEach((route) => {
|
||||
if (route.meta && route.meta.microApp) {
|
||||
route.component = getMicroApp({
|
||||
microAppName: route.meta.microApp,
|
||||
masterHistoryType,
|
||||
base,
|
||||
namespace
|
||||
});
|
||||
}
|
||||
if (route.children?.length) {
|
||||
modifyRoutesWithAttachMode({
|
||||
routes: route.children,
|
||||
masterHistoryType,
|
||||
base,
|
||||
namespace
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
patchRoutes(routes);
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
export default function modifyRoutes({ api, namespace }) {
|
||||
api.modifyRoutes((routes) => {
|
||||
const { router, base } = api.config;
|
||||
const masterHistoryType = (router && router?.mode) || defaultHistoryType;
|
||||
|
||||
modifyRoutesWithAttachMode({
|
||||
routes,
|
||||
masterHistoryType,
|
||||
base: base || '/',
|
||||
namespace
|
||||
});
|
||||
|
||||
return routes;
|
||||
});
|
||||
}
|
180
packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl
Normal file
180
packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl
Normal file
@ -0,0 +1,180 @@
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
} from "vue";
|
||||
import { loadMicroApp } from "qiankun";
|
||||
import mergeWith from "lodash/mergeWith";
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { getMasterOptions } from "./masterOptions";
|
||||
{{#HAS_PLUGIN_MODEL}}
|
||||
import { useModel } from '@@/core/pluginExports';
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
import { onBeforeRouteLeave } from "@@/core/coreExports";
|
||||
|
||||
let unmountPromise;
|
||||
async function unmountMicroApp(microApp) {
|
||||
if (microApp) {
|
||||
if (microApp.mountPromise) {
|
||||
await microApp.mountPromise;
|
||||
}
|
||||
if (!unmountPromise) {
|
||||
unmountPromise = microApp.unmount();
|
||||
}
|
||||
return await unmountPromise;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const MicroApp = defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
settings: Object,
|
||||
lifeCycles: Object,
|
||||
className: String,
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const {
|
||||
masterHistoryType,
|
||||
apps = [],
|
||||
lifeCycles: globalLifeCycles,
|
||||
...globalSettings
|
||||
} = getMasterOptions();
|
||||
|
||||
{{#HAS_PLUGIN_MODEL}}
|
||||
// 约定使用 src/models/qiankunStateForMicro 中的数据作为主应用透传给微应用的 props,优先级高于 propsFromConfig
|
||||
const stateForSlave = useModel('{{{qiankunStateForMicroModelNamespace}}}');
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
{{^HAS_PLUGIN_MODEL}}
|
||||
const stateForSlave = reactive({});
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
|
||||
// 挂载节点
|
||||
const containerRef = ref(null);
|
||||
const microAppRef = ref();
|
||||
const updatingPromiseRef = ref();
|
||||
const updatingTimestampRef = ref(Date.now());
|
||||
|
||||
const appConfigRef = computed(() => {
|
||||
const appConfig = apps.find((app) => app.name === props.name);
|
||||
if (!appConfig) {
|
||||
throw new Error(
|
||||
`[@fesjs/plugin-qiankun]: Can not find the configuration of ${props.name} app!`
|
||||
);
|
||||
}
|
||||
return appConfig;
|
||||
});
|
||||
|
||||
const propsFromConfigRef = computed(() => {
|
||||
const appConfig = appConfigRef.value;
|
||||
if (appConfig) {
|
||||
return appConfig.props;
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
const propsFromParams = attrs;
|
||||
|
||||
// 只有当name变化时才重新加载新的子应用
|
||||
const loadApp = () => {
|
||||
const appConfig = appConfigRef.value;
|
||||
const { name, entry } = appConfig;
|
||||
// 加载新的
|
||||
microAppRef.value = loadMicroApp(
|
||||
{
|
||||
name: name,
|
||||
entry: entry,
|
||||
container: containerRef.value,
|
||||
props: {
|
||||
...propsFromConfigRef.value,
|
||||
...stateForSlave,
|
||||
...propsFromParams,
|
||||
},
|
||||
},
|
||||
{
|
||||
...globalSettings,
|
||||
...(props.settings || {}),
|
||||
},
|
||||
mergeWith({}, globalLifeCycles || {}, props.lifeCycles || {}, (v1, v2) =>
|
||||
concat(v1 ?? [], v2 ?? [])
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// 当参数变化时,update子应用
|
||||
const updateApp = () => {
|
||||
const microApp = microAppRef.value;
|
||||
if (microApp) {
|
||||
if (!updatingPromiseRef.value) {
|
||||
// 初始化 updatingPromiseRef 为 microApp.mountPromise,从而确保后续更新是在应用 mount 完成之后
|
||||
updatingPromiseRef.value = microApp.mountPromise;
|
||||
} else {
|
||||
// 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成
|
||||
updatingPromiseRef.value = updatingPromiseRef.value.then(
|
||||
() => {
|
||||
const canUpdate = (app) =>
|
||||
app?.update && app.getStatus() === "MOUNTED";
|
||||
if (canUpdate(microApp)) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (
|
||||
Date.now() -
|
||||
updatingTimestampRef.value <
|
||||
200
|
||||
) {
|
||||
console.warn(
|
||||
`[@fesjs/plugin-qiankun] It seems like microApp ${props.name} is updating too many times in a short time(200ms), you may need to do some optimization to avoid the unnecessary re-rendering.`
|
||||
);
|
||||
}
|
||||
|
||||
console.info(
|
||||
`[@fesjs/plugin-qiankun] MicroApp ${props.name} is updating with props: `,
|
||||
props
|
||||
);
|
||||
updatingTimestampRef.value = Date.now();
|
||||
}
|
||||
|
||||
// 返回 microApp.update 形成链式调用
|
||||
return microApp.update({
|
||||
...propsFromConfigRef.value,
|
||||
...stateForSlave,
|
||||
...propsFromParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadApp();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unmountMicroApp(microAppRef.value);
|
||||
});
|
||||
|
||||
watch(appConfigRef, () => {
|
||||
unmountMicroApp(microAppRef.value);
|
||||
loadApp();
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(async () => {
|
||||
return await unmountMicroApp(microAppRef.value);
|
||||
});
|
||||
|
||||
watch(()=>{
|
||||
return {...{}, ...propsFromConfigRef.value, ...stateForSlave, ...propsFromParams}
|
||||
}, () => {
|
||||
updateApp();
|
||||
});
|
||||
|
||||
return () => <div ref={containerRef} className={props.className}></div>;
|
||||
},
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import { MicroApp } from './MicroApp';
|
||||
|
||||
export function getMicroAppRouteComponent({
|
||||
appName,
|
||||
base,
|
||||
masterHistoryType,
|
||||
routeProps
|
||||
}) {
|
||||
|
||||
return <MicroApp base={base} masterHistoryType={masterHistoryType} name={appName} {...routeProps} />;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
let initState;
|
||||
const setModelState = (val) => {
|
||||
initState = val;
|
||||
};
|
||||
|
||||
export default () => reactive(initState);
|
||||
|
||||
export { setModelState };
|
176
packages/fes-plugin-qiankun/src/micro/index.js
Normal file
176
packages/fes-plugin-qiankun/src/micro/index.js
Normal file
@ -0,0 +1,176 @@
|
||||
import assert from 'assert';
|
||||
import address from 'address';
|
||||
import { lodash } from '@umijs/utils';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { qiankunStateFromMainModelNamespace } from '../constants';
|
||||
|
||||
const namespace = 'plugin-qiankun/micro';
|
||||
|
||||
export function isSlaveEnable(api) {
|
||||
return (
|
||||
!!api.userConfig?.qiankun?.micro
|
||||
|| lodash.isEqual(api.userConfig?.qiankun, {})
|
||||
|| !!process.env.INITIAL_QIANKUN_MIRCO_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
export default function (api) {
|
||||
const {
|
||||
utils: { Mustache }
|
||||
} = api;
|
||||
|
||||
api.describe({
|
||||
enableBy: () => isSlaveEnable(api)
|
||||
});
|
||||
|
||||
api.modifyDefaultConfig((memo) => {
|
||||
const initialMicroOptions = {
|
||||
devSourceMap: true,
|
||||
...JSON.parse(process.env.INITIAL_QIANKUN_MIRCO_OPTIONS || '{}'),
|
||||
...(memo.qiankun || {}).micro
|
||||
};
|
||||
const modifiedDefaultConfig = {
|
||||
...memo,
|
||||
runtimePublicPath: true,
|
||||
qiankun: {
|
||||
...memo.qiankun,
|
||||
slave: initialMicroOptions
|
||||
}
|
||||
};
|
||||
|
||||
const shouldNotModifyDefaultBase = api.userConfig.qiankun?.slave?.shouldNotModifyDefaultBase
|
||||
?? initialMicroOptions.shouldNotModifyDefaultBase;
|
||||
if (!shouldNotModifyDefaultBase) {
|
||||
modifiedDefaultConfig.base = `/${api.pkg.name}`;
|
||||
}
|
||||
|
||||
return modifiedDefaultConfig;
|
||||
});
|
||||
|
||||
api.chainWebpack((config) => {
|
||||
assert(api.pkg.name, 'You should have name in package.json');
|
||||
config.output.libraryTarget('umd').library(`${api.pkg.name}-[name]`);
|
||||
return config;
|
||||
});
|
||||
|
||||
const port = process.env.PORT;
|
||||
// source-map 跨域设置
|
||||
if (process.env.NODE_ENV === 'development' && port) {
|
||||
const localHostname = process.env.USE_REMOTE_IP
|
||||
? address.ip()
|
||||
: process.env.HOST || 'localhost';
|
||||
|
||||
const protocol = process.env.HTTPS ? 'https' : 'http';
|
||||
// TODO: 变更 webpack-dev-server websocket 默认监听地址
|
||||
api.chainWebpack((memo, { webpack }) => {
|
||||
// 开启了 devSourceMap 配置,默认为 true
|
||||
if (
|
||||
api.config.qiankun
|
||||
&& api.config.qiankun.micro
|
||||
&& api.config.qiankun.micro.devSourceMap !== false
|
||||
) {
|
||||
// 禁用 devtool,启用 SourceMapDevToolPlugin
|
||||
memo.devtool(false);
|
||||
memo.plugin('source-map').use(webpack.SourceMapDevToolPlugin, [
|
||||
{
|
||||
// @ts-ignore
|
||||
namespace: api.pkg.name,
|
||||
append: `\n//# sourceMappingURL=${protocol}://${localHostname}:${port}/[url]`,
|
||||
filename: '[file].map'
|
||||
}
|
||||
]);
|
||||
}
|
||||
return memo;
|
||||
});
|
||||
}
|
||||
|
||||
const absRuntimePath = join(namespace, 'runtime.js');
|
||||
const absLifeclesPath = join(namespace, 'lifecycles.js');
|
||||
const absMicroOptionsPath = join(namespace, 'slaveOptions.js');
|
||||
const absPublicPath = join(namespace, 'publicPath.js');
|
||||
const absModelPath = join(namespace, 'qiankunModel.js');
|
||||
|
||||
// 更改public path
|
||||
api.addEntryImportsAhead(() => [{ source: `@@/${absPublicPath}` }]);
|
||||
|
||||
api.register({
|
||||
key: 'addExtraModels',
|
||||
fn: () => {
|
||||
const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']);
|
||||
return HAS_PLUGIN_MODEL ? [{
|
||||
absPath: `@@/${absModelPath}`,
|
||||
namespace: qiankunStateFromMainModelNamespace
|
||||
}] : [];
|
||||
}
|
||||
});
|
||||
|
||||
api.onGenerateFiles(() => {
|
||||
const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']);
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absRuntimePath,
|
||||
content: readFileSync(
|
||||
join(__dirname, 'runtime/runtime.tpl'),
|
||||
'utf-8'
|
||||
)
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absLifeclesPath,
|
||||
content: Mustache.render(readFileSync(
|
||||
join(__dirname, 'runtime/lifecycles.tpl'),
|
||||
'utf-8'
|
||||
), {
|
||||
HAS_PLUGIN_MODEL
|
||||
})
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absPublicPath,
|
||||
content: `
|
||||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
window.public_path = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
api.writeTmpFile({
|
||||
path: absMicroOptionsPath,
|
||||
content: `
|
||||
let options = ${JSON.stringify(
|
||||
(api.config.qiankun || {}).micro || {}
|
||||
)};
|
||||
export const getSlaveOptions = () => options;
|
||||
export const setSlaveOptions = (newOpts) => options = ({ ...options, ...newOpts });
|
||||
`
|
||||
});
|
||||
|
||||
if (HAS_PLUGIN_MODEL) {
|
||||
api.writeTmpFile({
|
||||
path: absModelPath,
|
||||
content: readFileSync(join(__dirname, 'runtime/qiankunModel.tpl'), 'utf-8')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
api.addEntryImports(() => ({
|
||||
source: `@@/${absLifeclesPath}`,
|
||||
specifier:
|
||||
'{ genMount as qiankun_genMount, genBootstrap as qiankun_genBootstrap, genUnmount as qiankun_genUnmount, genUpdate as qiankun_genUpdate }'
|
||||
}));
|
||||
|
||||
api.addEntryCode(
|
||||
() => `
|
||||
export const bootstrap = qiankun_genBootstrap(completeClientRender, app);
|
||||
export const mount = qiankun_genMount('#${api.config.mountElementId}');
|
||||
export const unmount = qiankun_genUnmount('#${api.config.mountElementId}');
|
||||
export const update = qiankun_genUpdate();
|
||||
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
bootstrap().then(mount);
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
105
packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl
Normal file
105
packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl
Normal file
@ -0,0 +1,105 @@
|
||||
import { plugin, ApplyPluginsType } from '@@/core/coreExports';
|
||||
{{#HAS_PLUGIN_MODEL}}
|
||||
import { setModelState } from './qiankunModel';
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
|
||||
const defer = {};
|
||||
defer.promise = new Promise((resolve) => {
|
||||
defer.resolve = resolve;
|
||||
});
|
||||
|
||||
function isPromise(obj) {
|
||||
return !!obj // 有实际含义的变量才执行方法,变量null,undefined和''空串都为false
|
||||
&& (typeof obj === 'object' || typeof obj === 'function') // 初始promise 或 promise.then返回的
|
||||
&& typeof obj.then === 'function';
|
||||
}
|
||||
|
||||
|
||||
let render = () => {};
|
||||
let cacheAppPromise = null;
|
||||
let hasMountedAtLeastOnce = false;
|
||||
|
||||
export default () => defer.promise;
|
||||
|
||||
function getSlaveRuntime() {
|
||||
const config = plugin.applyPlugins({
|
||||
key: 'qiankun',
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {}
|
||||
});
|
||||
const { slave } = config;
|
||||
return slave || config;
|
||||
}
|
||||
|
||||
// 子应用生命周期钩子Bootstrap
|
||||
export function genBootstrap(oldRender, appPromise) {
|
||||
return async (props) => {
|
||||
const slaveRuntime = getSlaveRuntime();
|
||||
if (slaveRuntime.bootstrap) {
|
||||
await slaveRuntime.bootstrap(props);
|
||||
}
|
||||
render = oldRender;
|
||||
if (isPromise(appPromise)) {
|
||||
cacheAppPromise = appPromise;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 子应用生命周期钩子Mount
|
||||
export function genMount() {
|
||||
return async (props) => {
|
||||
// props 有值时说明应用是通过 lifecycle 被主应用唤醒的,而不是独立运行时自己 mount
|
||||
if (typeof props !== 'undefined') {
|
||||
{{#HAS_PLUGIN_MODEL}}
|
||||
setModelState(props);
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
const slaveRuntime = getSlaveRuntime();
|
||||
if (slaveRuntime.mount) {
|
||||
await slaveRuntime.mount(props);
|
||||
}
|
||||
}
|
||||
|
||||
// 第一次 mount 会自动触发 render,非第一次 mount 则需手动触发
|
||||
if (hasMountedAtLeastOnce) {
|
||||
const appPromise = render();
|
||||
if (isPromise(appPromise)) {
|
||||
cacheAppPromise = appPromise;
|
||||
}
|
||||
} else {
|
||||
defer.resolve();
|
||||
}
|
||||
hasMountedAtLeastOnce = true;
|
||||
};
|
||||
}
|
||||
|
||||
export function genUpdate() {
|
||||
return async (props) => {
|
||||
{{#HAS_PLUGIN_MODEL}}
|
||||
setModelState(props);
|
||||
{{/HAS_PLUGIN_MODEL}}
|
||||
const slaveRuntime = await getSlaveRuntime();
|
||||
if (slaveRuntime.update) {
|
||||
await slaveRuntime.update(props);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 子应用生命周期钩子Unmount
|
||||
export function genUnmount(mountElementId) {
|
||||
return async (props) => {
|
||||
let container;
|
||||
try {
|
||||
container = props?.container
|
||||
? props.container.querySelector(mountElementId)
|
||||
: document.querySelector(mountElementId);
|
||||
} catch (e) {}
|
||||
if (container && cacheAppPromise) {
|
||||
const app = await cacheAppPromise;
|
||||
app.unmount(container);
|
||||
}
|
||||
const slaveRuntime = getSlaveRuntime();
|
||||
if (slaveRuntime.unmount) {
|
||||
await slaveRuntime.unmount(props);
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
let initState;
|
||||
const setModelState = (val) => {
|
||||
initState = val;
|
||||
};
|
||||
|
||||
export default () => reactive(initState);
|
||||
|
||||
export { setModelState };
|
@ -44,6 +44,7 @@ export default function () {
|
||||
require.resolve('./plugins/features/vueLoader'),
|
||||
require.resolve('./plugins/features/mock'),
|
||||
require.resolve('./plugins/features/dynamicImport'),
|
||||
require.resolve('./plugins/features/runtimePublicPath'),
|
||||
|
||||
// misc
|
||||
require.resolve('./plugins/misc/route'),
|
||||
|
@ -55,7 +55,16 @@ export async function getBundleAndConfigs({
|
||||
type: api.ApplyPluginsType.add,
|
||||
initialState: []
|
||||
});
|
||||
}
|
||||
},
|
||||
publicPath: await api.applyPlugins({
|
||||
key: 'modifyPublicPathStr',
|
||||
type: api.ApplyPluginsType.modify,
|
||||
initialValue: api.config.publicPath || '',
|
||||
args: {
|
||||
// route: args.route
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
args: {
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ export default async function createHtmlWebpackConfig({
|
||||
isProd
|
||||
}) {
|
||||
const htmlOptions = {
|
||||
title: 'fes.js',
|
||||
filename: '[name].html',
|
||||
...config.html,
|
||||
templateParameters: resolveDefine(config, true)
|
||||
templateParameters: resolveDefine(config, true),
|
||||
mountElementId: config.mountElementId
|
||||
};
|
||||
htmlOptions.title = htmlOptions.title || 'fes.js';
|
||||
|
||||
|
||||
if (isProd) {
|
||||
Object.assign(htmlOptions, {
|
||||
|
@ -7,6 +7,6 @@
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="<%= htmlWebpackPlugin.options.mountElementId %>"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -66,7 +66,8 @@ export default async function getConfig({
|
||||
modifyBabelOpts,
|
||||
modifyBabelPresetOpts,
|
||||
chainWebpack,
|
||||
headScripts
|
||||
headScripts,
|
||||
publicPath
|
||||
}) {
|
||||
const isDev = env === 'development';
|
||||
const isProd = env === 'production';
|
||||
@ -93,7 +94,7 @@ export default async function getConfig({
|
||||
// --------------- output -----------
|
||||
webpackConfig.output
|
||||
.path(absoluteOutput)
|
||||
.publicPath(config.publicPath || '')
|
||||
.publicPath(publicPath)
|
||||
.filename('[name].[contenthash:8].js')
|
||||
.chunkFilename('[name].[contenthash:8].chunk.js');
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const prefixRE = /^FES_APP_/;
|
||||
|
||||
const ENV_SHOULD_PASS = ['NODE_ENV', 'FES_ENV', 'HMR', 'SOCKET_SERVER', 'ERROR_OVERLAY'];
|
||||
const ENV_SHOULD_PASS = ['NODE_ENV', 'FES_ENV'];
|
||||
|
||||
export default function resolveDefine(opts = {}, raw) {
|
||||
const env = {};
|
||||
|
@ -3,7 +3,7 @@ export default (api) => {
|
||||
api.describe({
|
||||
key: 'mountElementId',
|
||||
config: {
|
||||
default: '#app',
|
||||
default: 'app',
|
||||
schema(joi) {
|
||||
return joi.string().allow('');
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'runtimePublicPath',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.boolean();
|
||||
}
|
||||
},
|
||||
default: false
|
||||
});
|
||||
};
|
@ -62,22 +62,19 @@ const getClientRender = (args = {}) => plugin.applyPlugins({
|
||||
args,
|
||||
});
|
||||
|
||||
|
||||
|
||||
const beforeRenderConfig = plugin.applyPlugins({
|
||||
key: "beforeRender",
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {
|
||||
loading: null,
|
||||
action: null
|
||||
},
|
||||
});
|
||||
|
||||
const beforeRender = async () => {
|
||||
const beforeRenderConfig = plugin.applyPlugins({
|
||||
key: "beforeRender",
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {
|
||||
loading: null,
|
||||
action: null
|
||||
},
|
||||
});
|
||||
let initialState = {};
|
||||
if (typeof beforeRenderConfig.action === "function") {
|
||||
const app = createApp(beforeRenderConfig.loading);
|
||||
app.mount("#app");
|
||||
app.mount('{{{ rootElement }}}');
|
||||
try {
|
||||
initialState = await beforeRenderConfig.action();
|
||||
} catch(e){
|
||||
@ -89,13 +86,16 @@ const beforeRender = async () => {
|
||||
return initialState;
|
||||
};
|
||||
|
||||
const render = async () => {
|
||||
const completeClientRender = async () => {
|
||||
const initialState = await beforeRender();
|
||||
const clientRender = getClientRender({initialState});
|
||||
clientRender();
|
||||
const app = clientRender();
|
||||
return app;
|
||||
};
|
||||
|
||||
render();
|
||||
const app = completeClientRender();
|
||||
|
||||
export default app;
|
||||
|
||||
|
||||
{{{ entryCode }}}
|
||||
|
@ -26,7 +26,7 @@ export default function (api) {
|
||||
enableTitle: api.config.title !== false,
|
||||
defaultTitle: api.config.title || '',
|
||||
runtimePath,
|
||||
rootElement: api.config.mountElementId || '#app',
|
||||
rootElement: `#${api.config.mountElementId || 'app'}`,
|
||||
entryCode: (
|
||||
await api.applyPlugins({
|
||||
key: 'addEntryCode',
|
||||
|
@ -265,7 +265,7 @@ export default function (api) {
|
||||
const absRuntimeFilePath = join(namespace, 'runtime.js');
|
||||
|
||||
const historyType = {
|
||||
h5: 'createWebHistory',
|
||||
history: 'createWebHistory',
|
||||
hash: 'createWebHashHistory',
|
||||
memory: 'createMemoryHistory'
|
||||
};
|
||||
|
@ -28,7 +28,8 @@ export default function (api) {
|
||||
'modifyBabelOpts',
|
||||
'modifyBabelPresetOpts',
|
||||
'chainWebpack',
|
||||
'addTmpGenerateWatcherPaths'
|
||||
'addTmpGenerateWatcherPaths',
|
||||
'modifyPublicPathStr'
|
||||
].forEach((name) => {
|
||||
api.registerMethod({ name });
|
||||
});
|
||||
|
@ -55,6 +55,7 @@
|
||||
"@fesjs/plugin-jest": "^2.0.0-rc.0",
|
||||
"@fesjs/plugin-vuex": "^2.0.0-rc.0",
|
||||
"@fesjs/plugin-request": "^2.0.0-rc.0",
|
||||
"@fesjs/plugin-qiankun": "^2.0.0-alpha.0",
|
||||
"ant-design-vue": "2.0.0",
|
||||
"vue": "^3.0.5",
|
||||
"vuex": "^4.0.0"
|
||||
|
37
yarn.lock
37
yarn.lock
@ -1587,6 +1587,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.7.2":
|
||||
version "7.13.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
|
||||
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.0.0", "@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3", "@babel/template@^7.4.0":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
|
||||
@ -4644,7 +4651,7 @@ acorn@^8.0.4:
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7"
|
||||
integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==
|
||||
|
||||
address@1.1.2:
|
||||
address@1.1.2, address@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
|
||||
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
|
||||
@ -9545,6 +9552,13 @@ import-from@^3.0.0:
|
||||
dependencies:
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
import-html-entry@^1.9.0:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/import-html-entry/-/import-html-entry-1.11.1.tgz#3d8c5977926bdd122ab8e658965c102068b4af8d"
|
||||
integrity sha512-O7mCUTwKdYU49/LH6nq1adWPnUlZQpKeGWIEcDq07KTcqP/v0jBLEIVc0oE0Mtlw3CEe0eeKGMyhl6LwfXCV7A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.2"
|
||||
|
||||
import-lazy@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
|
||||
@ -12892,6 +12906,11 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
path-to-regexp@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
|
||||
integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
@ -13706,6 +13725,17 @@ q@^1.1.2, q@^1.5.1:
|
||||
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||
|
||||
qiankun@2.3.4:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/qiankun/-/qiankun-2.3.4.tgz#a6a6382c1e909a76f9aea1708ff46276432428f2"
|
||||
integrity sha512-LJ3luGH0eAQ3xd7vH7xUtAS57eGUs4bMiCcFQx1OJ94XJ3VdKIb97jqT5p5ibOj82EPQdLJhVsB5+phm4iEXfw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.5"
|
||||
import-html-entry "^1.9.0"
|
||||
lodash "^4.17.11"
|
||||
single-spa "5.8.1"
|
||||
tslib "^1.10.0"
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
@ -14783,6 +14813,11 @@ simple-swizzle@^0.2.2:
|
||||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
single-spa@5.8.1:
|
||||
version "5.8.1"
|
||||
resolved "https://registry.yarnpkg.com/single-spa/-/single-spa-5.8.1.tgz#86c2575e297e31d8f06945944ec97e31851a59ae"
|
||||
integrity sha512-RlyLZ1IDIPdzI6mQPzCQnlgTt9jmbAXBZODmifoDut840wksPDSPhcSS8jXMpuUlqOidQiX2YuLVQSR9DEgsXw==
|
||||
|
||||
sirv@^1.0.7:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4"
|
||||
|
Loading…
x
Reference in New Issue
Block a user