@nocobase/client-v2 2.1.0-alpha.2 → 2.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +99 -0
- package/es/Application.d.ts +124 -0
- package/es/MockApplication.d.ts +16 -0
- package/es/Plugin.d.ts +33 -0
- package/es/PluginManager.d.ts +46 -0
- package/es/PluginSettingsManager.d.ts +68 -0
- package/es/RouterManager.d.ts +69 -0
- package/es/WebSocketClient.d.ts +45 -0
- package/es/components/BlankComponent.d.ts +12 -0
- package/es/components/MainComponent.d.ts +10 -0
- package/es/components/RouterBridge.d.ts +13 -0
- package/es/components/RouterContextCleaner.d.ts +12 -0
- package/es/components/index.d.ts +10 -0
- package/es/context.d.ts +11 -0
- package/es/hooks/index.d.ts +11 -0
- package/es/hooks/useApp.d.ts +10 -0
- package/es/hooks/usePlugin.d.ts +11 -0
- package/es/hooks/useRouter.d.ts +9 -0
- package/es/index.d.ts +14 -0
- package/es/utils/globalDeps.d.ts +13 -0
- package/es/utils/index.d.ts +11 -0
- package/es/utils/remotePlugins.d.ts +44 -0
- package/es/utils/requirejs.d.ts +18 -0
- package/es/utils/types.d.ts +330 -0
- package/lib/Application.js +14 -4
- package/lib/PluginManager.js +1 -1
- package/lib/PluginSettingsManager.d.ts +1 -0
- package/lib/PluginSettingsManager.js +2 -1
- package/lib/RouterManager.d.ts +8 -0
- package/lib/RouterManager.js +35 -2
- package/lib/utils/globalDeps.d.ts +13 -0
- package/lib/utils/globalDeps.js +82 -0
- package/lib/utils/remotePlugins.js +2 -2
- package/package.json +6 -6
- package/src/Application.tsx +13 -4
- package/src/PluginManager.ts +1 -1
- package/src/PluginSettingsManager.ts +2 -0
- package/src/RouterManager.tsx +48 -2
- package/src/__tests__/app.test.tsx +55 -0
- package/src/__tests__/globalDeps.test.ts +26 -0
- package/src/__tests__/plugin.test.ts +61 -0
- package/src/__tests__/remotePlugins.test.ts +72 -0
- package/src/utils/globalDeps.ts +61 -0
- package/src/utils/remotePlugins.ts +2 -2
- package/LICENSE.txt +0 -172
|
@@ -83,7 +83,8 @@ const _PluginSettingsManager = class _PluginSettingsManager {
|
|
|
83
83
|
}
|
|
84
84
|
this.app.router.add(this.getRouteName(name), {
|
|
85
85
|
path: this.getRoutePath(name),
|
|
86
|
-
Component: this.settings[name].Component
|
|
86
|
+
Component: this.settings[name].Component,
|
|
87
|
+
componentLoader: this.settings[name].componentLoader
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
remove(name) {
|
package/lib/RouterManager.d.ts
CHANGED
|
@@ -23,8 +23,14 @@ export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRo
|
|
|
23
23
|
routes?: Record<string, RouteType>;
|
|
24
24
|
};
|
|
25
25
|
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
|
26
|
+
export type ComponentLoaderResult = {
|
|
27
|
+
default?: ComponentTypeAndString;
|
|
28
|
+
Component?: ComponentTypeAndString;
|
|
29
|
+
} | ComponentTypeAndString;
|
|
30
|
+
export type ComponentLoader = () => Promise<ComponentLoaderResult>;
|
|
26
31
|
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
27
32
|
Component?: ComponentTypeAndString;
|
|
33
|
+
componentLoader?: ComponentLoader;
|
|
28
34
|
skipAuthCheck?: boolean;
|
|
29
35
|
}
|
|
30
36
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
@@ -37,6 +43,8 @@ export declare class RouterManager {
|
|
|
37
43
|
get state(): any;
|
|
38
44
|
get navigate(): any;
|
|
39
45
|
constructor(options: RouterOptions, app: Application);
|
|
46
|
+
protected resolveLoadedComponent(moduleOrComponent: ComponentLoaderResult): ComponentTypeAndString | undefined;
|
|
47
|
+
protected createRouteLazyComponent(componentLoader: ComponentLoader): ComponentType;
|
|
40
48
|
/**
|
|
41
49
|
* @internal
|
|
42
50
|
*/
|
package/lib/RouterManager.js
CHANGED
|
@@ -66,6 +66,37 @@ const _RouterManager = class _RouterManager {
|
|
|
66
66
|
this.app = app;
|
|
67
67
|
this.routes = options.routes || {};
|
|
68
68
|
}
|
|
69
|
+
resolveLoadedComponent(moduleOrComponent) {
|
|
70
|
+
if (!moduleOrComponent) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
if (typeof moduleOrComponent === "function" || typeof moduleOrComponent === "string") {
|
|
74
|
+
return moduleOrComponent;
|
|
75
|
+
}
|
|
76
|
+
return moduleOrComponent.default || moduleOrComponent.Component;
|
|
77
|
+
}
|
|
78
|
+
createRouteLazyComponent(componentLoader) {
|
|
79
|
+
const LazyComponent = import_react.default.lazy(
|
|
80
|
+
() => componentLoader().then((moduleOrComponent) => {
|
|
81
|
+
const loadedComponent = this.resolveLoadedComponent(moduleOrComponent);
|
|
82
|
+
if (!loadedComponent) {
|
|
83
|
+
throw new Error("componentLoader must resolve to a React component or component module.");
|
|
84
|
+
}
|
|
85
|
+
if (typeof loadedComponent === "string") {
|
|
86
|
+
const StringRouteComponent = /* @__PURE__ */ __name((props) => this.app.renderComponent(loadedComponent, props), "StringRouteComponent");
|
|
87
|
+
return {
|
|
88
|
+
default: StringRouteComponent
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
default: loadedComponent
|
|
93
|
+
};
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
return /* @__PURE__ */ __name(function RouteLazyComponentWrapper(props) {
|
|
97
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Suspense, { fallback: null }, /* @__PURE__ */ import_react.default.createElement(LazyComponent, { ...props }));
|
|
98
|
+
}, "RouteLazyComponentWrapper");
|
|
99
|
+
}
|
|
69
100
|
/**
|
|
70
101
|
* @internal
|
|
71
102
|
*/
|
|
@@ -79,9 +110,11 @@ const _RouterManager = class _RouterManager {
|
|
|
79
110
|
if (Object.keys(item).length === 1 && item.children) {
|
|
80
111
|
acc.push(...buildRoutesTree(item.children));
|
|
81
112
|
} else {
|
|
82
|
-
const { Component, element, children, ...reset } = item;
|
|
113
|
+
const { Component, componentLoader, element, children, ...reset } = item;
|
|
83
114
|
let ele = element;
|
|
84
|
-
if (
|
|
115
|
+
if (componentLoader) {
|
|
116
|
+
ele = import_react.default.createElement(this.createRouteLazyComponent(componentLoader));
|
|
117
|
+
} else if (Component) {
|
|
85
118
|
if (typeof Component === "string") {
|
|
86
119
|
ele = this.app.renderComponent(Component);
|
|
87
120
|
} else {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import type { RequireJS } from './requirejs';
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineGlobalDeps(requirejs: RequireJS): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var globalDeps_exports = {};
|
|
39
|
+
__export(globalDeps_exports, {
|
|
40
|
+
defineGlobalDeps: () => defineGlobalDeps
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(globalDeps_exports);
|
|
43
|
+
var antdCssinjs = __toESM(require("@ant-design/cssinjs"));
|
|
44
|
+
var antdIcons = __toESM(require("@ant-design/icons"));
|
|
45
|
+
var formilyCore = __toESM(require("@formily/core"));
|
|
46
|
+
var formilyReact = __toESM(require("@formily/react"));
|
|
47
|
+
var formilyReactive = __toESM(require("@formily/reactive"));
|
|
48
|
+
var formilyShared = __toESM(require("@formily/shared"));
|
|
49
|
+
var nocobaseFlowEngine = __toESM(require("@nocobase/flow-engine"));
|
|
50
|
+
var antd = __toESM(require("antd"));
|
|
51
|
+
var i18next = __toESM(require("i18next"));
|
|
52
|
+
var import_react = __toESM(require("react"));
|
|
53
|
+
var import_react_dom = __toESM(require("react-dom"));
|
|
54
|
+
var reactI18next = __toESM(require("react-i18next"));
|
|
55
|
+
var ReactRouter = __toESM(require("react-router"));
|
|
56
|
+
var ReactRouterDom = __toESM(require("react-router-dom"));
|
|
57
|
+
var import_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
58
|
+
var nocobaseClientV2 = __toESM(require("../index"));
|
|
59
|
+
function defineGlobalDeps(requirejs) {
|
|
60
|
+
requirejs.define("react", () => import_react.default);
|
|
61
|
+
requirejs.define("react-dom", () => import_react_dom.default);
|
|
62
|
+
requirejs.define("react/jsx-runtime", () => import_jsx_runtime.default);
|
|
63
|
+
requirejs.define("react-router", () => ReactRouter);
|
|
64
|
+
requirejs.define("react-router-dom", () => ReactRouterDom);
|
|
65
|
+
requirejs.define("antd", () => antd);
|
|
66
|
+
requirejs.define("@ant-design/icons", () => antdIcons);
|
|
67
|
+
requirejs.define("@ant-design/cssinjs", () => antdCssinjs);
|
|
68
|
+
requirejs.define("i18next", () => i18next);
|
|
69
|
+
requirejs.define("react-i18next", () => reactI18next);
|
|
70
|
+
requirejs.define("@formily/core", () => formilyCore);
|
|
71
|
+
requirejs.define("@formily/react", () => formilyReact);
|
|
72
|
+
requirejs.define("@formily/reactive", () => formilyReactive);
|
|
73
|
+
requirejs.define("@formily/shared", () => formilyShared);
|
|
74
|
+
requirejs.define("@nocobase/client-v2", () => nocobaseClientV2);
|
|
75
|
+
requirejs.define("@nocobase/client-v2/client-v2", () => nocobaseClientV2);
|
|
76
|
+
requirejs.define("@nocobase/flow-engine", () => nocobaseFlowEngine);
|
|
77
|
+
}
|
|
78
|
+
__name(defineGlobalDeps, "defineGlobalDeps");
|
|
79
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
80
|
+
0 && (module.exports = {
|
|
81
|
+
defineGlobalDeps
|
|
82
|
+
});
|
|
@@ -37,12 +37,12 @@ __export(remotePlugins_exports, {
|
|
|
37
37
|
module.exports = __toCommonJS(remotePlugins_exports);
|
|
38
38
|
function defineDevPlugins(plugins) {
|
|
39
39
|
Object.entries(plugins).forEach(([packageName, plugin]) => {
|
|
40
|
-
window.define(`${packageName}/client`, () => plugin);
|
|
40
|
+
window.define(`${packageName}/client-v2`, () => plugin);
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
__name(defineDevPlugins, "defineDevPlugins");
|
|
44
44
|
function definePluginClient(packageName) {
|
|
45
|
-
window.define(`${packageName}/client`, ["exports", packageName], function(_exports, _pluginExports) {
|
|
45
|
+
window.define(`${packageName}/client-v2`, ["exports", packageName], function(_exports, _pluginExports) {
|
|
46
46
|
Object.defineProperty(_exports, "__esModule", {
|
|
47
47
|
value: true
|
|
48
48
|
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
4
|
-
"license": "
|
|
3
|
+
"version": "2.1.0-alpha.20",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
7
7
|
"types": "es/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@nocobase/flow-engine": "2.1.0-alpha.
|
|
10
|
-
"@nocobase/sdk": "2.1.0-alpha.
|
|
11
|
-
"@nocobase/shared": "2.1.0-alpha.
|
|
9
|
+
"@nocobase/flow-engine": "2.1.0-alpha.20",
|
|
10
|
+
"@nocobase/sdk": "2.1.0-alpha.20",
|
|
11
|
+
"@nocobase/shared": "2.1.0-alpha.20",
|
|
12
12
|
"i18next": "^22.4.9",
|
|
13
13
|
"react-router-dom": "^6.30.1"
|
|
14
14
|
},
|
|
15
|
-
"gitHead": "
|
|
15
|
+
"gitHead": "3d1535db6bf93ca23257faf474afee0d565f54c6"
|
|
16
16
|
}
|
package/src/Application.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from '@nocobase/flow-engine';
|
|
19
19
|
import { APIClient, type APIClientOptions, getSubAppName } from '@nocobase/sdk';
|
|
20
20
|
import { createInstance, type i18n as i18next } from 'i18next';
|
|
21
|
-
import
|
|
21
|
+
import { get, merge, set } from 'lodash-es';
|
|
22
22
|
import React, { ComponentType, FC, ReactElement, ReactNode } from 'react';
|
|
23
23
|
import { createRoot } from 'react-dom/client';
|
|
24
24
|
import { I18nextProvider } from 'react-i18next';
|
|
@@ -30,6 +30,7 @@ import { type ComponentTypeAndString, RouterManager, type RouterOptions } from '
|
|
|
30
30
|
import { WebSocketClient, type WebSocketClientOptions } from './WebSocketClient';
|
|
31
31
|
import { BlankComponent } from './components';
|
|
32
32
|
import { compose, normalizeContainer } from './utils';
|
|
33
|
+
import { defineGlobalDeps } from './utils/globalDeps';
|
|
33
34
|
import type { RequireJS } from './utils/requirejs';
|
|
34
35
|
import { getRequireJs } from './utils/requirejs';
|
|
35
36
|
|
|
@@ -139,10 +140,17 @@ export class Application {
|
|
|
139
140
|
error: observable.ref,
|
|
140
141
|
});
|
|
141
142
|
this.devDynamicImport = options.devDynamicImport;
|
|
142
|
-
this.components =
|
|
143
|
+
this.components = merge(this.components, options.components);
|
|
143
144
|
this.apiClient = new APIClient(options.apiClient);
|
|
144
145
|
this.i18n = options.i18n || createInstance();
|
|
145
146
|
this.router = new RouterManager(options.router, this);
|
|
147
|
+
if (typeof options.router?.basename === 'undefined') {
|
|
148
|
+
const publicPath = this.getPublicPath();
|
|
149
|
+
const basename = publicPath === '/' ? undefined : publicPath.replace(/\/$/, '');
|
|
150
|
+
if (basename) {
|
|
151
|
+
this.router.setBasename(basename);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
146
154
|
this.pluginManager = new PluginManager(options.plugins, options.loadRemotePlugins, this);
|
|
147
155
|
this.flowEngine = new FlowEngine();
|
|
148
156
|
this.flowEngine.registerModels({ ApplicationModel });
|
|
@@ -225,6 +233,7 @@ export class Application {
|
|
|
225
233
|
return;
|
|
226
234
|
}
|
|
227
235
|
window['requirejs'] = this.requirejs = getRequireJs();
|
|
236
|
+
defineGlobalDeps(this.requirejs);
|
|
228
237
|
window.define = this.requirejs.define;
|
|
229
238
|
}
|
|
230
239
|
|
|
@@ -421,7 +430,7 @@ export class Application {
|
|
|
421
430
|
|
|
422
431
|
// Component is a string, try to get it from this.components
|
|
423
432
|
if (typeof Component === 'string') {
|
|
424
|
-
const res =
|
|
433
|
+
const res = get(this.components, Component) as ComponentType<T>;
|
|
425
434
|
if (!res) {
|
|
426
435
|
showError(`Component ${Component} not found`);
|
|
427
436
|
return;
|
|
@@ -443,7 +452,7 @@ export class Application {
|
|
|
443
452
|
console.error('Component must have a displayName or pass name as second argument');
|
|
444
453
|
return;
|
|
445
454
|
}
|
|
446
|
-
|
|
455
|
+
set(this.components, componentName, component);
|
|
447
456
|
}
|
|
448
457
|
|
|
449
458
|
addComponents(components: Record<string, ComponentType>) {
|
package/src/PluginManager.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class PluginManager {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
private async initRemotePlugins() {
|
|
57
|
-
const res = await this.app.apiClient.request({ url: 'pm:
|
|
57
|
+
const res = await this.app.apiClient.request({ url: 'pm:listEnabledV2' });
|
|
58
58
|
const pluginList: PluginData[] = res?.data?.data || [];
|
|
59
59
|
const plugins = await getPlugins({
|
|
60
60
|
requirejs: this.app.requirejs,
|
|
@@ -24,6 +24,7 @@ export interface PluginSettingOptions {
|
|
|
24
24
|
* @default Outlet
|
|
25
25
|
*/
|
|
26
26
|
Component?: RouteType['Component'];
|
|
27
|
+
componentLoader?: RouteType['componentLoader'];
|
|
27
28
|
icon?: string;
|
|
28
29
|
/**
|
|
29
30
|
* sort, the smaller the number, the higher the priority
|
|
@@ -109,6 +110,7 @@ export class PluginSettingsManager {
|
|
|
109
110
|
this.app.router.add(this.getRouteName(name), {
|
|
110
111
|
path: this.getRoutePath(name),
|
|
111
112
|
Component: this.settings[name].Component,
|
|
113
|
+
componentLoader: this.settings[name].componentLoader,
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
|
package/src/RouterManager.tsx
CHANGED
|
@@ -40,8 +40,13 @@ export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRo
|
|
|
40
40
|
routes?: Record<string, RouteType>;
|
|
41
41
|
};
|
|
42
42
|
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
|
43
|
+
export type ComponentLoaderResult =
|
|
44
|
+
| { default?: ComponentTypeAndString; Component?: ComponentTypeAndString }
|
|
45
|
+
| ComponentTypeAndString;
|
|
46
|
+
export type ComponentLoader = () => Promise<ComponentLoaderResult>;
|
|
43
47
|
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
44
48
|
Component?: ComponentTypeAndString;
|
|
49
|
+
componentLoader?: ComponentLoader;
|
|
45
50
|
skipAuthCheck?: boolean;
|
|
46
51
|
}
|
|
47
52
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
@@ -67,6 +72,45 @@ export class RouterManager {
|
|
|
67
72
|
this.routes = options.routes || {};
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
protected resolveLoadedComponent(moduleOrComponent: ComponentLoaderResult): ComponentTypeAndString | undefined {
|
|
76
|
+
if (!moduleOrComponent) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
if (typeof moduleOrComponent === 'function' || typeof moduleOrComponent === 'string') {
|
|
80
|
+
return moduleOrComponent;
|
|
81
|
+
}
|
|
82
|
+
return moduleOrComponent.default || moduleOrComponent.Component;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected createRouteLazyComponent(componentLoader: ComponentLoader): ComponentType {
|
|
86
|
+
const LazyComponent = React.lazy(() =>
|
|
87
|
+
componentLoader().then((moduleOrComponent) => {
|
|
88
|
+
const loadedComponent = this.resolveLoadedComponent(moduleOrComponent);
|
|
89
|
+
if (!loadedComponent) {
|
|
90
|
+
throw new Error('componentLoader must resolve to a React component or component module.');
|
|
91
|
+
}
|
|
92
|
+
if (typeof loadedComponent === 'string') {
|
|
93
|
+
const StringRouteComponent: ComponentType<any> = (props: Record<string, any>) =>
|
|
94
|
+
this.app.renderComponent(loadedComponent, props);
|
|
95
|
+
return {
|
|
96
|
+
default: StringRouteComponent,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
default: loadedComponent as ComponentType<any>,
|
|
101
|
+
};
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return function RouteLazyComponentWrapper(props: Record<string, any>) {
|
|
106
|
+
return (
|
|
107
|
+
<React.Suspense fallback={null}>
|
|
108
|
+
<LazyComponent {...props} />
|
|
109
|
+
</React.Suspense>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
70
114
|
/**
|
|
71
115
|
* @internal
|
|
72
116
|
*/
|
|
@@ -96,9 +140,11 @@ export class RouterManager {
|
|
|
96
140
|
if (Object.keys(item).length === 1 && item.children) {
|
|
97
141
|
acc.push(...buildRoutesTree(item.children));
|
|
98
142
|
} else {
|
|
99
|
-
const { Component, element, children, ...reset } = item;
|
|
143
|
+
const { Component, componentLoader, element, children, ...reset } = item;
|
|
100
144
|
let ele = element;
|
|
101
|
-
if (
|
|
145
|
+
if (componentLoader) {
|
|
146
|
+
ele = React.createElement(this.createRouteLazyComponent(componentLoader));
|
|
147
|
+
} else if (Component) {
|
|
102
148
|
if (typeof Component === 'string') {
|
|
103
149
|
ele = this.app.renderComponent(Component);
|
|
104
150
|
} else {
|
|
@@ -68,6 +68,61 @@ describe('app', () => {
|
|
|
68
68
|
expect(screen.getByText('Hello Route')).toBeInTheDocument();
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it('should support router componentLoader lazy functionality', async () => {
|
|
72
|
+
class PluginHelloClient extends Plugin {
|
|
73
|
+
async load() {
|
|
74
|
+
this.router.add('root', {
|
|
75
|
+
path: '/',
|
|
76
|
+
componentLoader: async () => ({
|
|
77
|
+
default: () => <div>Hello Lazy Route</div>,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
83
|
+
await renderApp(app);
|
|
84
|
+
expect(await screen.findByText('Hello Lazy Route')).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should support publicPath basename for plugin routes', async () => {
|
|
88
|
+
class PluginHelloClient extends Plugin {
|
|
89
|
+
async load() {
|
|
90
|
+
this.router.add('demo.route', {
|
|
91
|
+
path: '/demo/app-info',
|
|
92
|
+
componentLoader: async () => ({
|
|
93
|
+
default: () => <div>Hello Basename Route</div>,
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const app = createMockClient({
|
|
99
|
+
publicPath: '/v2/',
|
|
100
|
+
plugins: [PluginHelloClient],
|
|
101
|
+
router: { type: 'memory', initialEntries: ['/v2/demo/app-info'] },
|
|
102
|
+
});
|
|
103
|
+
await renderApp(app);
|
|
104
|
+
expect(await screen.findByText('Hello Basename Route')).toBeInTheDocument();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should support plugin settings componentLoader lazy functionality', async () => {
|
|
108
|
+
class PluginHelloClient extends Plugin {
|
|
109
|
+
async load() {
|
|
110
|
+
this.pluginSettingsManager.add('demo', {
|
|
111
|
+
title: 'Demo',
|
|
112
|
+
componentLoader: async () => ({
|
|
113
|
+
default: () => <div>Hello Lazy Settings</div>,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const app = createMockClient({
|
|
119
|
+
plugins: [PluginHelloClient],
|
|
120
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/demo'] },
|
|
121
|
+
});
|
|
122
|
+
await renderApp(app);
|
|
123
|
+
expect(await screen.findByText('Hello Lazy Settings')).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
|
|
71
126
|
it('should show maintaining state', async () => {
|
|
72
127
|
class PluginHelloClient extends Plugin {}
|
|
73
128
|
const app = createMockClient({ plugins: [PluginHelloClient] });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineGlobalDeps } from '../utils/globalDeps';
|
|
11
|
+
|
|
12
|
+
describe('client-v2 defineGlobalDeps', () => {
|
|
13
|
+
it('should register shared AMD dependencies for remote plugins', () => {
|
|
14
|
+
const define = vi.fn();
|
|
15
|
+
|
|
16
|
+
defineGlobalDeps({
|
|
17
|
+
define,
|
|
18
|
+
} as any);
|
|
19
|
+
|
|
20
|
+
expect(define).toHaveBeenCalledWith('react', expect.any(Function));
|
|
21
|
+
expect(define).toHaveBeenCalledWith('react-router-dom', expect.any(Function));
|
|
22
|
+
expect(define).toHaveBeenCalledWith('@formily/react', expect.any(Function));
|
|
23
|
+
expect(define).toHaveBeenCalledWith('@nocobase/client-v2', expect.any(Function));
|
|
24
|
+
expect(define).toHaveBeenCalledWith('@nocobase/flow-engine', expect.any(Function));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createMockClient, Plugin } from '@nocobase/client-v2';
|
|
11
|
+
|
|
12
|
+
describe('PluginManager', () => {
|
|
13
|
+
it('should request remote plugins from pm:listEnabledV2', async () => {
|
|
14
|
+
const app = createMockClient({
|
|
15
|
+
loadRemotePlugins: true,
|
|
16
|
+
});
|
|
17
|
+
app.apiMock.onGet('pm:listEnabledV2').reply(200, { data: [] });
|
|
18
|
+
|
|
19
|
+
await app.load();
|
|
20
|
+
|
|
21
|
+
expect(app.apiMock.history.get).toHaveLength(1);
|
|
22
|
+
expect(app.apiMock.history.get[0]?.url).toBe('pm:listEnabledV2');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should not request remote plugins from pm:listEnabled', async () => {
|
|
26
|
+
const app = createMockClient({
|
|
27
|
+
loadRemotePlugins: true,
|
|
28
|
+
});
|
|
29
|
+
app.apiMock.onGet('pm:listEnabledV2').reply(200, { data: [] });
|
|
30
|
+
|
|
31
|
+
await app.load();
|
|
32
|
+
|
|
33
|
+
expect(app.apiMock.history.get.some((request) => request.url === 'pm:listEnabled')).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should define client-v2 module ids for dev plugins', async () => {
|
|
37
|
+
class DemoPlugin extends Plugin {}
|
|
38
|
+
|
|
39
|
+
const mockDefine: any = vi.fn();
|
|
40
|
+
window.define = mockDefine;
|
|
41
|
+
|
|
42
|
+
const app = createMockClient({
|
|
43
|
+
loadRemotePlugins: true,
|
|
44
|
+
devDynamicImport: vi.fn().mockResolvedValue({ default: DemoPlugin }) as any,
|
|
45
|
+
});
|
|
46
|
+
app.apiMock.onGet('pm:listEnabledV2').reply(200, {
|
|
47
|
+
data: [
|
|
48
|
+
{
|
|
49
|
+
name: '@nocobase/demo',
|
|
50
|
+
packageName: '@nocobase/demo',
|
|
51
|
+
url: 'https://demo.com/dist/client-v2/index.js',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await app.load();
|
|
57
|
+
|
|
58
|
+
expect(mockDefine).toHaveBeenCalledWith('@nocobase/demo/client-v2', expect.any(Function));
|
|
59
|
+
expect(mockDefine).not.toHaveBeenCalledWith('@nocobase/demo/client', expect.any(Function));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Plugin } from '../Plugin';
|
|
11
|
+
import { defineDevPlugins, definePluginClient, getPlugins } from '../utils/remotePlugins';
|
|
12
|
+
|
|
13
|
+
describe('client-v2 remotePlugins', () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
window.define = undefined;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should define dev plugins with /client-v2 module ids', () => {
|
|
19
|
+
class DemoPlugin extends Plugin {}
|
|
20
|
+
|
|
21
|
+
const mockDefine: any = vi.fn();
|
|
22
|
+
window.define = mockDefine;
|
|
23
|
+
|
|
24
|
+
defineDevPlugins({
|
|
25
|
+
'@nocobase/demo': DemoPlugin,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(mockDefine).toHaveBeenCalledWith('@nocobase/demo/client-v2', expect.any(Function));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should define remote plugin proxies with /client-v2 module ids', () => {
|
|
32
|
+
const mockDefine: any = vi.fn();
|
|
33
|
+
window.define = mockDefine;
|
|
34
|
+
|
|
35
|
+
definePluginClient('@nocobase/demo');
|
|
36
|
+
|
|
37
|
+
expect(mockDefine).toHaveBeenCalledWith(
|
|
38
|
+
'@nocobase/demo/client-v2',
|
|
39
|
+
['exports', '@nocobase/demo'],
|
|
40
|
+
expect.any(Function),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not define /client aliases when loading v2 plugins', async () => {
|
|
45
|
+
class DemoPlugin extends Plugin {}
|
|
46
|
+
|
|
47
|
+
const requirejs: any = {
|
|
48
|
+
requirejs: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
requirejs.requirejs.config = vi.fn();
|
|
51
|
+
requirejs.requirejs.requirejs = vi.fn();
|
|
52
|
+
|
|
53
|
+
const mockDefine: any = vi.fn();
|
|
54
|
+
window.define = mockDefine;
|
|
55
|
+
|
|
56
|
+
const plugins = await getPlugins({
|
|
57
|
+
requirejs,
|
|
58
|
+
pluginData: [
|
|
59
|
+
{
|
|
60
|
+
name: '@nocobase/demo',
|
|
61
|
+
packageName: '@nocobase/demo',
|
|
62
|
+
url: 'https://demo.com/dist/client-v2/index.js',
|
|
63
|
+
},
|
|
64
|
+
] as any,
|
|
65
|
+
devDynamicImport: vi.fn().mockResolvedValue({ default: DemoPlugin }) as any,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(plugins).toEqual([['@nocobase/demo', DemoPlugin]]);
|
|
69
|
+
expect(mockDefine).toHaveBeenCalledWith('@nocobase/demo/client-v2', expect.any(Function));
|
|
70
|
+
expect(mockDefine).not.toHaveBeenCalledWith('@nocobase/demo/client', expect.any(Function));
|
|
71
|
+
});
|
|
72
|
+
});
|