diff --git a/packages/plugin-sample-preview/src/index.tsx b/packages/plugin-sample-preview/src/index.tsx index 27cb93fcc..e5a4609e3 100644 --- a/packages/plugin-sample-preview/src/index.tsx +++ b/packages/plugin-sample-preview/src/index.tsx @@ -2,7 +2,7 @@ import React, { useState, ComponentType } from 'react'; import { Button, Dialog } from '@alifd/next'; import { PluginProps, NpmInfo } from '@ali/lowcode-types'; import { Designer } from '@ali/lowcode-designer'; -import { buildComponents } from '@ali/lowcode-utils'; +import { buildComponents, assetBundle, AssetList, AssetLevel, AssetLoader } from '@ali/lowcode-utils'; import ReactRenderer from '@ali/lowcode-react-renderer'; import './index.scss'; @@ -16,15 +16,34 @@ const SamplePreview = ({ editor }: PluginProps) => { const designer = editor.get(Designer); if (designer) { const assets = await editor.get('assets'); + const { packages } = assets; + const { componentsMap, schema } = designer; + console.info('save schema:', designer, assets); const libraryMap = {}; - assets.packages.forEach(({ package, library }) => { + const libraryAsset: AssetList = []; + packages.forEach(({ package, library, urls }) => { libraryMap[package] = library; + if (urls) { + libraryAsset.push(urls); + } }); + + const vendors = [ + assetBundle(libraryAsset, AssetLevel.Library), + ]; + console.log('libraryMap&vendors', libraryMap, vendors); + + // TODO asset may cause pollution + const assetLoader = new AssetLoader(); + assetLoader.load(libraryAsset); + const components = buildComponents(libraryMap, componentsMap); + console.log('components', components); + setData({ - schema: designer.schema.componentsTree[0], - components: buildComponents(libraryMap, designer.componentsMap), + schema: schema.componentsTree[0], + components, }); setVisible(true); } diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index 61edf182c..836aa5e29 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -5,15 +5,16 @@ import SimulatorRendererView from './renderer-view'; import { computed, obx } from '@recore/obx'; import { Asset } from '@ali/lowcode-utils'; import { getClientRects } from './utils/get-client-rects'; -import loader from './utils/loader'; import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes'; -import { isElement, cursor, setNativeSelection, buildComponents, getSubComponent } from '@ali/lowcode-utils'; +import { isElement, cursor, setNativeSelection, buildComponents, getSubComponent, AssetLoader } from '@ali/lowcode-utils'; import { RootSchema, ComponentSchema, TransformStage } from '@ali/lowcode-types'; // just use types import { BuiltinSimulatorRenderer, NodeInstance, Component } from '@ali/lowcode-designer'; import Slot from './builtin-components/slot'; import Leaf from './builtin-components/leaf'; +const loader = new AssetLoader(); + export class SimulatorRenderer implements BuiltinSimulatorRenderer { readonly isSimulatorRenderer = true; private dispose?: () => void; diff --git a/packages/react-simulator-renderer/src/utils/loader.ts b/packages/react-simulator-renderer/src/utils/loader.ts deleted file mode 100644 index 3bba55ba1..000000000 --- a/packages/react-simulator-renderer/src/utils/loader.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { load, evaluate } from './script'; -import StylePoint from './style'; -import { - Asset, - AssetLevel, - AssetLevels, - AssetType, - AssetList, - isAssetBundle, - isAssetItem, - assetItem, - AssetItem, - isCSSUrl, -} from '@ali/lowcode-utils'; - -function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) { - for (const asset of assets) { - parseAsset(scripts, styles, asset, level); - } -} - -function parseAsset(scripts: any, styles: any, asset: Asset | undefined | null, level?: AssetLevel) { - if (!asset) { - return; - } - if (Array.isArray(asset)) { - return parseAssetList(scripts, styles, asset, level); - } - - if (isAssetBundle(asset)) { - if (asset.assets) { - if (Array.isArray(asset.assets)) { - parseAssetList(scripts, styles, asset.assets, asset.level || level); - } else { - parseAsset(scripts, styles, asset.assets, asset.level || level); - } - return; - } - return; - } - - if (!isAssetItem(asset)) { - asset = assetItem(isCSSUrl(asset) ? AssetType.CSSUrl : AssetType.JSUrl, asset, level)!; - } - - let lv = asset.level || level; - - if (!lv || AssetLevel[lv] == null) { - lv = AssetLevel.App; - } - - asset.level = lv; - if (asset.type === AssetType.CSSUrl || asset.type == AssetType.CSSText) { - styles[lv].push(asset); - } else { - scripts[lv].push(asset); - } -} - -export class AssetLoader { - async load(asset: Asset) { - const styles: any = {}; - const scripts: any = {}; - AssetLevels.forEach(lv => { - styles[lv] = []; - scripts[lv] = []; - }); - parseAsset(scripts, styles, asset); - const styleQueue: AssetItem[] = styles[AssetLevel.Environment].concat( - styles[AssetLevel.Library], - styles[AssetLevel.Theme], - styles[AssetLevel.Runtime], - styles[AssetLevel.App], - ); - const scriptQueue: AssetItem[] = scripts[AssetLevel.Environment].concat( - scripts[AssetLevel.Library], - scripts[AssetLevel.Theme], - scripts[AssetLevel.Runtime], - scripts[AssetLevel.App], - ); - await Promise.all( - styleQueue.map(({ content, level, type, id }) => this.loadStyle(content, level!, type === AssetType.CSSUrl, id)), - ); - await Promise.all(scriptQueue.map(({ content, type }) => this.loadScript(content, type === AssetType.JSUrl))); - } - - private stylePoints = new Map(); - private loadStyle(content: string | undefined | null, level: AssetLevel, isUrl?: boolean, id?: string) { - if (!content) { - return; - } - let point: StylePoint | undefined; - if (id) { - point = this.stylePoints.get(id); - if (!point) { - point = new StylePoint(level, id); - this.stylePoints.set(id, point); - } - } else { - point = new StylePoint(level); - } - return isUrl ? point.applyUrl(content) : point.applyText(content); - } - - private loadScript(content: string | undefined | null, isUrl?: boolean) { - if (!content) { - return; - } - return isUrl ? load(content) : evaluate(content); - } -} - -export default new AssetLoader(); diff --git a/packages/react-simulator-renderer/src/utils/style.ts b/packages/react-simulator-renderer/src/utils/style.ts deleted file mode 100644 index 74f4eddd4..000000000 --- a/packages/react-simulator-renderer/src/utils/style.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { createDefer } from './create-defer'; - -export default class StylePoint { - private lastContent: string | undefined; - private lastUrl: string | undefined; - private placeholder: Element | Text; - - constructor(readonly level: number, readonly id?: string) { - let placeholder: any; - if (id) { - placeholder = document.head.querySelector(`style[data-id="${id}"]`); - } - if (!placeholder) { - placeholder = document.createTextNode(''); - const meta = document.head.querySelector(`meta[level="${level}"]`); - if (meta) { - document.head.insertBefore(placeholder, meta); - } else { - document.head.appendChild(placeholder); - } - } - this.placeholder = placeholder; - } - - applyText(content: string) { - if (this.lastContent === content) { - return; - } - this.lastContent = content; - this.lastUrl = undefined; - const element = document.createElement('style'); - element.setAttribute('type', 'text/css'); - if (this.id) { - element.setAttribute('data-id', this.id); - } - element.appendChild(document.createTextNode(content)); - document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null); - document.head.removeChild(this.placeholder); - this.placeholder = element; - } - - applyUrl(url: string) { - if (this.lastUrl === url) { - return; - } - this.lastContent = undefined; - this.lastUrl = url; - const element = document.createElement('link'); - element.onload = onload; - element.onerror = onload; - - const i = createDefer(); - function onload(e: any) { - element.onload = null; - element.onerror = null; - if (e.type === 'load') { - i.resolve(); - } else { - i.reject(); - } - } - - element.href = url; - element.rel = 'stylesheet'; - if (this.id) { - element.setAttribute('data-id', this.id); - } - document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null); - document.head.removeChild(this.placeholder); - this.placeholder = element; - return i.promise(); - } -} diff --git a/packages/utils/src/asset.ts b/packages/utils/src/asset.ts index 79ab83531..f6f842160 100644 --- a/packages/utils/src/asset.ts +++ b/packages/utils/src/asset.ts @@ -1,3 +1,7 @@ +import { isCSSUrl } from './is-css-url'; +import { createDefer } from './create-defer'; +import { load, evaluate } from './script'; + export interface AssetItem { type: AssetType; content?: string | null; @@ -88,3 +92,172 @@ export function assetItem(type: AssetType, content?: string | null, level?: Asse id, }; } + +export class StylePoint { + private lastContent: string | undefined; + private lastUrl: string | undefined; + private placeholder: Element | Text; + + constructor(readonly level: number, readonly id?: string) { + let placeholder: any; + if (id) { + placeholder = document.head.querySelector(`style[data-id="${id}"]`); + } + if (!placeholder) { + placeholder = document.createTextNode(''); + const meta = document.head.querySelector(`meta[level="${level}"]`); + if (meta) { + document.head.insertBefore(placeholder, meta); + } else { + document.head.appendChild(placeholder); + } + } + this.placeholder = placeholder; + } + + applyText(content: string) { + if (this.lastContent === content) { + return; + } + this.lastContent = content; + this.lastUrl = undefined; + const element = document.createElement('style'); + element.setAttribute('type', 'text/css'); + if (this.id) { + element.setAttribute('data-id', this.id); + } + element.appendChild(document.createTextNode(content)); + document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null); + document.head.removeChild(this.placeholder); + this.placeholder = element; + } + + applyUrl(url: string) { + if (this.lastUrl === url) { + return; + } + this.lastContent = undefined; + this.lastUrl = url; + const element = document.createElement('link'); + element.onload = onload; + element.onerror = onload; + + const i = createDefer(); + function onload(e: any) { + element.onload = null; + element.onerror = null; + if (e.type === 'load') { + i.resolve(); + } else { + i.reject(); + } + } + + element.href = url; + element.rel = 'stylesheet'; + if (this.id) { + element.setAttribute('data-id', this.id); + } + document.head.insertBefore(element, this.placeholder.parentNode === document.head ? this.placeholder.nextSibling : null); + document.head.removeChild(this.placeholder); + this.placeholder = element; + return i.promise(); + } +} + +function parseAssetList(scripts: any, styles: any, assets: AssetList, level?: AssetLevel) { + for (const asset of assets) { + parseAsset(scripts, styles, asset, level); + } +} + +function parseAsset(scripts: any, styles: any, asset: Asset | undefined | null, level?: AssetLevel) { + if (!asset) { + return; + } + if (Array.isArray(asset)) { + return parseAssetList(scripts, styles, asset, level); + } + + if (isAssetBundle(asset)) { + if (asset.assets) { + if (Array.isArray(asset.assets)) { + parseAssetList(scripts, styles, asset.assets, asset.level || level); + } else { + parseAsset(scripts, styles, asset.assets, asset.level || level); + } + return; + } + return; + } + + if (!isAssetItem(asset)) { + asset = assetItem(isCSSUrl(asset) ? AssetType.CSSUrl : AssetType.JSUrl, asset, level)!; + } + + let lv = asset.level || level; + + if (!lv || AssetLevel[lv] == null) { + lv = AssetLevel.App; + } + + asset.level = lv; + if (asset.type === AssetType.CSSUrl || asset.type == AssetType.CSSText) { + styles[lv].push(asset); + } else { + scripts[lv].push(asset); + } +} + +export class AssetLoader { + async load(asset: Asset) { + const styles: any = {}; + const scripts: any = {}; + AssetLevels.forEach(lv => { + styles[lv] = []; + scripts[lv] = []; + }); + parseAsset(scripts, styles, asset); + const styleQueue: AssetItem[] = styles[AssetLevel.Environment].concat( + styles[AssetLevel.Library], + styles[AssetLevel.Theme], + styles[AssetLevel.Runtime], + styles[AssetLevel.App], + ); + const scriptQueue: AssetItem[] = scripts[AssetLevel.Environment].concat( + scripts[AssetLevel.Library], + scripts[AssetLevel.Theme], + scripts[AssetLevel.Runtime], + scripts[AssetLevel.App], + ); + await Promise.all( + styleQueue.map(({ content, level, type, id }) => this.loadStyle(content, level!, type === AssetType.CSSUrl, id)), + ); + await Promise.all(scriptQueue.map(({ content, type }) => this.loadScript(content, type === AssetType.JSUrl))); + } + + private stylePoints = new Map(); + private loadStyle(content: string | undefined | null, level: AssetLevel, isUrl?: boolean, id?: string) { + if (!content) { + return; + } + let point: StylePoint | undefined; + if (id) { + point = this.stylePoints.get(id); + if (!point) { + point = new StylePoint(level, id); + this.stylePoints.set(id, point); + } + } else { + point = new StylePoint(level); + } + return isUrl ? point.applyUrl(content) : point.applyText(content); + } + + private loadScript(content: string | undefined | null, isUrl?: boolean) { + if (!content) { + return; + } + return isUrl ? load(content) : evaluate(content); + } +} diff --git a/packages/react-simulator-renderer/src/utils/create-defer.ts b/packages/utils/src/create-defer.ts similarity index 100% rename from packages/react-simulator-renderer/src/utils/create-defer.ts rename to packages/utils/src/create-defer.ts diff --git a/packages/react-simulator-renderer/src/utils/script.ts b/packages/utils/src/script.ts similarity index 100% rename from packages/react-simulator-renderer/src/utils/script.ts rename to packages/utils/src/script.ts