@nocobase/flow-engine 2.0.0-beta.9 → 2.0.1

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.
Files changed (245) hide show
  1. package/lib/BlockScopedFlowEngine.js +0 -1
  2. package/lib/FlowDefinition.d.ts +2 -0
  3. package/lib/JSRunner.d.ts +6 -0
  4. package/lib/JSRunner.js +32 -2
  5. package/lib/ViewScopedFlowEngine.js +3 -0
  6. package/lib/acl/Acl.js +13 -3
  7. package/lib/components/FlowContextSelector.js +155 -10
  8. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  9. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
  10. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  11. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
  12. package/lib/components/variables/VariableInput.js +9 -4
  13. package/lib/components/variables/VariableTag.js +46 -39
  14. package/lib/components/variables/utils.d.ts +7 -0
  15. package/lib/components/variables/utils.js +42 -2
  16. package/lib/data-source/index.d.ts +7 -27
  17. package/lib/data-source/index.js +81 -51
  18. package/lib/executor/FlowExecutor.d.ts +2 -1
  19. package/lib/executor/FlowExecutor.js +163 -22
  20. package/lib/flowContext.d.ts +230 -7
  21. package/lib/flowContext.js +2267 -148
  22. package/lib/flowEngine.d.ts +21 -0
  23. package/lib/flowEngine.js +56 -8
  24. package/lib/flowI18n.js +6 -4
  25. package/lib/flowSettings.js +17 -11
  26. package/lib/index.d.ts +7 -1
  27. package/lib/index.js +21 -0
  28. package/lib/locale/en-US.json +9 -2
  29. package/lib/locale/index.d.ts +14 -0
  30. package/lib/locale/zh-CN.json +8 -1
  31. package/lib/models/CollectionFieldModel.d.ts +1 -0
  32. package/lib/models/CollectionFieldModel.js +3 -2
  33. package/lib/models/flowModel.js +12 -1
  34. package/lib/provider.js +5 -5
  35. package/lib/resources/baseRecordResource.d.ts +5 -0
  36. package/lib/resources/baseRecordResource.js +24 -0
  37. package/lib/resources/multiRecordResource.d.ts +1 -0
  38. package/lib/resources/multiRecordResource.js +11 -4
  39. package/lib/resources/singleRecordResource.js +2 -0
  40. package/lib/resources/sqlResource.d.ts +4 -3
  41. package/lib/resources/sqlResource.js +8 -3
  42. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
  43. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
  44. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
  45. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
  46. package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
  47. package/lib/runjs-context/contexts/base.js +706 -41
  48. package/lib/runjs-context/contributions.d.ts +33 -0
  49. package/lib/runjs-context/contributions.js +88 -0
  50. package/lib/runjs-context/helpers.js +12 -1
  51. package/lib/runjs-context/setup.js +6 -0
  52. package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
  53. package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
  54. package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
  55. package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
  56. package/lib/runjs-context/snippets/index.d.ts +11 -1
  57. package/lib/runjs-context/snippets/index.js +61 -40
  58. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
  59. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
  60. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
  61. package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
  62. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
  63. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
  64. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
  65. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
  66. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
  67. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
  68. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
  69. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
  70. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
  71. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
  72. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
  73. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
  74. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
  75. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
  76. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
  77. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
  78. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
  79. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
  80. package/lib/runjsLibs.d.ts +28 -0
  81. package/lib/runjsLibs.js +532 -0
  82. package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
  83. package/lib/scheduler/ModelOperationScheduler.js +25 -21
  84. package/lib/types.d.ts +27 -0
  85. package/lib/utils/associationObjectVariable.d.ts +2 -2
  86. package/lib/utils/createCollectionContextMeta.js +1 -0
  87. package/lib/utils/createEphemeralContext.js +2 -2
  88. package/lib/utils/dateVariable.d.ts +16 -0
  89. package/lib/utils/dateVariable.js +380 -0
  90. package/lib/utils/exceptions.d.ts +7 -0
  91. package/lib/utils/exceptions.js +10 -0
  92. package/lib/utils/index.d.ts +8 -3
  93. package/lib/utils/index.js +45 -0
  94. package/lib/utils/params-resolvers.js +16 -9
  95. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  96. package/lib/utils/resolveModuleUrl.js +65 -0
  97. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  98. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  99. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  100. package/lib/utils/runjsModuleLoader.js +422 -0
  101. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  102. package/lib/utils/runjsTemplateCompat.js +743 -0
  103. package/lib/utils/runjsValue.d.ts +29 -0
  104. package/lib/utils/runjsValue.js +275 -0
  105. package/lib/utils/safeGlobals.d.ts +18 -8
  106. package/lib/utils/safeGlobals.js +164 -17
  107. package/lib/utils/schema-utils.d.ts +10 -0
  108. package/lib/utils/schema-utils.js +61 -0
  109. package/lib/views/createViewMeta.d.ts +0 -7
  110. package/lib/views/createViewMeta.js +19 -70
  111. package/lib/views/index.d.ts +1 -2
  112. package/lib/views/index.js +4 -3
  113. package/lib/views/useDialog.js +7 -2
  114. package/lib/views/useDrawer.js +7 -2
  115. package/lib/views/usePage.d.ts +4 -0
  116. package/lib/views/usePage.js +43 -6
  117. package/lib/views/usePopover.js +4 -1
  118. package/lib/views/viewEvents.d.ts +17 -0
  119. package/lib/views/viewEvents.js +90 -0
  120. package/package.json +4 -4
  121. package/src/BlockScopedFlowEngine.ts +2 -5
  122. package/src/JSRunner.ts +44 -2
  123. package/src/ViewScopedFlowEngine.ts +4 -0
  124. package/src/__tests__/JSRunner.test.ts +64 -0
  125. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  126. package/src/__tests__/flowContext.test.ts +693 -1
  127. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  128. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  129. package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
  130. package/src/__tests__/flowRuntimeContext.test.ts +2 -1
  131. package/src/__tests__/flowSettings.open.test.tsx +123 -19
  132. package/src/__tests__/runjsContext.test.ts +10 -7
  133. package/src/__tests__/runjsContextImplementations.test.ts +34 -3
  134. package/src/__tests__/runjsContextRuntime.test.ts +3 -3
  135. package/src/__tests__/runjsContributions.test.ts +89 -0
  136. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  137. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  138. package/src/__tests__/runjsLocales.test.ts +4 -1
  139. package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
  140. package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
  141. package/src/__tests__/runjsSnippets.test.ts +40 -3
  142. package/src/acl/Acl.tsx +3 -3
  143. package/src/components/FlowContextSelector.tsx +208 -12
  144. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  145. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  146. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
  147. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  148. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
  149. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
  150. package/src/components/variables/VariableInput.tsx +12 -4
  151. package/src/components/variables/VariableTag.tsx +54 -45
  152. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
  153. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  154. package/src/components/variables/__tests__/utils.test.ts +81 -3
  155. package/src/components/variables/utils.ts +67 -6
  156. package/src/data-source/index.ts +85 -110
  157. package/src/executor/FlowExecutor.ts +200 -23
  158. package/src/executor/__tests__/flowExecutor.test.ts +66 -0
  159. package/src/flowContext.ts +2986 -211
  160. package/src/flowEngine.ts +59 -8
  161. package/src/flowI18n.ts +7 -5
  162. package/src/flowSettings.ts +18 -12
  163. package/src/index.ts +14 -1
  164. package/src/locale/en-US.json +9 -2
  165. package/src/locale/zh-CN.json +8 -1
  166. package/src/models/CollectionFieldModel.tsx +3 -1
  167. package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
  168. package/src/models/__tests__/flowModel.test.ts +20 -4
  169. package/src/models/flowModel.tsx +13 -1
  170. package/src/provider.tsx +7 -6
  171. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  172. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  173. package/src/resources/baseRecordResource.ts +31 -0
  174. package/src/resources/multiRecordResource.ts +11 -4
  175. package/src/resources/singleRecordResource.ts +3 -0
  176. package/src/resources/sqlResource.ts +11 -6
  177. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
  178. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
  179. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
  180. package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
  181. package/src/runjs-context/contexts/base.ts +715 -44
  182. package/src/runjs-context/contributions.ts +88 -0
  183. package/src/runjs-context/helpers.ts +11 -1
  184. package/src/runjs-context/setup.ts +6 -0
  185. package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
  186. package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
  187. package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
  188. package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
  189. package/src/runjs-context/snippets/index.ts +75 -41
  190. package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
  191. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
  192. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
  193. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
  194. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
  195. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
  196. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
  197. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
  198. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
  199. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
  200. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
  201. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
  202. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
  203. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
  204. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
  205. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
  206. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
  207. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
  208. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
  209. package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
  210. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
  211. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
  212. package/src/runjsLibs.ts +622 -0
  213. package/src/scheduler/ModelOperationScheduler.ts +27 -21
  214. package/src/types.ts +38 -1
  215. package/src/utils/__tests__/dateVariable.test.ts +101 -0
  216. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  217. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  218. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  219. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  220. package/src/utils/__tests__/safeGlobals.test.ts +57 -2
  221. package/src/utils/__tests__/utils.test.ts +95 -0
  222. package/src/utils/associationObjectVariable.ts +2 -2
  223. package/src/utils/createCollectionContextMeta.ts +1 -0
  224. package/src/utils/createEphemeralContext.ts +5 -4
  225. package/src/utils/dateVariable.ts +397 -0
  226. package/src/utils/exceptions.ts +11 -0
  227. package/src/utils/index.ts +37 -3
  228. package/src/utils/params-resolvers.ts +23 -9
  229. package/src/utils/resolveModuleUrl.ts +91 -0
  230. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  231. package/src/utils/runjsModuleLoader.ts +553 -0
  232. package/src/utils/runjsTemplateCompat.ts +828 -0
  233. package/src/utils/runjsValue.ts +287 -0
  234. package/src/utils/safeGlobals.ts +188 -17
  235. package/src/utils/schema-utils.ts +79 -0
  236. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  237. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
  238. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  239. package/src/views/createViewMeta.ts +22 -75
  240. package/src/views/index.tsx +1 -2
  241. package/src/views/useDialog.tsx +8 -1
  242. package/src/views/useDrawer.tsx +8 -1
  243. package/src/views/usePage.tsx +51 -5
  244. package/src/views/usePopover.tsx +4 -1
  245. package/src/views/viewEvents.ts +55 -0
