@nocobase/app 3.0.0-alpha.2 → 3.0.0-alpha.4
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/__tests__/browserChecker.test.ts +24 -40
- package/__tests__/clientDevProxy.test.ts +34 -0
- package/client/rsbuild.config.ts +4 -5
- package/client-v2/public/browser-checker.js +1 -49
- package/client-v2/rsbuild.config.ts +8 -6
- package/clientDevProxy.ts +32 -0
- package/dist/client/assets/{index-c94119b4.js → index-54849ef3.js} +256 -256
- package/dist/client/assets/{vendor-antd-b77deb8f.js → vendor-antd-f421fb70.js} +4 -4
- package/dist/client/index.html +1 -1
- package/dist/client/index.html.tpl +1 -1
- package/dist/client/settings/assets/{index-3da8ef5b.js → index-e8b81081.js} +158 -98
- package/dist/client/settings/browser-checker.js +1 -49
- package/dist/client/settings/index.html +1 -1
- package/dist/client/v/assets/{index-41529ca7.js → index-8ecef45a.js} +10 -10
- package/dist/client/v/browser-checker.js +1 -49
- package/dist/client/v/index.html +1 -1
- package/package.json +7 -7
- /package/dist/client/assets/{index-c94119b4.js.LICENSE.txt → index-54849ef3.js.LICENSE.txt} +0 -0
- /package/dist/client/settings/assets/{index-3da8ef5b.js.LICENSE.txt → index-e8b81081.js.LICENSE.txt} +0 -0
- /package/dist/client/v/assets/{index-41529ca7.js.LICENSE.txt → index-8ecef45a.js.LICENSE.txt} +0 -0
|
@@ -75,47 +75,31 @@ function runBrowserChecker(options: RunBrowserCheckerOptions) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
describe('v2 browser checker', () => {
|
|
78
|
-
it('
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pathname: '/v/',
|
|
90
|
-
}).replacements,
|
|
91
|
-
).toEqual([]);
|
|
92
|
-
|
|
93
|
-
expect(
|
|
94
|
-
runBrowserChecker({
|
|
95
|
-
pathname: '/v/',
|
|
96
|
-
}).replacements,
|
|
97
|
-
).toEqual([]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('redirects scoped modern client roots to scoped Settings only in modern-only mode', () => {
|
|
101
|
-
expect(
|
|
102
|
-
runBrowserChecker({
|
|
103
|
-
appClientEntryMode: 'modern-only',
|
|
104
|
-
pathname: '/v/apps/demo',
|
|
105
|
-
search: '?from=admin',
|
|
106
|
-
hash: '#portal',
|
|
107
|
-
}).replacements,
|
|
108
|
-
).toEqual(['https://example.test/settings/apps/demo?from=admin#portal']);
|
|
78
|
+
it.each([undefined, 'modern-default', 'modern-only'] as const)(
|
|
79
|
+
'lets the modern client handle its root for entry mode %s',
|
|
80
|
+
(appClientEntryMode) => {
|
|
81
|
+
expect(
|
|
82
|
+
runBrowserChecker({
|
|
83
|
+
appClientEntryMode,
|
|
84
|
+
pathname: '/v/',
|
|
85
|
+
}).replacements,
|
|
86
|
+
).toEqual([]);
|
|
87
|
+
},
|
|
88
|
+
);
|
|
109
89
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
90
|
+
it.each([undefined, 'modern-default', 'modern-only'] as const)(
|
|
91
|
+
'lets scoped modern clients handle their roots for entry mode %s',
|
|
92
|
+
(appClientEntryMode) => {
|
|
93
|
+
expect(
|
|
94
|
+
runBrowserChecker({
|
|
95
|
+
appClientEntryMode,
|
|
96
|
+
pathname: '/v/apps/demo',
|
|
97
|
+
search: '?from=admin',
|
|
98
|
+
hash: '#portal',
|
|
99
|
+
}).replacements,
|
|
100
|
+
).toEqual([]);
|
|
101
|
+
},
|
|
102
|
+
);
|
|
119
103
|
|
|
120
104
|
it('keeps Settings scoped trailing slash normalization independent from entry mode', () => {
|
|
121
105
|
expect(
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { describe, expect, it } from 'vitest';
|
|
11
|
+
import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
|
|
12
|
+
|
|
13
|
+
describe('client dev proxy', () => {
|
|
14
|
+
it.each([
|
|
15
|
+
['/x', '/x/', true],
|
|
16
|
+
['/x/', '/x/', true],
|
|
17
|
+
['/x/test2/assets/index.js', '/x/', true],
|
|
18
|
+
['/x-other', '/x/', false],
|
|
19
|
+
['/console/v?from=portal', '/console/v/', true],
|
|
20
|
+
['/v', '/console/v/', false],
|
|
21
|
+
])('matches complete client base paths: %s', (url, basePath, expected) => {
|
|
22
|
+
expect(isClientDevProxyPath(url, basePath)).toBe(expected);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.each([
|
|
26
|
+
['/v', '/v/', '/v/'],
|
|
27
|
+
['/v?from=portal', '/v/', '/v/?from=portal'],
|
|
28
|
+
['/console/v#section', '/console/v/', '/console/v/#section'],
|
|
29
|
+
['/v/admin', '/v/', '/v/admin'],
|
|
30
|
+
['/v/', '/v/', '/v/'],
|
|
31
|
+
])('normalizes only a slashless client root: %s', (url, basePath, expected) => {
|
|
32
|
+
expect(rewriteClientDevProxyRootPath(url, basePath)).toBe(expected);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/client/rsbuild.config.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
|
|
|
16
16
|
import { pluginReact } from '@rsbuild/plugin-react';
|
|
17
17
|
import { pluginSvgr } from '@rsbuild/plugin-svgr';
|
|
18
18
|
import { generatePlugins, getRsbuildBrowserAlias } from '@nocobase/devtools/rsbuildConfig';
|
|
19
|
+
import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
|
|
19
20
|
import { createSettingsDevProxyOptions } from '../settingsDevProxy';
|
|
20
21
|
|
|
21
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -259,17 +260,15 @@ export default defineConfig(({ command }) => {
|
|
|
259
260
|
xfwd: true,
|
|
260
261
|
},
|
|
261
262
|
{
|
|
262
|
-
context: v2BasePath,
|
|
263
|
+
context: (pathname) => isClientDevProxyPath(pathname, v2BasePath),
|
|
263
264
|
target: `http://127.0.0.1:${v2Port}`,
|
|
264
265
|
changeOrigin: true,
|
|
265
266
|
ws: true,
|
|
266
|
-
pathRewrite:
|
|
267
|
-
[`^${v2BasePath}`]: v2BasePath,
|
|
268
|
-
},
|
|
267
|
+
pathRewrite: (pathname) => rewriteClientDevProxyRootPath(pathname, v2BasePath),
|
|
269
268
|
xfwd: true,
|
|
270
269
|
},
|
|
271
270
|
{
|
|
272
|
-
context: portalBasePath,
|
|
271
|
+
context: (pathname) => isClientDevProxyPath(pathname, portalBasePath),
|
|
273
272
|
target: proxyTargetUrl,
|
|
274
273
|
changeOrigin: true,
|
|
275
274
|
ws: true,
|
|
@@ -17,46 +17,6 @@ function normalizePublicPath(value) {
|
|
|
17
17
|
return ensureTrailingSlash(normalized);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function normalizePathname(value) {
|
|
21
|
-
const normalized = ensureLeadingSlash(String(value || '/').trim() || '/').replace(/\/{2,}/g, '/');
|
|
22
|
-
return normalized === '/' ? normalized : normalized.replace(/\/+$/g, '');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function resolveSettingsRootPath(publicPath, modernPrefix, pathname) {
|
|
26
|
-
const normalizedPublicPath = normalizePublicPath(publicPath);
|
|
27
|
-
const normalizedModernPrefix =
|
|
28
|
-
String(modernPrefix || 'v')
|
|
29
|
-
.trim()
|
|
30
|
-
.replace(/^\/+|\/+$/g, '') || 'v';
|
|
31
|
-
const modernPublicPathSuffix = `/${normalizedModernPrefix}/`;
|
|
32
|
-
if (!normalizedPublicPath.endsWith(modernPublicPathSuffix)) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const modernRootPath = normalizePathname(normalizedPublicPath);
|
|
37
|
-
const normalizedPathname = normalizePathname(pathname);
|
|
38
|
-
let relativePath = '';
|
|
39
|
-
if (normalizedPathname !== modernRootPath) {
|
|
40
|
-
if (!normalizedPathname.startsWith(`${modernRootPath}/`)) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
relativePath = normalizedPathname.slice(modernRootPath.length + 1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let appScope = '';
|
|
47
|
-
if (relativePath) {
|
|
48
|
-
const match = /^(apps|_app)\/([^/]+)$/.exec(relativePath);
|
|
49
|
-
if (!match) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
appScope = `/${match[1]}/${match[2]}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const rootPublicPath = normalizedPublicPath.slice(0, -(normalizedModernPrefix.length + 1));
|
|
56
|
-
const rootPrefix = rootPublicPath === '/' ? '' : rootPublicPath.replace(/\/+$/g, '');
|
|
57
|
-
return `${rootPrefix}/settings${appScope}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
20
|
function isSettingsBrowserCheckerScript(script) {
|
|
61
21
|
const source = String((script && script.src) || '').split(/[?#]/)[0];
|
|
62
22
|
return source.endsWith('/settings/browser-checker.js');
|
|
@@ -81,19 +41,11 @@ function resolveScopedSettingsRootPath(publicPath, pathname) {
|
|
|
81
41
|
const basename = normalizePublicPath(window['__nocobase_public_path__'] || '/');
|
|
82
42
|
const currentPath = ensureLeadingSlash(String(window.location.pathname || '/').trim() || '/').replace(/\/{2,}/g, '/');
|
|
83
43
|
const basenameWithoutTrailingSlash = basename === '/' ? '/' : basename.replace(/\/+$/, '');
|
|
84
|
-
const appClientEntryMode = window['__nocobase_app_client_entry_mode__'];
|
|
85
|
-
const settingsRootPath =
|
|
86
|
-
appClientEntryMode === 'modern-only'
|
|
87
|
-
? resolveSettingsRootPath(basename, window['__nocobase_modern_client_prefix__'], currentPath)
|
|
88
|
-
: null;
|
|
89
44
|
const scopedSettingsRootPath = isSettingsBrowserCheckerScript(document.currentScript)
|
|
90
45
|
? resolveScopedSettingsRootPath(basename, currentPath)
|
|
91
46
|
: null;
|
|
92
47
|
|
|
93
|
-
if (
|
|
94
|
-
const newUrl = `${window.location.origin}${settingsRootPath}${window.location.search}${window.location.hash}`;
|
|
95
|
-
window.location.replace(newUrl);
|
|
96
|
-
} else if (scopedSettingsRootPath) {
|
|
48
|
+
if (scopedSettingsRootPath) {
|
|
97
49
|
const newUrl = `${window.location.origin}${scopedSettingsRootPath}${window.location.search}${window.location.hash}`;
|
|
98
50
|
window.location.replace(newUrl);
|
|
99
51
|
} else if (basename !== '/' && currentPath === basenameWithoutTrailingSlash) {
|
|
@@ -15,6 +15,7 @@ import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
|
|
|
15
15
|
import { pluginReact } from '@rsbuild/plugin-react';
|
|
16
16
|
import { pluginSvgr } from '@rsbuild/plugin-svgr';
|
|
17
17
|
import { generateV2Plugins, getRsbuildBrowserAlias } from '@nocobase/devtools/rsbuildConfig';
|
|
18
|
+
import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
|
|
18
19
|
import { createSettingsDevProxyOptions, isSettingsDevPath } from '../settingsDevProxy';
|
|
19
20
|
|
|
20
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -300,7 +301,7 @@ export default defineConfig(({ command }) => {
|
|
|
300
301
|
changeOrigin: true,
|
|
301
302
|
},
|
|
302
303
|
{
|
|
303
|
-
context: portalBasePath,
|
|
304
|
+
context: (pathname) => isClientDevProxyPath(pathname, portalBasePath),
|
|
304
305
|
target: proxyTargetUrl,
|
|
305
306
|
changeOrigin: true,
|
|
306
307
|
ws: true,
|
|
@@ -324,11 +325,12 @@ export default defineConfig(({ command }) => {
|
|
|
324
325
|
lazyCompilation: false,
|
|
325
326
|
setupMiddlewares: [
|
|
326
327
|
(middlewares) => {
|
|
327
|
-
if (appClientEntryMode !== 'modern-only') {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
328
|
middlewares.unshift((req, res, next) => {
|
|
329
|
+
req.url = rewriteClientDevProxyRootPath(String(req.url || '/'), v2PublicPath);
|
|
330
|
+
if (appClientEntryMode !== 'modern-only') {
|
|
331
|
+
next();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
332
334
|
const [rawPathname = '/', query = ''] = String(req.url || '/').split('?');
|
|
333
335
|
const pathname = normalizePathname(rawPathname);
|
|
334
336
|
if (
|
|
@@ -337,7 +339,7 @@ export default defineConfig(({ command }) => {
|
|
|
337
339
|
pathname.startsWith(apiBasePath) ||
|
|
338
340
|
pathname.startsWith(wsBasePath) ||
|
|
339
341
|
pathname.startsWith(localStorageBasePath) ||
|
|
340
|
-
pathname
|
|
342
|
+
isClientDevProxyPath(pathname, portalBasePath) ||
|
|
341
343
|
pathname.startsWith(staticBasePath) ||
|
|
342
344
|
isSettingsDevPath(pathname, appPublicPath)
|
|
343
345
|
) {
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
function normalizeBasePath(value: string) {
|
|
11
|
+
let normalized = value || '/';
|
|
12
|
+
if (!normalized.startsWith('/')) {
|
|
13
|
+
normalized = `/${normalized}`;
|
|
14
|
+
}
|
|
15
|
+
normalized = normalized.replace(/\/{2,}/g, '/');
|
|
16
|
+
return normalized === '/' ? normalized : normalized.replace(/\/+$/g, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isClientDevProxyPath(url: string, basePath: string) {
|
|
20
|
+
const [pathname] = String(url || '/').split(/[?#]/, 1);
|
|
21
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
22
|
+
return pathname === normalizedBasePath || pathname.startsWith(`${normalizedBasePath}/`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rewriteClientDevProxyRootPath(url: string, basePath: string) {
|
|
26
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
27
|
+
const [pathname] = String(url || '/').split(/[?#]/, 1);
|
|
28
|
+
if (pathname !== normalizedBasePath || normalizedBasePath === '/') {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
return `${normalizedBasePath}/${url.slice(pathname.length)}`;
|
|
32
|
+
}
|