diff --git a/package.json b/package.json index 3d3a7b816..f471dd0ce 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "private": true, "type": "module", "scripts": { - "playground": "pnpm --filter playground dev", "build": "node ./scripts/build.js", "test": "vitest", "clean": "rimraf ./packages/*/dist", @@ -35,6 +34,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "globals": "^15.0.0", "husky": "^9.0.11", + "jsdom": "^24.1.0", "lint-staged": "^15.2.2", "prettier": "^3.2.5", "rimraf": "^5.0.2", diff --git a/packages/react-renderer/src/components/route.tsx b/packages/react-renderer/src/components/route.tsx index c2c132cca..246f70669 100644 --- a/packages/react-renderer/src/components/route.tsx +++ b/packages/react-renderer/src/components/route.tsx @@ -26,6 +26,11 @@ function RouteOutlet({ pageConfig }: OutletProps) { const context = useRenderContext(); const { schema, packageManager } = context; const { type = 'lowCode', mappingId } = pageConfig; + console.log( + '%c [ pageConfig ]-29', + 'font-size:13px; background:pink; color:#bf2c9f;', + pageConfig, + ); if (type === 'lowCode') { // 在页面渲染时重新获取 componentsMap diff --git a/packages/react-renderer/src/components/routerView.tsx b/packages/react-renderer/src/components/routerView.tsx index 4aaa90317..bb76722f7 100644 --- a/packages/react-renderer/src/components/routerView.tsx +++ b/packages/react-renderer/src/components/routerView.tsx @@ -13,12 +13,12 @@ export const createRouterProvider = (router: Router) => { return () => remove(); }, []); - const pageSchema = useMemo(() => { + const pageConfig = useMemo(() => { const pages = schema.get('pages') ?? []; const matched = location.matched[location.matched.length - 1]; if (matched) { - const page = pages.find((item) => matched.page === item.id); + const page = pages.find((item) => matched.page === item.mappingId); return page; } @@ -28,7 +28,7 @@ export const createRouterProvider = (router: Router) => { return ( - {children} + {children} ); diff --git a/packages/react-renderer/src/runtime/dataSource.ts b/packages/react-renderer/src/runtime/dataSource.ts index eadc5050f..daa27c093 100644 --- a/packages/react-renderer/src/runtime/dataSource.ts +++ b/packages/react-renderer/src/runtime/dataSource.ts @@ -1 +1,5 @@ -export const dataSourceCreator = () => ({}) as any; +export const dataSourceCreator = () => + ({ + dataSourceMap: {}, + reloadDataSource: () => {}, + }) as any; diff --git a/packages/react-renderer/src/runtime/index.tsx b/packages/react-renderer/src/runtime/index.tsx index 866472b7d..26d756f87 100644 --- a/packages/react-renderer/src/runtime/index.tsx +++ b/packages/react-renderer/src/runtime/index.tsx @@ -1,15 +1,14 @@ import { processValue, someValue } from '@alilc/lowcode-renderer-core'; import { - watch, isJSExpression, isJSFunction, isJSSlot, invariant, - isLowCodeComponentSchema, + isLowCodeComponentPackage, isJSI18nNode, } from '@alilc/lowcode-shared'; import { forwardRef, useRef, useEffect, createElement, memo } from 'react'; -import { appendExternalStyle } from '../utils/element'; +import { appendExternalStyle } from '../../../../playground/renderer/src/plugin/remote/element'; import { reactive } from '../utils/reactive'; import { useRenderContext } from '../context/render'; import { reactiveStateCreator } from './reactiveState'; @@ -63,7 +62,7 @@ export function getComponentByName( invariant(result, `${name} component not found in componentsRecord`); - if (isLowCodeComponentSchema(result)) { + if (isLowCodeComponentPackage(result)) { const { componentsMap, componentsTree, utils, i18n } = result.schema; if (componentsMap.length > 0) { @@ -120,6 +119,7 @@ export function createComponentBySchema( } const model = modelRef.current; + console.log('%c [ model ]-123', 'font-size:13px; background:pink; color:#bf2c9f;', model); const isConstructed = useRef(false); const isMounted = useRef(false); @@ -133,7 +133,7 @@ export function createComponentBySchema( const scopeValue = model.codeScope.value; // init dataSource - scopeValue.reloadDataSource(); + scopeValue.reloadDataSource?.(); let styleEl: HTMLElement | undefined; const cssText = model.getCssText(); @@ -148,11 +148,11 @@ export function createComponentBySchema( model.triggerLifeCycle('componentDidMount'); // 当 state 改变之后调用 - const unwatch = watch(scopeValue.state, (_, oldVal) => { - if (isMounted.current) { - model.triggerLifeCycle('componentDidUpdate', props, oldVal); - } - }); + // const unwatch = watch(scopeValue.state, (_, oldVal) => { + // if (isMounted.current) { + // model.triggerLifeCycle('componentDidUpdate', props, oldVal); + // } + // }); isMounted.current = true; @@ -160,7 +160,7 @@ export function createComponentBySchema( // componentWillUnmount?.(); model.triggerLifeCycle('componentWillUnmount'); styleEl?.parentNode?.removeChild(styleEl); - unwatch(); + // unwatch(); isMounted.current = false; }; }, []); @@ -247,7 +247,7 @@ function createElementByWidget( }; // 先将 jsslot, jsFunction 对象转换 - const finalProps = processValue( + let finalProps = processValue( componentProps, (node) => isJSFunction(node) || isJSSlot(node), (node: Spec.JSSlot | Spec.JSFunction) => { @@ -288,6 +288,17 @@ function createElementByWidget( }, ); + finalProps = processValue( + finalProps, + (value) => { + return value.type === 'JSSlot' && !value.value; + }, + (node) => { + console.log('%c [ node ]-303', 'font-size:13px; background:pink; color:#bf2c9f;', node); + return null; + }, + ); + const childElements = children?.map((child) => createElementByWidget(child, codeScope, renderContext, componentRefAttached), ); diff --git a/packages/react-renderer/src/utils/element.ts b/packages/react-renderer/src/utils/element.ts deleted file mode 100644 index 1ac0942d6..000000000 --- a/packages/react-renderer/src/utils/element.ts +++ /dev/null @@ -1,158 +0,0 @@ -const addLeadingSlash = (path: string): string => { - return path.charAt(0) === '/' ? path : `/${path}`; -}; - -export function getElementById(id: string, tag: string = 'div') { - let el = document.getElementById(id); - if (!el) { - el = document.createElement(tag); - el.id = id; - } - - return el; -} - -const enum AssetType { - STYLE = 'style', - SCRIPT = 'script', - UNKONWN = 'unkonwn', -} - -function getAssetTypeByUrl(url: string): AssetType { - const IS_CSS_REGEX = /\.css(\?((?!\.js$).)+)?$/; - const IS_JS_REGEX = /\.[t|j]sx?(\?((?!\.css$).)+)?$/; - const IS_JSON_REGEX = /\.json$/; - - if (IS_CSS_REGEX.test(url)) { - return AssetType.STYLE; - } else if (IS_JS_REGEX.test(url) || IS_JSON_REGEX.test(url)) { - return AssetType.SCRIPT; - } - - return AssetType.UNKONWN; -} - -export async function loadPackageUrls(urls: string[]) { - const styles: string[] = []; - const scripts: string[] = []; - - for (const url of urls) { - const type = getAssetTypeByUrl(url); - - if (type === AssetType.SCRIPT) { - scripts.push(url); - } else if (type === AssetType.STYLE) { - styles.push(url); - } - } - - await Promise.all(styles.map((item) => appendExternalCss(item))); - await Promise.all(scripts.map((item) => appendExternalScript(item))); -} - -async function appendExternalScript( - url: string, - root: HTMLElement = document.body, -): Promise { - if (url) { - const el = getIfExistAssetByUrl(url, 'script'); - if (el) return el; - } - - return new Promise((resolve, reject) => { - const scriptElement = document.createElement('script'); - - // scriptElement.type = moduleType === 'module' ? 'module' : 'text/javascript'; - /** - * `async=false` is required to make sure all js resources execute sequentially. - */ - scriptElement.async = false; - scriptElement.crossOrigin = 'anonymous'; - scriptElement.src = url; - - scriptElement.addEventListener( - 'load', - () => { - resolve(scriptElement); - }, - false, - ); - scriptElement.addEventListener('error', (error) => { - if (root.contains(scriptElement)) { - root.removeChild(scriptElement); - } - reject(error); - }); - - root.appendChild(scriptElement); - }); -} - -async function appendExternalCss( - url: string, - root: HTMLElement = document.head, -): Promise { - if (url) { - const el = getIfExistAssetByUrl(url, 'link'); - if (el) return el; - } - - return new Promise((resolve, reject) => { - const el: HTMLLinkElement = document.createElement('link'); - el.rel = 'stylesheet'; - el.href = url; - - el.addEventListener( - 'load', - () => { - resolve(el); - }, - false, - ); - el.addEventListener('error', (error) => { - reject(error); - }); - - root.appendChild(el); - }); -} - -export async function appendExternalStyle( - cssText: string, - root: HTMLElement = document.head, -): Promise { - return new Promise((resolve, reject) => { - const el: HTMLStyleElement = document.createElement('style'); - el.innerText = cssText; - - el.addEventListener( - 'load', - () => { - resolve(el); - }, - false, - ); - el.addEventListener('error', (error) => { - reject(error); - }); - - root.appendChild(el); - }); -} - -function getIfExistAssetByUrl( - url: string, - tag: 'link' | 'script', -): HTMLLinkElement | HTMLScriptElement | undefined { - return Array.from(document.getElementsByTagName(tag)).find((item) => { - const elUrl = (item as HTMLLinkElement).href || (item as HTMLScriptElement).src; - - if (/^(https?:)?\/\/([\w.]+\/?)\S*/gi.test(url)) { - // if url === http://xxx.xxx - return url === elUrl; - } else { - // if url === /xx/xx/xx.xx - return `${location.origin}${addLeadingSlash(url)}` === elUrl; - } - }); -} diff --git a/packages/react-renderer/src/utils/node.ts b/packages/react-renderer/src/utils/node.ts index 554374c22..e455e8bdc 100644 --- a/packages/react-renderer/src/utils/node.ts +++ b/packages/react-renderer/src/utils/node.ts @@ -10,5 +10,6 @@ export function normalizeComponentNode(node: Spec.ComponentNode): NormalizedComp ...node, loopArgs: node.loopArgs ?? ['item', 'index'], props: node.props ?? {}, + condition: node.condition || node.condition === false ? node.condition : true, }; } diff --git a/packages/react-renderer/tsconfig.json b/packages/react-renderer/tsconfig.json index 039e0b4d1..946b55298 100644 --- a/packages/react-renderer/tsconfig.json +++ b/packages/react-renderer/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "dist" }, - "include": ["src"] + "include": ["src", "../../playground/renderer/src/plugin/remote/element.ts"] } diff --git a/packages/renderer-core/__tests__/boosts.spec.ts b/packages/renderer-core/__tests__/boosts.spec.ts deleted file mode 100644 index 3486eb29e..000000000 --- a/packages/renderer-core/__tests__/boosts.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { appBoosts } from '../src/boosts'; - -describe('appBoosts', () => { - it('should add value successfully', () => { - appBoosts.add('test', 1); - expect(appBoosts.value.test).toBe(1); - }); - - it('should clear removed value', () => { - appBoosts.add('test', 1); - expect(appBoosts.value.test).toBe(1); - - appBoosts.remove('test'); - expect(appBoosts.value.test).toBeUndefined(); - }); -}); diff --git a/packages/renderer-core/__tests__/code-runtime.spec.ts b/packages/renderer-core/__tests__/code-runtime.spec.ts deleted file mode 100644 index 81470aec3..000000000 --- a/packages/renderer-core/__tests__/code-runtime.spec.ts +++ /dev/null @@ -1 +0,0 @@ -import {} from 'vitest'; diff --git a/packages/renderer-core/__tests__/package.spec.ts b/packages/renderer-core/__tests__/package.spec.ts deleted file mode 100644 index e130a9d59..000000000 --- a/packages/renderer-core/__tests__/package.spec.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { expect } from 'vitest'; -import { createPackageManager } from '../src/package'; diff --git a/packages/renderer-core/__tests__/parts/code-runtime/codeScope.spec.ts b/packages/renderer-core/__tests__/parts/code-runtime/codeScope.spec.ts new file mode 100644 index 000000000..3c56eeda9 --- /dev/null +++ b/packages/renderer-core/__tests__/parts/code-runtime/codeScope.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { ICodeScope, CodeScope } from '../../../src/parts/code-runtime'; + +describe('codeScope', () => { + let scope: ICodeScope; + + beforeAll(() => { + scope = new CodeScope({}); + }); + + it('should inject a new value', () => { + scope.inject('username', 'Alice'); + expect(scope.value).toEqual({ username: 'Alice' }); + }); + + it('should not overwrite an existing value without force', () => { + scope.inject('username', 'Bob'); + expect(scope.value).toEqual({ username: 'Alice' }); + }); + + it('should overwrite an existing value with force', () => { + scope.inject('username', 'Bob', true); + expect(scope.value).toEqual({ username: 'Bob' }); + }); + + it('should set new value without replacing existing values', () => { + scope.setValue({ age: 25 }); + expect(scope.value).toEqual({ username: 'Bob', age: 25 }); + }); + + it('should set new value and replace all existing values', () => { + scope.setValue({ loggedIn: true }, true); + expect(scope.value).toEqual({ loggedIn: true }); + }); + + it('should create a child scope with initial values', () => { + const childScope = scope.createChild({ sessionId: 'abc123' }); + expect(childScope.value).toEqual({ loggedIn: true, sessionId: 'abc123' }); + }); + + it('should set new values in the child scope without affecting the parent scope', () => { + const childScope = scope.createChild({ theme: 'dark' }); + expect(childScope.value).toEqual({ loggedIn: true, sessionId: 'abc123', theme: 'dark' }); + expect(scope.value).toEqual({ loggedIn: true }); + }); +}); diff --git a/packages/renderer-core/__tests__/plugin.spec.ts b/packages/renderer-core/__tests__/plugin.spec.ts deleted file mode 100644 index 0af367ee7..000000000 --- a/packages/renderer-core/__tests__/plugin.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, expect, expectTypeOf } from 'vitest'; -import { definePlugin, type Plugin, createPluginManager } from '../src/plugin'; - -describe('createPluginManager', () => { - it('should install plugin successfully', () => {}); - - it('should install plugins when deps installed', () => {}); -}); - -describe('definePlugin', () => { - it('should return a plugin', () => {}); -}); diff --git a/packages/renderer-core/__tests__/utils/non-setter-proxy.spec.ts b/packages/renderer-core/__tests__/utils/non-setter-proxy.spec.ts deleted file mode 100644 index 3e6f871c4..000000000 --- a/packages/renderer-core/__tests__/utils/non-setter-proxy.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { nonSetterProxy } from '../../src/utils/non-setter-proxy'; - -describe('nonSetterProxy', () => { - it('should non setter on proxy', () => { - const target = { a: 1 }; - const proxy = nonSetterProxy(target); - - expect(() => ((proxy as any).b = 1)).toThrowError(/trap returned falsish for property 'b'/); - }); - - it('should correct value when getter', () => { - const target = { a: 1 }; - const proxy = nonSetterProxy(target); - - expect(proxy.a).toBe(1); - expect('a' in proxy).toBeTruthy(); - }); -}); diff --git a/packages/renderer-core/src/main.ts b/packages/renderer-core/src/main.ts index 426d602c5..541e61d21 100644 --- a/packages/renderer-core/src/main.ts +++ b/packages/renderer-core/src/main.ts @@ -32,6 +32,8 @@ export class RendererMain { // valid schema this.schemaService.initialize(schema); + this.codeRuntimeService.initialize(options); + // init intl const finalLocale = options.locale ?? navigator.language; const i18nTranslations = this.schemaService.get('i18n') ?? {}; diff --git a/packages/renderer-core/src/parts/code-runtime/codeRuntimeService.ts b/packages/renderer-core/src/parts/code-runtime/codeRuntimeService.ts index 1df8c2d68..300433492 100644 --- a/packages/renderer-core/src/parts/code-runtime/codeRuntimeService.ts +++ b/packages/renderer-core/src/parts/code-runtime/codeRuntimeService.ts @@ -11,25 +11,43 @@ import { import { type ICodeScope, CodeScope } from './codeScope'; import { processValue } from '../../utils/value'; +type BeforeResolveCb = ( + code: Spec.JSExpression | Spec.JSFunction, +) => Spec.JSExpression | Spec.JSFunction; + export interface ICodeRuntimeService { + initialize({ evalCodeFunction }: CodeRuntimeInitializeOptions): void; + getScope(): ICodeScope; run(code: string, scope?: ICodeScope): R | undefined; resolve(value: PlainObject, scope?: ICodeScope): any; - beforeRun(fn: (code: string) => string): EventDisposable; + beforeResolve(fn: BeforeResolveCb): EventDisposable; createChildScope(value: PlainObject): ICodeScope; } export const ICodeRuntimeService = createDecorator('codeRuntimeService'); +export type EvalCodeFunction = (code: string, scope: any) => any; + +export interface CodeRuntimeInitializeOptions { + evalCodeFunction?: EvalCodeFunction; +} + @Provide(ICodeRuntimeService) export class CodeRuntimeService implements ICodeRuntimeService { private codeScope: ICodeScope = new CodeScope({}); - private callbacks = createCallback<(code: string) => string>(); + private callbacks = createCallback(); + + private evalCodeFunction: EvalCodeFunction = evaluate; + + initialize({ evalCodeFunction }: CodeRuntimeInitializeOptions) { + if (evalCodeFunction) this.evalCodeFunction = evalCodeFunction; + } getScope() { return this.codeScope; @@ -39,13 +57,7 @@ export class CodeRuntimeService implements ICodeRuntimeService { if (!code) return undefined; try { - const cbs = this.callbacks.list(); - const finalCode = cbs.reduce((code, cb) => cb(code), code); - - let result = new Function( - 'scope', - `"use strict";return (function(){return (${finalCode})}).bind(scope)();`, - )(scope.value); + let result = this.evalCodeFunction(code, scope.value); if (typeof result === 'function') { result = result.bind(scope.value); @@ -66,17 +78,20 @@ export class CodeRuntimeService implements ICodeRuntimeService { return isJSExpression(data) || isJSFunction(data); }, (node: Spec.JSExpression | Spec.JSFunction) => { - const v = this.run(node.value, scope); + const cbs = this.callbacks.list(); + const finalNode = cbs.reduce((node, cb) => cb(node), node); - if (typeof v === 'undefined' && (node as any).mock) { - return (node as any).mock; + const v = this.run(finalNode.value, scope); + + if (typeof v === 'undefined' && finalNode.mock) { + return this.resolve(finalNode.mock, scope); } return v; }, ); } - beforeRun(fn: (code: string) => string): EventDisposable { + beforeResolve(fn: BeforeResolveCb): EventDisposable { return this.callbacks.add(fn); } @@ -84,3 +99,9 @@ export class CodeRuntimeService implements ICodeRuntimeService { return this.codeScope.createChild(value); } } + +function evaluate(code: string, scope: any) { + return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)( + scope, + ); +} diff --git a/packages/renderer-core/src/parts/code-runtime/codeScope.ts b/packages/renderer-core/src/parts/code-runtime/codeScope.ts index c08edeeeb..1e9a24c8c 100644 --- a/packages/renderer-core/src/parts/code-runtime/codeScope.ts +++ b/packages/renderer-core/src/parts/code-runtime/codeScope.ts @@ -38,11 +38,23 @@ export class CodeScope implements ICodeScope { if (Reflect.has(valueTarget.current, p)) { return Reflect.get(valueTarget.current, p, receiver); } - valueTarget = this.__node.prev; + valueTarget = valueTarget.prev; } return Reflect.get(target, p, receiver); }, + has: (target, p) => { + let valueTarget: IScopeNode | undefined = this.__node; + + while (valueTarget) { + if (Reflect.has(valueTarget.current, p)) { + return true; + } + valueTarget = valueTarget.prev; + } + + return Reflect.has(target, p); + }, }); } diff --git a/packages/renderer-core/src/parts/component-tree-model/treeModel.ts b/packages/renderer-core/src/parts/component-tree-model/treeModel.ts index 5590d1d70..853d52ca6 100644 --- a/packages/renderer-core/src/parts/component-tree-model/treeModel.ts +++ b/packages/renderer-core/src/parts/component-tree-model/treeModel.ts @@ -1,11 +1,4 @@ -import { - type Spec, - type PlainObject, - isJSFunction, - isComponentNode, - invariant, - type AnyFunction, -} from '@alilc/lowcode-shared'; +import { type Spec, type PlainObject, isComponentNode, invariant } from '@alilc/lowcode-shared'; import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime'; import { IWidget, Widget } from '../widget'; @@ -118,8 +111,8 @@ export class ComponentTreeModel ); for (const [key, fn] of Object.entries(methods)) { - const customMethod = this.codeRuntime.run(fn.value, this.codeScope); - if (customMethod) { + const customMethod = this.codeRuntime.resolve(fn, this.codeScope); + if (typeof customMethod === 'function') { this.codeScope.inject(key, customMethod); } } @@ -140,11 +133,9 @@ export class ComponentTreeModel const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName]; - if (isJSFunction(lifeCycleSchema)) { - const lifeCycleFn = this.codeRuntime.run(lifeCycleSchema.value, this.codeScope); - if (lifeCycleFn) { - lifeCycleFn.apply(this.codeScope.value, args); - } + const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema, this.codeScope); + if (typeof lifeCycleFn === 'function') { + lifeCycleFn.apply(this.codeScope.value, args); } } diff --git a/packages/renderer-core/src/parts/package/managementService.ts b/packages/renderer-core/src/parts/package/managementService.ts index 7d815d7d0..6b59f0938 100644 --- a/packages/renderer-core/src/parts/package/managementService.ts +++ b/packages/renderer-core/src/parts/package/managementService.ts @@ -1,6 +1,23 @@ -import { type Spec, type LowCodeComponent, createDecorator, Provide } from '@alilc/lowcode-shared'; +import { + type Spec, + type LowCodeComponent, + type ProCodeComponent, + createDecorator, + Provide, + isLowCodeComponentPackage, + isProCodeComponentPackage, +} from '@alilc/lowcode-shared'; +import { get as lodashGet } from 'lodash-es'; import { PackageLoader } from './loader'; +export interface NormalizedPackage { + id: string; + package: string; + library: string; + exportSource?: NormalizedPackage | undefined; + raw: ProCodeComponent; +} + export interface IPackageManagementService { /** * 新增资产包 @@ -39,36 +56,30 @@ export class PackageManagementService implements IPackageManagementService { private packageStore: Map = ((window as any).__PACKAGE_STORE__ ??= new Map()); - private packagesRef: Spec.Package[] = []; + private packagesMap: Map = new Map(); + + private lowCodeComponentPackages: Map = new Map(); private packageLoaders: PackageLoader[] = []; async loadPackages(packages: Spec.Package[]) { for (const item of packages) { - if (!item.package && !item.id) continue; - - const newId = item.package ?? item.id!; - const isExist = this.packagesRef.some((_) => { - const itemId = _.package ?? _.id; - return itemId === newId; - }); + // low code component not need load + if (isLowCodeComponentPackage(item)) { + this.lowCodeComponentPackages.set(item.id, item); + continue; + } - if (!isExist) { - this.packagesRef.push(item); + if (!isProCodeComponentPackage(item)) continue; - if (!this.packageStore.has(newId)) { - const loader = this.packageLoaders.find((loader) => loader.active(item)); - if (!loader) continue; + const normalized = this.normalizePackage(item); - const result = await loader.load.call(this, item); - if (result) this.packageStore.set(newId, result); - } - } + await this.loadPackageByNormalized(normalized); } } getPackageInfo(packageName: string) { - return this.packagesRef.find((p) => p.package === packageName); + return this.packagesMap.get(packageName)?.raw; } getLibraryByPackageName(packageName: string) { @@ -86,9 +97,7 @@ export class PackageManagementService implements IPackageManagementService { resolveComponentMaps(componentMaps: Spec.ComponentMap[]) { for (const map of componentMaps) { if (map.devMode === 'lowCode') { - const packageInfo = this.packagesRef.find((_) => { - return _.id === (map as LowCodeComponent).id; - }); + const packageInfo = this.lowCodeComponentPackages.get((map as LowCodeComponent).id); if (packageInfo) { this.componentsRecord[map.componentName] = packageInfo; @@ -123,7 +132,6 @@ export class PackageManagementService implements IPackageManagementService { getComponentsNameRecord(componentMaps?: Spec.ComponentMap[]) { if (componentMaps) { const newMaps = componentMaps.filter((item) => !this.componentsRecord[item.componentName]); - this.resolveComponentMaps(newMaps); } @@ -143,4 +151,72 @@ export class PackageManagementService implements IPackageManagementService { this.packageLoaders.push(loader); } } + + private normalizePackage(packageInfo: ProCodeComponent): NormalizedPackage { + if (this.packagesMap.has(packageInfo.package)) { + return this.packagesMap.get(packageInfo.package)!; + } + + const normalized: NormalizedPackage = { + package: packageInfo.package, + id: packageInfo.id ?? packageInfo.package, + library: packageInfo.library, + raw: packageInfo, + }; + + this.packagesMap.set(normalized.package, normalized); + + // add normalized to package exports dependency graph + const packagesRef = [...this.packagesMap.values()]; + + // set export source + if (packageInfo.exportSourceId || packageInfo.exportSourceLibrary) { + const found = packagesRef.find((item) => { + if (!packageInfo.exportSourceId) { + return item.library === packageInfo.exportSourceLibrary; + } else { + return item.package === packageInfo.exportSourceId; + } + }); + + if (found) { + normalized.exportSource = found; + } + } + + return normalized; + } + + private async loadPackageByNormalized(normalized: NormalizedPackage) { + if (this.packageStore.has(normalized.package)) return; + + // if it has export source package, wait export source package loaded + if (normalized.exportSource) { + if (this.packageStore.has(normalized.package)) { + const library = lodashGet(window, normalized.library); + if (library) { + this.packageStore.set(normalized.package, library); + } + } + } else { + const loader = this.packageLoaders.find((loader) => loader.active(normalized.raw)); + if (!loader) return; + + const result = await loader.load.call(this, normalized.raw); + if (result) { + this.packageStore.set(normalized.package, result); + } + } + + // if current package loaded, set the value of the dependency on this package + if (this.packageStore.has(normalized.package)) { + const chilren = [...this.packagesMap.values()].filter((item) => { + return item.exportSource?.package === normalized.package; + }); + + for (const child of chilren) { + await this.loadPackageByNormalized(child); + } + } + } } diff --git a/packages/renderer-core/src/parts/runtimeUtil.ts b/packages/renderer-core/src/parts/runtimeUtil.ts index 789680168..7785efb59 100644 --- a/packages/renderer-core/src/parts/runtimeUtil.ts +++ b/packages/renderer-core/src/parts/runtimeUtil.ts @@ -33,7 +33,7 @@ export class RuntimeUtilService implements IRuntimeUtilService { } } else { const fn = this.parseUtil(name); - this.utilsMap.set(name.name, fn); + if (fn) this.utilsMap.set(name.name, fn); } } diff --git a/packages/renderer-core/src/types.ts b/packages/renderer-core/src/types.ts index 95144eee5..d1f304f2f 100644 --- a/packages/renderer-core/src/types.ts +++ b/packages/renderer-core/src/types.ts @@ -3,6 +3,7 @@ import { type Plugin } from './parts/extension'; import { type ISchemaService } from './parts/schema'; import { type IPackageManagementService } from './parts/package'; import { type IExtensionHostService } from './parts/extension'; +import { type EvalCodeFunction } from './parts/code-runtime'; export interface AppOptions { schema: Spec.Project; @@ -18,6 +19,8 @@ export interface AppOptions { * 运行模式 */ mode?: 'development' | 'production'; + + evalCodeFunction?: EvalCodeFunction; } export type RendererApplication = { diff --git a/packages/shared/src/abilities/intl.ts b/packages/shared/src/abilities/intl.ts index 6362f62c3..27a205342 100644 --- a/packages/shared/src/abilities/intl.ts +++ b/packages/shared/src/abilities/intl.ts @@ -100,9 +100,10 @@ function nomarlizeLocale(target: Locale) { return navigatorLanguageMapping[target]; } - const replaced = target.replace('_', '-'); - const splited = replaced.split('-').slice(0, 2); - splited[1] = splited[1].toUpperCase(); + // const replaced = target.replace('_', '-'); + // const splited = replaced.split('-').slice(0, 2); + // splited[1] = splited[1].toUpperCase(); - return splited.join('-'); + // return splited.join('-'); + return target; } diff --git a/packages/shared/src/types/material.ts b/packages/shared/src/types/material.ts index d9b781f59..e4eba25e8 100644 --- a/packages/shared/src/types/material.ts +++ b/packages/shared/src/types/material.ts @@ -4,6 +4,7 @@ import { Project } from './specs/lowcode-spec'; export interface ProCodeComponent extends Package { package: string; type: 'proCode'; + library: string; } export interface LowCodeComponent extends Package { diff --git a/packages/shared/src/types/specs/asset-spec.ts b/packages/shared/src/types/specs/asset-spec.ts index 97e5ef691..28cb51e16 100644 --- a/packages/shared/src/types/specs/asset-spec.ts +++ b/packages/shared/src/types/specs/asset-spec.ts @@ -56,7 +56,7 @@ export interface Package { /** * 作为全局变量引用时的名称,和 webpack output.library 字段含义一样,用来定义全局变量名 */ - library: string; + library?: string | undefined; /** * 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容; */ diff --git a/packages/shared/src/types/specs/lowcode-spec.ts b/packages/shared/src/types/specs/lowcode-spec.ts index ce878371b..b24650677 100644 --- a/packages/shared/src/types/specs/lowcode-spec.ts +++ b/packages/shared/src/types/specs/lowcode-spec.ts @@ -423,6 +423,8 @@ export interface JSSlot { type: 'JSSlot'; value: ComponentNode | ComponentNode[]; params?: string[]; + + [key: string]: any; } /** @@ -431,6 +433,8 @@ export interface JSSlot { export interface JSFunction { type: 'JSFunction'; value: string; + + [key: string]: any; } /** @@ -439,6 +443,8 @@ export interface JSFunction { export interface JSExpression { type: 'JSExpression'; value: string; + + [key: string]: any; } /** @@ -456,4 +462,6 @@ export interface JSI18n { params?: Record; } +export type JSNode = JSSlot | JSExpression | JSExpression | JSI18n; + export type NodeType = string | JSExpression | JSI18n | ComponentNode; diff --git a/packages/shared/src/utils/type-guards/index.ts b/packages/shared/src/utils/type-guards/index.ts index 97bec9d72..aa58622ee 100644 --- a/packages/shared/src/utils/type-guards/index.ts +++ b/packages/shared/src/utils/type-guards/index.ts @@ -1 +1,2 @@ export * from './spec'; +export * from './material'; diff --git a/packages/shared/src/utils/type-guards/material.ts b/packages/shared/src/utils/type-guards/material.ts new file mode 100644 index 000000000..5e58b72c7 --- /dev/null +++ b/packages/shared/src/utils/type-guards/material.ts @@ -0,0 +1,10 @@ +import { LowCodeComponent, ProCodeComponent } from '../../types'; +import { isPlainObject } from 'lodash-es'; + +export function isLowCodeComponentPackage(v: unknown): v is LowCodeComponent { + return isPlainObject(v) && (v as any).type === 'lowCode' && (v as any).schema; +} + +export function isProCodeComponentPackage(v: unknown): v is ProCodeComponent { + return isPlainObject(v) && (v as any).package && (v as any).library; +} diff --git a/packages/shared/src/utils/type-guards/spec.ts b/packages/shared/src/utils/type-guards/spec.ts index 61b65a648..da4ca4089 100644 --- a/packages/shared/src/utils/type-guards/spec.ts +++ b/packages/shared/src/utils/type-guards/spec.ts @@ -1,4 +1,4 @@ -import type { Spec, LowCodeComponent } from '../../types'; +import type { Spec } from '../../types'; import { isPlainObject } from 'lodash-es'; export function isJSExpression(v: unknown): v is Spec.JSExpression { @@ -24,7 +24,3 @@ export function isJSI18nNode(v: unknown): v is Spec.JSI18n { export function isComponentNode(v: unknown): v is Spec.ComponentNode { return isPlainObject(v) && (v as any).componentName; } - -export function isLowCodeComponentSchema(v: unknown): v is LowCodeComponent { - return isPlainObject(v) && (v as any).type === 'lowCode' && (v as any).schema; -}