@@ -0,0 +1,553 @@
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 { setRunJSLibOverride } from '../runjsLibs';
11
+ import { resolveModuleUrl } from './resolveModuleUrl';
12
+ import { registerRunJSSafeDocumentGlobals, registerRunJSSafeWindowGlobals } from './safeGlobals';
13
+
14
+ /**
15
+ * RunJS 外部模块加载辅助(浏览器侧)。
16
+ *
17
+ * 背景:
18
+ * - NocoBase 前端整体使用 requirejs(AMD)来加载插件与模块;
19
+ * - RunJS 场景下又需要支持 `ctx.importAsync(url)` 直接用浏览器原生 `import()` 加载远端 ESM;
20
+ * - 某些第三方库(典型:UMD/CJS 产物,例如 lodash)会在运行时探测 `define.amd`:
21
+ * - 若存在 AMD,则优先走 `define(...)` 分支;
22
+ * - 但像 esm.sh 这类 CDN 的“CJS/UMD → ESM 包装”往往依赖 `module.exports` 提取导出;
23
+ * - 当 UMD 走了 AMD 分支时,`module.exports` 不会被赋值,最终表现为 ESM 导出(default/命名导出)为 `undefined`。
24
+ *
25
+ * 这里的策略:
26
+ * 1) 全局串行锁:避免 `requireAsync` / `importAsync` 并发时互相影响(尤其是临时 patch 全局 `define.amd`)。
27
+ * 2) 尽量缩小影响窗口:先预取模块(不改全局),再等待 requirejs 空闲,最后只在真正执行 `import()` 的瞬间临时屏蔽 `define.amd`。
28
+ * 3) 所有操作均为 best-effort:内部结构变化、浏览器能力差异等情况下不会阻断正常流程。
29
+ */
30
+
31
+ type RequireJsLike =
32
+ | ((deps: string[], onLoad: (...args: any[]) => void, onError: (err: any) => void) => void)
33
+ | undefined;
34
+
35
+ type ParsedPackageSpecifier = {
36
+ name: string;
37
+ version?: string;
38
+ subpath?: string;
39
+ };
40
+
41
+ function snapshotOwnKeys(obj: any): string[] {
42
+ try {
43
+ if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) return [];
44
+ return Object.getOwnPropertyNames(obj);
45
+ } catch (_) {
46
+ return [];
47
+ }
48
+ }
49
+
50
+ function diffAddedKeys(afterKeys: string[], beforeKeys: string[]): string[] {
51
+ if (!afterKeys.length) return [];
52
+ if (!beforeKeys.length) return [...afterKeys];
53
+ const beforeSet = new Set(beforeKeys);
54
+ const added: string[] = [];
55
+ for (const k of afterKeys) {
56
+ if (!beforeSet.has(k)) added.push(k);
57
+ }
58
+ return added;
59
+ }
60
+
61
+ /**
62
+ * 使用全局 Promise 链实现“互斥锁”:
63
+ * - 锁存放在 `globalThis.__nocobaseRunjsModuleLoadLock`;
64
+ * - 每次进入都会等待上一个任务完成;
65
+ * - 无论任务成功/失败都会释放,避免死锁。
66
+ *
67
+ * 注意:这是 RunJS 运行时的全局锁(同一页面内共享),用于保护对全局对象的临时改动。
68
+ */
69
+ async function withRunjsModuleLoadLock<T>(task: () => Promise<T>): Promise<T> {
70
+ const g = globalThis as any;
71
+ g.__nocobaseRunjsModuleLoadLock = (g.__nocobaseRunjsModuleLoadLock || Promise.resolve()).catch(() => {});
72
+ const prev: Promise<void> = g.__nocobaseRunjsModuleLoadLock;
73
+ let release: (() => void) | undefined;
74
+ const current = new Promise<void>((resolve) => {
75
+ release = resolve;
76
+ });
77
+ g.__nocobaseRunjsModuleLoadLock = prev.then(() => current);
78
+ await prev;
79
+ try {
80
+ return await task();
81
+ } finally {
82
+ release?.();
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 临时屏蔽 `define.amd`(而不是删除 define),让 UMD 库不要走 AMD 分支。
88
+ *
89
+ * 关键点:
90
+ * - 只在 `globalThis.define` 且 `define.amd` 存在时生效;
91
+ * - 通过 property descriptor 恢复原状,尽量不破坏 requirejs 对 `define.amd` 的定义方式;
92
+ * - 返回 restore 函数,调用后恢复到原先状态。
93
+ */
94
+ function disableAmdDefineTemporarily(): () => void {
95
+ const g = globalThis as any;
96
+ const def = g?.define;
97
+ const amd = def?.amd;
98
+ if (!def || !amd) return () => {};
99
+
100
+ const desc = Object.getOwnPropertyDescriptor(def, 'amd');
101
+ const hadOwn = !!desc;
102
+ const prev = amd;
103
+
104
+ try {
105
+ if (desc?.writable) {
106
+ def.amd = undefined;
107
+ } else if (desc?.configurable) {
108
+ delete def.amd;
109
+ } else {
110
+ def.amd = undefined;
111
+ }
112
+ } catch (_) {
113
+ // 忽略异常(best-effort,避免影响主流程)
114
+ }
115
+
116
+ return () => {
117
+ try {
118
+ if (hadOwn && desc) {
119
+ // 恢复原始属性描述符(兼容数据/访问器 descriptor)。
120
+ Object.defineProperty(def, 'amd', desc);
121
+ return;
122
+ }
123
+ def.amd = prev;
124
+ } catch (_) {
125
+ // 忽略异常(best-effort,避免影响主流程)
126
+ }
127
+ };
128
+ }
129
+
130
+ /**
131
+ * 判断 requirejs 当前是否仍有“正在加载、尚未初始化”的模块。
132
+ *
133
+ * 实现方式:
134
+ * - requirejs 内部会维护 `requirejs.s.contexts[*].registry`;
135
+ * - 我们 best-effort 扫描 registry,复用 requirejs `checkLoaded()` 的信号:
136
+ * `enabled && fetched && !inited && map.isDefine` => 仍处于 define() 加载链路中。
137
+ *
138
+ * 注意:这是内部结构探测,requirejs 升级/改动时可能失效,但失效时会返回 false(不阻断流程)。
139
+ */
140
+ function hasPendingRequireJsLoads(): boolean {
141
+ const g = globalThis as any;
142
+ const req = g?.requirejs?.requirejs;
143
+ const contexts = req?.s?.contexts;
144
+ if (!contexts || typeof contexts !== 'object') return false;
145
+ for (const ctxName of Object.keys(contexts)) {
146
+ const ctx = contexts[ctxName];
147
+ const registry = ctx?.registry;
148
+ if (!registry || typeof registry !== 'object') continue;
149
+ for (const id of Object.keys(registry)) {
150
+ const mod = registry[id];
151
+ if (!mod || typeof mod !== 'object') continue;
152
+ if (!mod.enabled) continue;
153
+ if (mod.error) continue;
154
+ const map = mod.map;
155
+ if (!map || typeof map !== 'object') continue;
156
+ // 复用 requirejs `checkLoaded()` 的判定信号:识别仍在加载 define() 模块的情况。
157
+ if (!mod.inited && mod.fetched && map.isDefine) return true;
158
+ }
159
+ }
160
+ return false;
161
+ }
162
+
163
+ /**
164
+ * 等待 requirejs “空闲”(没有挂起加载):
165
+ * - 轮询 `hasPendingRequireJsLoads()`;
166
+ * - 达到超时后直接放行(best-effort,不阻塞用户逻辑)。
167
+ */
168
+ async function waitForRequireJsIdle(options?: { timeoutMs?: number; pollMs?: number }): Promise<void> {
169
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? 1500);
170
+ const pollMs = Math.max(10, options?.pollMs ?? 30);
171
+ if (timeoutMs === 0) return;
172
+
173
+ const start = Date.now();
174
+ while (hasPendingRequireJsLoads()) {
175
+ if (Date.now() - start >= timeoutMs) return;
176
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 预取 ESM 模块(best-effort)以缩短后续 `import()` 实际执行期间的全局影响窗口。
182
+ *
183
+ * 为什么需要预取:
184
+ * - 我们会在真实 `import()` 期间临时屏蔽 `define.amd`;
185
+ * - 如果此时还要走网络请求,影响窗口会被拉长;
186
+ * - 先做预取(不改全局),可让浏览器缓存命中,从而缩短“屏蔽 amd”的时间。
187
+ *
188
+ * 策略:
189
+ * 1) 优先使用 `<link rel="modulepreload">`(若浏览器支持);
190
+ * 2) 否则用 `fetch` 拉取并 drain body 来“暖缓存”;
191
+ * 3) 结果会缓存在 `globalThis.__nocobaseImportAsyncPrefetchCache`,避免重复预取;
192
+ * 4) 预取失败不影响后续导入(内部会吞掉异常)。
193
+ */
194
+ async function prefetchEsmModule(url: string, options?: { timeoutMs?: number }): Promise<void> {
195
+ const u = typeof url === 'string' ? url.trim() : '';
196
+ if (!u) return;
197
+
198
+ const g = globalThis as any;
199
+ g.__nocobaseImportAsyncPrefetchCache = g.__nocobaseImportAsyncPrefetchCache || new Map<string, Promise<void>>();
200
+ const cache: Map<string, Promise<void>> = g.__nocobaseImportAsyncPrefetchCache;
201
+ if (cache.has(u)) return await cache.get(u);
202
+
203
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? 5000);
204
+
205
+ const task: Promise<void> = (async () => {
206
+ // best-effort:优先用 modulepreload,让后续 dynamic import 尽量复用同一次响应/缓存。
207
+ try {
208
+ if (typeof document !== 'undefined' && document?.createElement) {
209
+ const href = new URL(u, (globalThis as any)?.location?.href || undefined).toString();
210
+ const head = document.head || document.getElementsByTagName('head')[0];
211
+ if (head) {
212
+ const selector = `link[rel="modulepreload"][href="${href}"]`;
213
+ const existing = document.querySelector(selector);
214
+ if (!existing) {
215
+ const link = document.createElement('link');
216
+ link.rel = 'modulepreload';
217
+ link.href = href;
218
+ // 与 dynamic import 的跨域拉取模式对齐。
219
+ (link as any).crossOrigin = 'anonymous';
220
+ const done = await new Promise<void>((resolve) => {
221
+ let settled = false;
222
+ const finalize = () => {
223
+ if (settled) return;
224
+ settled = true;
225
+ link.onload = null;
226
+ link.onerror = null;
227
+ resolve();
228
+ };
229
+ const timer =
230
+ timeoutMs > 0
231
+ ? setTimeout(() => {
232
+ finalize();
233
+ }, timeoutMs)
234
+ : undefined;
235
+ link.onload = () => {
236
+ if (timer) clearTimeout(timer);
237
+ finalize();
238
+ };
239
+ link.onerror = () => {
240
+ if (timer) clearTimeout(timer);
241
+ finalize();
242
+ };
243
+ head.appendChild(link);
244
+ });
245
+ return done;
246
+ }
247
+ }
248
+ }
249
+ } catch (_) {
250
+ // 忽略异常(best-effort,避免影响主流程)
251
+ }
252
+
253
+ // 兜底:用 fetch 拉取并 drain body,以“暖缓存”(尽量让后续 import 命中浏览器缓存)。
254
+ try {
255
+ if (typeof fetch !== 'function') return;
256
+ const controller = typeof AbortController === 'function' ? new AbortController() : undefined;
257
+ const timer =
258
+ controller && timeoutMs > 0
259
+ ? setTimeout(() => {
260
+ try {
261
+ controller.abort();
262
+ } catch (_) {
263
+ // 忽略异常
264
+ }
265
+ }, timeoutMs)
266
+ : undefined;
267
+ const res = await fetch(u, {
268
+ method: 'GET',
269
+ mode: 'cors',
270
+ credentials: 'omit',
271
+ cache: 'force-cache',
272
+ signal: controller?.signal,
273
+ } as any);
274
+ if (timer) clearTimeout(timer);
275
+ // 读取响应体,避免浏览器因未消费 body 而不写入缓存。
276
+ if (res && (res as any).ok && typeof (res as any).arrayBuffer === 'function') {
277
+ try {
278
+ await (res as any).arrayBuffer();
279
+ } catch (_) {
280
+ // 忽略异常
281
+ }
282
+ }
283
+ } catch (_) {
284
+ // 忽略异常(best-effort,避免影响主流程)
285
+ }
286
+ })();
287
+
288
+ // 预取失败不应影响真正 import:吞掉异常。
289
+ const safeTask = task.catch(() => {});
290
+ cache.set(u, safeTask);
291
+ return await safeTask;
292
+ }
293
+
294
+ /**
295
+ * 在 RunJS 内部按 URL 通过 requirejs 加载模块:
296
+ * - 与 `runjsImportAsync` 共用同一把锁,避免并发加载期间全局 patch 互相干扰;
297
+ * - 返回 requirejs 回调的第一个模块值(与现有 `ctx.requireAsync` 行为保持一致)。
298
+ */
299
+ export async function runjsRequireAsync(requirejs: RequireJsLike, url: string): Promise<any> {
300
+ return await withRunjsModuleLoadLock(async () => {
301
+ const beforeWinKeys = typeof window !== 'undefined' ? snapshotOwnKeys(window) : [];
302
+ const beforeDocKeys = typeof document !== 'undefined' ? snapshotOwnKeys(document) : [];
303
+
304
+ let result: any;
305
+ let error: any;
306
+ try {
307
+ result = await new Promise((resolve, reject) => {
308
+ if (!requirejs) {
309
+ reject(new Error('requirejs is not available'));
310
+ return;
311
+ }
312
+ requirejs(
313
+ [url],
314
+ (...args: any[]) => {
315
+ resolve(args[0]);
316
+ },
317
+ reject,
318
+ );
319
+ });
320
+ } catch (e) {
321
+ error = e;
322
+ } finally {
323
+ const afterWinKeys = typeof window !== 'undefined' ? snapshotOwnKeys(window) : [];
324
+ const afterDocKeys = typeof document !== 'undefined' ? snapshotOwnKeys(document) : [];
325
+ const addedWinKeys = diffAddedKeys(afterWinKeys, beforeWinKeys);
326
+ const addedDocKeys = diffAddedKeys(afterDocKeys, beforeDocKeys);
327
+ // Best-effort: allow RunJS safe window/document to access globals introduced by this module load.
328
+ registerRunJSSafeWindowGlobals(addedWinKeys);
329
+ registerRunJSSafeDocumentGlobals(addedDocKeys);
330
+ }
331
+
332
+ if (error) throw error;
333
+ return result;
334
+ });
335
+ }
336
+
337
+ /**
338
+ * 在 RunJS 内部按 URL 动态导入 ESM 模块(返回模块命名空间对象)。
339
+ *
340
+ * 兼容点:
341
+ * - 若检测到 `globalThis.define.amd`,则会在导入前做预取,并在真正执行 `import()` 时短暂屏蔽 `define.amd`;
342
+ * - 为了进一步降低风险,会先等待 requirejs 没有挂起加载;
343
+ * - 使用带 `@vite-ignore` / `webpackIgnore: true` 标记的 dynamic import,避免被打包器重写;
344
+ * - 若仍被拦截,再用 `eval('u => import(u)')` 兜底。
345
+ *
346
+ * 重要说明:
347
+ * - 这里不做 URL 解析(如拼 CDN base/suffix),调用方应传入“最终可 import 的 URL”;
348
+ * - 缓存通常由上层(例如 `ctx.importAsync`)按 URL 做 Map 缓存,此处只负责导入与兼容逻辑。
349
+ */
350
+ export async function runjsImportAsync(url: string): Promise<any> {
351
+ if (!url || typeof url !== 'string') {
352
+ throw new Error('invalid url');
353
+ }
354
+ const u = url.trim();
355
+ const shouldPatchAmd = !!(globalThis as any)?.define?.amd;
356
+ // 尽量缩小全局副作用窗口:
357
+ // 1) 先预取模块(保持 `define.amd` 不动)
358
+ // 2) 等待 requirejs 空闲
359
+ // 3) 仅在模块实例化(import 执行)期间短暂屏蔽 `define.amd`
360
+ if (shouldPatchAmd) await prefetchEsmModule(u);
361
+
362
+ return await withRunjsModuleLoadLock(async () => {
363
+ await waitForRequireJsIdle();
364
+ // 一些 UMD 包(例如 lodash)在检测到 `define.amd` 时会优先走 AMD 分支,
365
+ // 这会导致 esm.sh 等“CJS/UMD → ESM 包装”无法从 module.exports 提取导出(表现为导出为 undefined)。
366
+ // 因此在评估/实例化阶段临时屏蔽 AMD 标志,强制走非 AMD 分支。
367
+ const restoreAmd = shouldPatchAmd ? disableAmdDefineTemporarily() : () => {};
368
+ try {
369
+ // 尝试使用原生 dynamic import(加上 vite/webpack 的 ignore 注释)
370
+ const nativeImport = () => import(/* @vite-ignore */ /* webpackIgnore: true */ u);
371
+ // 兜底方案:通过 eval 在运行时构造 import,避免被打包器接管
372
+ const evalImport = () => {
373
+ const importer = (0, eval)('u => import(u)');
374
+ return importer(u);
375
+ };
376
+ try {
377
+ return await nativeImport();
378
+ } catch (err: any) {
379
+ // 常见于打包产物仍然拦截了 dynamic import 或开发态插件未识别 ignore 注释
380
+ try {
381
+ return await evalImport();
382
+ } catch (err2) {
383
+ throw err2 || err;
384
+ }
385
+ }
386
+ } finally {
387
+ restoreAmd();
388
+ }
389
+ });
390
+ }
391
+
392
+ function stripUrlQueryAndHash(input: string): string {
393
+ const q = input.indexOf('?');
394
+ const h = input.indexOf('#');
395
+ const cut = q >= 0 && h >= 0 ? Math.min(q, h) : q >= 0 ? q : h;
396
+ return cut >= 0 ? input.slice(0, cut) : input;
397
+ }
398
+
399
+ function parsePackageSpecifier(input: string): ParsedPackageSpecifier | null {
400
+ if (!input || typeof input !== 'string') return null;
401
+ const s = stripUrlQueryAndHash(input).trim();
402
+ if (!s) return null;
403
+ if (s.startsWith('http://') || s.startsWith('https://')) return null;
404
+
405
+ const pathPart = s.replace(/^\//, '');
406
+ if (!pathPart) return null;
407
+
408
+ // Scoped package: @scope/name@version/subpath
409
+ if (pathPart.startsWith('@')) {
410
+ const m = pathPart.match(/^(@[^/]+\/[^/]+?)(?:@([^/]+))?(?:\/(.*))?$/);
411
+ if (!m) return null;
412
+ return { name: m[1], version: m[2], subpath: m[3] };
413
+ }
414
+
415
+ // Normal package: name@version/subpath
416
+ const m = pathPart.match(/^([^/]+?)(?:@([^/]+))?(?:\/(.*))?$/);
417
+ if (!m) return null;
418
+ return { name: m[1], version: m[2], subpath: m[3] };
419
+ }
420
+
421
+ function appendQueryParams(specifier: string, params: Record<string, string>): string {
422
+ if (!specifier || typeof specifier !== 'string') return specifier;
423
+ const s = specifier.trim();
424
+ const hashIndex = s.indexOf('#');
425
+ const baseAndQuery = hashIndex >= 0 ? s.slice(0, hashIndex) : s;
426
+ const hash = hashIndex >= 0 ? s.slice(hashIndex) : '';
427
+
428
+ const qIndex = baseAndQuery.indexOf('?');
429
+ const base = qIndex >= 0 ? baseAndQuery.slice(0, qIndex) : baseAndQuery;
430
+ const query = qIndex >= 0 ? baseAndQuery.slice(qIndex + 1) : '';
431
+
432
+ const search = new URLSearchParams(query);
433
+ for (const [k, v] of Object.entries(params)) {
434
+ if (!k) continue;
435
+ if (search.has(k)) continue;
436
+ search.set(k, v);
437
+ }
438
+
439
+ const nextQuery = search.toString();
440
+ return nextQuery ? `${base}?${nextQuery}${hash}` : `${base}${hash}`;
441
+ }
442
+
443
+ function getEsmCdnBaseUrl(): string {
444
+ const g: any = globalThis as any;
445
+ // 浏览器环境:globalThis === window
446
+ if (typeof g?.__esm_cdn_base_url__ === 'string') return g.__esm_cdn_base_url__;
447
+ // 非浏览器/测试环境:读取挂载在 window 上的配置(如果存在)
448
+ const w: any = typeof window !== 'undefined' ? (window as any) : g?.window;
449
+ if (typeof w?.__esm_cdn_base_url__ === 'string') return w.__esm_cdn_base_url__;
450
+ return 'https://esm.sh';
451
+ }
452
+
453
+ function normalizeModule(mod: any) {
454
+ // 许多经由 esm.sh / esbuild 转换的模块会将主导出挂在 default 上
455
+ // 如果只有 default 一个导出,则直接返回 default,提升易用性
456
+ if (mod && typeof mod === 'object' && 'default' in mod) {
457
+ const keys = Object.keys(mod);
458
+ if (keys.length === 1 && keys[0] === 'default') {
459
+ return (mod as any).default;
460
+ }
461
+ }
462
+ return mod;
463
+ }
464
+
465
+ /**
466
+ * RunJS 专用:按“用户 specifier”导入模块,并在导入 core UI libs 时自动覆盖 ctx/libs。
467
+ *
468
+ * 说明:
469
+ * - 这里负责:specifier rewrite(如 esm.sh antd bundle)、URL 解析、全局 import cache、以及 override。
470
+ * - 具体的 dynamic import 由 `options.importer` 执行(默认使用 `runjsImportAsync`)。
471
+ * 这样在测试中可注入 mock importer,避免真实网络导入。
472
+ */
473
+ export async function runjsImportModule(
474
+ ctx: any,
475
+ specifier: string,
476
+ options?: { importer?: (url: string) => Promise<any>; addSuffix?: boolean },
477
+ ): Promise<any> {
478
+ if (!specifier || typeof specifier !== 'string') {
479
+ throw new Error('invalid url');
480
+ }
481
+
482
+ // Special handling for some packages on esm.sh like cdn:
483
+ // - antd may import CJS deps (e.g. @ant-design/colors) via named exports that are not generated by default.
484
+ // Use `bundle` to inline deps and avoid missing named exports.
485
+ // - When external React is enabled, also pin deps to the same React/ReactDOM version to avoid multiple instances.
486
+ let finalSpecifier = specifier;
487
+ const parsedForRewrite = parsePackageSpecifier(specifier);
488
+ if (parsedForRewrite?.name === 'antd' && !parsedForRewrite.subpath) {
489
+ const baseUrl = getEsmCdnBaseUrl();
490
+ if (typeof baseUrl === 'string') {
491
+ const params: Record<string, string> = { bundle: '1' };
492
+ const reactInfo = (ctx as any)?.__runjsExternalReact;
493
+ const reactVersion = reactInfo?.version;
494
+ if (reactVersion && typeof reactVersion === 'string') {
495
+ params.deps = `react@${reactVersion},react-dom@${reactVersion}`;
496
+ }
497
+ finalSpecifier = appendQueryParams(finalSpecifier, params);
498
+ }
499
+ }
500
+
501
+ const resolvedUrl = resolveModuleUrl(finalSpecifier, { addSuffix: options?.addSuffix ?? true });
502
+ const importer = options?.importer || runjsImportAsync;
503
+
504
+ const g = globalThis as any;
505
+ g.__nocobaseImportAsyncCache = g.__nocobaseImportAsyncCache || new Map<string, Promise<any>>();
506
+ const cache: Map<string, Promise<any>> = g.__nocobaseImportAsyncCache;
507
+
508
+ const loadTask =
509
+ cache.get(resolvedUrl) ||
510
+ (async () => {
511
+ const mod = await importer(resolvedUrl);
512
+ return normalizeModule(mod);
513
+ })();
514
+ cache.set(resolvedUrl, loadTask);
515
+
516
+ const mod = await loadTask;
517
+
518
+ // best-effort override:仅在 RunJS ctx(存在 libs)时生效,避免影响非 RunJS 使用场景。
519
+ const libs = (ctx as any)?.libs;
520
+ if (!libs || typeof libs !== 'object') return mod;
521
+
522
+ const parsed = parsePackageSpecifier(finalSpecifier);
523
+ if (!parsed) return mod;
524
+
525
+ const subpath = parsed.subpath || '';
526
+ if (parsed.name === 'react' && !subpath) {
527
+ setRunJSLibOverride(ctx as any, 'React', mod);
528
+ (ctx as any).__runjsExternalReact = { specifier, version: parsed.version, url: resolvedUrl };
529
+
530
+ const domSpecifier = parsed.version ? `react-dom@${parsed.version}/client` : 'react-dom/client';
531
+ try {
532
+ await (ctx as any).importAsync(domSpecifier);
533
+ } catch (err: any) {
534
+ const e: any = new Error(
535
+ `External React has been loaded via ctx.importAsync("${specifier}"), but auto-loading "${domSpecifier}" failed.` +
536
+ `\nPlease run: await ctx.importAsync("${domSpecifier}") to ensure ctx.render uses a matching ReactDOM.`,
537
+ );
538
+ e.cause = err;
539
+ throw e;
540
+ }
541
+ } else if (parsed.name === 'react-dom' && (subpath === 'client' || subpath.startsWith('client/'))) {
542
+ setRunJSLibOverride(ctx as any, 'ReactDOM', mod);
543
+ (ctx as any).__runjsExternalReactDOM = { specifier, version: parsed.version, url: resolvedUrl };
544
+ } else if (parsed.name === 'antd' && !subpath) {
545
+ setRunJSLibOverride(ctx as any, 'antd', mod);
546
+ (ctx as any).__runjsExternalAntd = { specifier, version: parsed.version, url: resolvedUrl };
547
+ } else if (parsed.name === '@ant-design/icons' && !subpath) {
548
+ setRunJSLibOverride(ctx as any, 'antdIcons', mod, { topLevelKey: false });
549
+ (ctx as any).__runjsExternalAntdIcons = { specifier, version: parsed.version, url: resolvedUrl };
550
+ }
551
+
552
+ return mod;
553
+ }