diff --git a/.fatherrc.js b/.fatherrc.js
index f0d53670..6ceb55cb 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -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(
diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts
index 45b97a43..33ffac35 100644
--- a/docs/.vuepress/configs/sidebar/en.ts
+++ b/docs/.vuepress/configs/sidebar/en.ts
@@ -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',
],
},
{
diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts
index eb5967d6..60fa945a 100644
--- a/docs/.vuepress/configs/sidebar/zh.ts
+++ b/docs/.vuepress/configs/sidebar/zh.ts
@@ -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',
],
},
{
diff --git a/docs/guide/route.md b/docs/guide/route.md
index 642929d8..0200633b 100644
--- a/docs/guide/route.md
+++ b/docs/guide/route.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`模式。
+
## 约定式路由
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
diff --git a/docs/reference/plugin/plugins/model.md b/docs/reference/plugin/plugins/model.md
index 8f89ded8..16e4b5ed 100644
--- a/docs/reference/plugin/plugins/model.md
+++ b/docs/reference/plugin/plugins/model.md
@@ -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
+
+```
+
+
+## API
+
+### useModel
+
+**useModel(name)**
+- **类型**:函数
+
+- **详情**: 获取 Model 数据,也就是 Model 文件默认导出函数执行的结果。
+- **参数**:
+ - name,传入 Model 文件名
-## API
\ No newline at end of file
diff --git a/docs/reference/plugin/plugins/qiankun.md b/docs/reference/plugin/plugins/qiankun.md
new file mode 100644
index 00000000..12340703
--- /dev/null
+++ b/docs/reference/plugin/plugins/qiankun.md
@@ -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
+
+{
+ "name": "son",
+ "title": "子应用",
+ "microApp": "app1"
+}
+
+```
+
+
+#### 使用 `` 组件的方式
+:::tip
+建议使用这种方式来引入不带路由的子应用。 否则请自行关注子应用依赖的路由跟当前浏览器 url 是否能正确匹配上,否则很容易出现子应用加载了,但是页面没有渲染出来的情况。
+:::
+```vue
+
+
+
+
+```
+
+
+## 子应用配置
+
+### 第一步:插件注册
+```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
+
+
+
+
+```
+
+- 如果使用路由绑定式消费子应用,那么约定`src/models/qiankunStateForMicro.js` 的模型数据将作为 `props` 船体给子应用,如:
+```js
+import { reactive } from 'vue';
+
+export default () => {
+ const state = reactive({ c: 1 });
+ return {
+ state
+ };
+};
+```
+
+#### 子应用消费 props
+
+子应用中会自动生成一个全局名为 `qiankunStateFromMain` 的 `model`, 可以在任意组件中获取主应用透传的 `props` 的值。
+
+```vue
+
+```
+
+### 基于 props 传递
+
+- 主应用使用 props 的模式传递数据(参考主应用装载子应用配置一节)
+- 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)
diff --git a/docs/zh/guide/route.md b/docs/zh/guide/route.md
index 642929d8..0200633b 100644
--- a/docs/zh/guide/route.md
+++ b/docs/zh/guide/route.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`模式。
+
## 约定式路由
约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
diff --git a/docs/zh/reference/plugin/plugins/model.md b/docs/zh/reference/plugin/plugins/model.md
index 8f89ded8..16e4b5ed 100644
--- a/docs/zh/reference/plugin/plugins/model.md
+++ b/docs/zh/reference/plugin/plugins/model.md
@@ -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
+
+```
+
+
+## API
+
+### useModel
+
+**useModel(name)**
+- **类型**:函数
+
+- **详情**: 获取 Model 数据,也就是 Model 文件默认导出函数执行的结果。
+- **参数**:
+ - name,传入 Model 文件名
-## API
\ No newline at end of file
diff --git a/docs/zh/reference/plugin/plugins/qiankun.md b/docs/zh/reference/plugin/plugins/qiankun.md
new file mode 100644
index 00000000..12340703
--- /dev/null
+++ b/docs/zh/reference/plugin/plugins/qiankun.md
@@ -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
+
+{
+ "name": "son",
+ "title": "子应用",
+ "microApp": "app1"
+}
+
+```
+
+
+#### 使用 `` 组件的方式
+:::tip
+建议使用这种方式来引入不带路由的子应用。 否则请自行关注子应用依赖的路由跟当前浏览器 url 是否能正确匹配上,否则很容易出现子应用加载了,但是页面没有渲染出来的情况。
+:::
+```vue
+
+
+
+
+```
+
+
+## 子应用配置
+
+### 第一步:插件注册
+```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
+
+
+
+
+```
+
+- 如果使用路由绑定式消费子应用,那么约定`src/models/qiankunStateForMicro.js` 的模型数据将作为 `props` 船体给子应用,如:
+```js
+import { reactive } from 'vue';
+
+export default () => {
+ const state = reactive({ c: 1 });
+ return {
+ state
+ };
+};
+```
+
+#### 子应用消费 props
+
+子应用中会自动生成一个全局名为 `qiankunStateFromMain` 的 `model`, 可以在任意组件中获取主应用透传的 `props` 的值。
+
+```vue
+
+```
+
+### 基于 props 传递
+
+- 主应用使用 props 的模式传递数据(参考主应用装载子应用配置一节)
+- 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)
diff --git a/packages/fes-plugin-layout/src/node/helper.js b/packages/fes-plugin-layout/src/node/helper.js
index 6065365d..a14a01e6 100644
--- a/packages/fes-plugin-layout/src/node/helper.js
+++ b/packages/fes-plugin-layout/src/node/helper.js
@@ -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',
diff --git a/packages/fes-plugin-model/src/index.js b/packages/fes-plugin-model/src/index.js
index 4f08d782..a09165f4 100644
--- a/packages/fes-plugin-model/src/index.js
+++ b/packages/fes-plugin-model/src/index.js
@@ -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}')
]);
}
diff --git a/packages/fes-plugin-model/src/utils/getTmpFile.js b/packages/fes-plugin-model/src/utils/getTmpFile.js
index c0bbdf13..bf832f9a 100644
--- a/packages/fes-plugin-model/src/utils/getTmpFile.js
+++ b/packages/fes-plugin-model/src/utils/getTmpFile.js
@@ -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);
diff --git a/packages/fes-plugin-qiankun/LICENSE b/packages/fes-plugin-qiankun/LICENSE
new file mode 100644
index 00000000..0978fbf7
--- /dev/null
+++ b/packages/fes-plugin-qiankun/LICENSE
@@ -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.
\ No newline at end of file
diff --git a/packages/fes-plugin-qiankun/package.json b/packages/fes-plugin-qiankun/package.json
new file mode 100644
index 00000000..b9fe443c
--- /dev/null
+++ b/packages/fes-plugin-qiankun/package.json
@@ -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"
+ }
+}
diff --git a/packages/fes-plugin-qiankun/src/constants.js b/packages/fes-plugin-qiankun/src/constants.js
new file mode 100644
index 00000000..92f19f5b
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/constants.js
@@ -0,0 +1,4 @@
+export const defaultMainRootId = 'root-master';
+export const defaultHistoryType = 'hash';
+export const qiankunStateForMicroModelNamespace = 'qiankunStateForMicro';
+export const qiankunStateFromMainModelNamespace = 'qiankunStateFromMain';
diff --git a/packages/fes-plugin-qiankun/src/index.js b/packages/fes-plugin-qiankun/src/index.js
new file mode 100644
index 00000000..33f82ba1
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/index.js
@@ -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')
+ ]);
+};
diff --git a/packages/fes-plugin-qiankun/src/main/index.js b/packages/fes-plugin-qiankun/src/main/index.js
new file mode 100644
index 00000000..2caea696
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/main/index.js
@@ -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
+ }
+ ]);
+}
diff --git a/packages/fes-plugin-qiankun/src/main/modifyRoutes.js b/packages/fes-plugin-qiankun/src/main/modifyRoutes.js
new file mode 100644
index 00000000..8a1ff4e0
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/main/modifyRoutes.js
@@ -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;
+ });
+}
diff --git a/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl b/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl
new file mode 100644
index 00000000..c4804697
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl
@@ -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 () =>
;
+ },
+});
diff --git a/packages/fes-plugin-qiankun/src/main/runtime/getMicroAppRouteComponent.tpl b/packages/fes-plugin-qiankun/src/main/runtime/getMicroAppRouteComponent.tpl
new file mode 100644
index 00000000..55b3776b
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/main/runtime/getMicroAppRouteComponent.tpl
@@ -0,0 +1,11 @@
+import { MicroApp } from './MicroApp';
+
+export function getMicroAppRouteComponent({
+ appName,
+ base,
+ masterHistoryType,
+ routeProps
+}) {
+
+ return ;
+}
diff --git a/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl b/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl
new file mode 100644
index 00000000..7348a0b5
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl
@@ -0,0 +1,11 @@
+
+import { reactive } from 'vue';
+
+let initState;
+const setModelState = (val) => {
+ initState = val;
+};
+
+export default () => reactive(initState);
+
+export { setModelState };
diff --git a/packages/fes-plugin-qiankun/src/main/runtime/runtime.tpl b/packages/fes-plugin-qiankun/src/main/runtime/runtime.tpl
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/fes-plugin-qiankun/src/micro/index.js b/packages/fes-plugin-qiankun/src/micro/index.js
new file mode 100644
index 00000000..1945e4f7
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/micro/index.js
@@ -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);
+}
+`
+ );
+}
diff --git a/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl b/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl
new file mode 100644
index 00000000..0bebf7c1
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl
@@ -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);
+ }
+ };
+}
diff --git a/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl b/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl
new file mode 100644
index 00000000..7348a0b5
--- /dev/null
+++ b/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl
@@ -0,0 +1,11 @@
+
+import { reactive } from 'vue';
+
+let initState;
+const setModelState = (val) => {
+ initState = val;
+};
+
+export default () => reactive(initState);
+
+export { setModelState };
diff --git a/packages/fes-plugin-qiankun/src/micro/runtime/runtime.tpl b/packages/fes-plugin-qiankun/src/micro/runtime/runtime.tpl
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/fes-preset-built-in/src/index.js b/packages/fes-preset-built-in/src/index.js
index 0a2883dd..e491f6a2 100644
--- a/packages/fes-preset-built-in/src/index.js
+++ b/packages/fes-preset-built-in/src/index.js
@@ -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'),
diff --git a/packages/fes-preset-built-in/src/plugins/commands/buildDevUtils.js b/packages/fes-preset-built-in/src/plugins/commands/buildDevUtils.js
index 1d7af459..b2b86505 100644
--- a/packages/fes-preset-built-in/src/plugins/commands/buildDevUtils.js
+++ b/packages/fes-preset-built-in/src/plugins/commands/buildDevUtils.js
@@ -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: {
}
diff --git a/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/html.js b/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/html.js
index 9d03d087..5a5fd5b8 100644
--- a/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/html.js
+++ b/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/html.js
@@ -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, {
diff --git a/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/index-default.html b/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/index-default.html
index 4b9c8375..cf7300b6 100644
--- a/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/index-default.html
+++ b/packages/fes-preset-built-in/src/plugins/commands/webpackConfig/index-default.html
@@ -7,6 +7,6 @@
<%= htmlWebpackPlugin.options.title %>
-
+