feat: 更新container版本,修改新的引入方式
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -4,3 +4,5 @@ build | |||||||
| .cache | .cache | ||||||
| .DS_Store | .DS_Store | ||||||
| *.log | *.log | ||||||
|  |  | ||||||
|  | .turbo | ||||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | //npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} | ||||||
|  | @abearxiong:registry=https://npm.pkg.github.com | ||||||
|  | //registry.npmjs.org/:_authToken=${NPM_TOKEN} | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@kevisual/container", |   "name": "@kevisual/container", | ||||||
|   "version": "0.0.3", |   "version": "1.0.0", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "main": "dist/container.js", |   "main": "dist/container.js", | ||||||
|   "publishConfig": { |   "publishConfig": { | ||||||
| @@ -37,6 +37,7 @@ | |||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@rollup/plugin-commonjs": "^28.0.0", |     "@rollup/plugin-commonjs": "^28.0.0", | ||||||
|     "@rollup/plugin-node-resolve": "^15.3.0", |     "@rollup/plugin-node-resolve": "^15.3.0", | ||||||
|  |     "@rollup/plugin-terser": "^0.4.4", | ||||||
|     "@rollup/plugin-typescript": "^12.1.0", |     "@rollup/plugin-typescript": "^12.1.0", | ||||||
|     "@types/crypto-js": "^4.2.2", |     "@types/crypto-js": "^4.2.2", | ||||||
|     "@types/react": "^18.3.4", |     "@types/react": "^18.3.4", | ||||||
| @@ -53,6 +54,7 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "nanoid": "^5.0.7", |     "nanoid": "^5.0.7", | ||||||
|     "rollup-plugin-dts": "^6.1.1", |     "rollup-plugin-dts": "^6.1.1", | ||||||
|  |     "scheduler": "^0.23.2", | ||||||
|     "zustand": "^4.5.5" |     "zustand": "^4.5.5" | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -5,6 +5,7 @@ import resolve from '@rollup/plugin-node-resolve'; | |||||||
| import commonjs from '@rollup/plugin-commonjs'; | import commonjs from '@rollup/plugin-commonjs'; | ||||||
| import copy from 'rollup-plugin-copy'; | import copy from 'rollup-plugin-copy'; | ||||||
| import { dts } from 'rollup-plugin-dts'; | import { dts } from 'rollup-plugin-dts'; | ||||||
|  | import terser from '@rollup/plugin-terser'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @type {import('rollup').RollupOptions} |  * @type {import('rollup').RollupOptions} | ||||||
| @@ -18,6 +19,15 @@ const config1 = { | |||||||
|   plugins: [ |   plugins: [ | ||||||
|     resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块 |     resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块 | ||||||
|     commonjs(), // |     commonjs(), // | ||||||
|  |     terser({ | ||||||
|  |       format: { | ||||||
|  |         comments: false, // 移除注释 | ||||||
|  |       }, | ||||||
|  |       compress: { | ||||||
|  |         drop_console: true, // 移除 console.log | ||||||
|  |         drop_debugger: true, // 移除 debugger | ||||||
|  |       }, | ||||||
|  |     }), | ||||||
|     typescript({ |     typescript({ | ||||||
|       allowImportingTsExtensions: true, |       allowImportingTsExtensions: true, | ||||||
|       noEmit: true, |       noEmit: true, | ||||||
| @@ -50,9 +60,9 @@ const config2 = { | |||||||
|       declaration: false, |       declaration: false, | ||||||
|     }), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 |     }), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 | ||||||
|     // 复制/src/container.css 到dist/container.css |     // 复制/src/container.css 到dist/container.css | ||||||
|     copy({ |     // copy({ | ||||||
|       targets: [{ src: 'src/container.css', dest: 'dist' }], |     //   targets: [{ src: 'src/container.css', dest: 'dist' }], | ||||||
|     }), |     // }), | ||||||
|   ], |   ], | ||||||
| }; | }; | ||||||
| const config2Dts = { | const config2Dts = { | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								src/code-url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/code-url.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | import { MD5 } from 'crypto-js'; | ||||||
|  |  | ||||||
|  | type CodeUrl = { | ||||||
|  |   url: string; // 地址 | ||||||
|  |   code?: string; // 代码 | ||||||
|  |   id?: string; // codeId | ||||||
|  |   hash?: string; | ||||||
|  | }; | ||||||
|  | export class CodeUrlManager { | ||||||
|  |   urls: CodeUrl[] = []; | ||||||
|  |   urlMap: { [key: string]: CodeUrl } = {}; | ||||||
|  |   constructor() { | ||||||
|  |     this.urls = []; | ||||||
|  |   } | ||||||
|  |   addCode(code: string, id: string, hash?: string) { | ||||||
|  |     const urlMap = this.urlMap; | ||||||
|  |     const resultContent = (content: CodeUrl) => { | ||||||
|  |       const index = this.urls.findIndex((item) => item.id === id); | ||||||
|  |       if (index === -1) { | ||||||
|  |         this.urls.push(content); | ||||||
|  |       } | ||||||
|  |       return content; | ||||||
|  |     }; | ||||||
|  |     if (urlMap[hash]) { | ||||||
|  |       // 可能id不同,但是hash相同。已经加载过了。 | ||||||
|  |       const content = { | ||||||
|  |         ...urlMap[hash], | ||||||
|  |         id, | ||||||
|  |       }; | ||||||
|  |       return resultContent(content); | ||||||
|  |     } | ||||||
|  |     const _hash = hash || MD5(code).toString(); | ||||||
|  |     if (urlMap[_hash]) { | ||||||
|  |       const content = { | ||||||
|  |         ...urlMap[_hash], | ||||||
|  |         id, | ||||||
|  |       }; | ||||||
|  |       return resultContent(content); | ||||||
|  |     } | ||||||
|  |     const index = this.urls.findIndex((item) => item.id === id); | ||||||
|  |     if (index > -1) { | ||||||
|  |       // 没有共同hash的值了,但是还是找了id,这个时候应该移除之前 | ||||||
|  |       const url = this.urls[index]; | ||||||
|  |       const list = this.urls.filter((item) => item.hash === url.hash); | ||||||
|  |       if (list.length <= 1) { | ||||||
|  |         // 这个hash的值没有人用了 | ||||||
|  |         URL.revokeObjectURL(url.url); | ||||||
|  |         this.urlMap[url.hash] = null; | ||||||
|  |       } | ||||||
|  |       this.urls.splice(index, 1); | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * 全新代码 | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' })); | ||||||
|  |     const content = { | ||||||
|  |       id, | ||||||
|  |       url, | ||||||
|  |       code, | ||||||
|  |       hash: _hash, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.urls.push(content); | ||||||
|  |     urlMap[_hash] = { ...content, id: null }; | ||||||
|  |     return content; | ||||||
|  |   } | ||||||
|  |   removeUrl(id?: string, url?: string) { | ||||||
|  |     const index = this.urls.findIndex((item) => item.url === url || item.id === id); | ||||||
|  |     if (index > -1) { | ||||||
|  |       const url = this.urls[index]; | ||||||
|  |       URL.revokeObjectURL(url.url); | ||||||
|  |       this.urlMap[url.hash] = null; | ||||||
|  |       this.urls.splice(index, 1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | export const codeUrlManager = new CodeUrlManager(); | ||||||
| @@ -1,8 +1,10 @@ | |||||||
| import { Container, ContainerOpts } from './container'; | import { Container, ContainerOpts, createEmotion } from './container'; | ||||||
| import { addListener } from './listener/dom'; | import { addListener } from './listener/dom'; | ||||||
|  |  | ||||||
| export type ContainerEditOpts = { | export type ContainerEditOpts = { | ||||||
|   edit?: boolean; |   edit?: boolean; | ||||||
|   mask?: boolean; |   mask?: boolean; | ||||||
|  |   initCss?: string; | ||||||
| } & ContainerOpts; | } & ContainerOpts; | ||||||
| let isListener = false; | let isListener = false; | ||||||
| export class ContainerEdit extends Container { | export class ContainerEdit extends Container { | ||||||
| @@ -28,9 +30,12 @@ export class ContainerEdit extends Container { | |||||||
|         addListener(this.root); |         addListener(this.root); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     if (opts.initCss && this.root) { | ||||||
|  |       this.root.classList.add(kvContainerStyle); | ||||||
|     } |     } | ||||||
|   renderChildren(cid: string, parentElement?: any, pid?: any) { |   } | ||||||
|     const el = super.renderChildren(cid, parentElement, pid); |   renderChildren(cid: string, parentElement?: any, pid?: any, isNew = true) { | ||||||
|  |     const el = super.renderChildren(cid, parentElement, pid, isNew); | ||||||
|  |  | ||||||
|     if (el) { |     if (el) { | ||||||
|       const computedStyle = window.getComputedStyle(el); |       const computedStyle = window.getComputedStyle(el); | ||||||
| @@ -72,3 +77,28 @@ export class ContainerEdit extends Container { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const css = `.kv-container.active { | ||||||
|  |   background: #000 ; | ||||||
|  |   border: 2px solid #195ca9; | ||||||
|  |   z-index: 100; | ||||||
|  | } | ||||||
|  | .kv-container.dragging { | ||||||
|  |   opacity: 0.5; /* 拖动时降低透明度 */ | ||||||
|  |   box-sizing: content-box; | ||||||
|  |   border: 2px dashed blue; | ||||||
|  | } | ||||||
|  | .kv-container.hover { | ||||||
|  |   cursor: move; | ||||||
|  | } | ||||||
|  | .kv-container > .resizer, .kv-container > .drag-title { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | .kv-container.active > .resizer, .kv-container.active > .drag-title { | ||||||
|  |   display: block; | ||||||
|  | } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export const emotion = createEmotion({ key: 'kv-container-style', speedy: true }); | ||||||
|  |  | ||||||
|  | const kvContainerStyle = emotion.css(css); | ||||||
|   | |||||||
							
								
								
									
										351
									
								
								src/container-old.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								src/container-old.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,351 @@ | |||||||
|  | // @ts-nocheck | ||||||
|  | import createEmotion from '@emotion/css/create-instance'; | ||||||
|  | import EventEmitter from 'eventemitter3'; | ||||||
|  | import { ContainerEvent } from './event/continer'; | ||||||
|  | import { RenderCode } from './render'; | ||||||
|  | import { codeUrlManager } from './code-url'; | ||||||
|  |  | ||||||
|  | const handleOneCode = async (data: RenderData) => { | ||||||
|  |   const { code } = data; | ||||||
|  |   const urlsBegin = ['http', 'https', 'blob', '//:']; | ||||||
|  |   if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) { | ||||||
|  |     const importContent = await import(/* @vite-ignore */ code); | ||||||
|  |     const { render, unmount, ...rest } = importContent || {}; | ||||||
|  |     const _data = { | ||||||
|  |       ...data, | ||||||
|  |       code: { | ||||||
|  |         render, | ||||||
|  |         unmount, | ||||||
|  |         ...rest, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     return _data; | ||||||
|  |   } | ||||||
|  |   if (typeof code === 'string' && data.codeId) { | ||||||
|  |     const codeId = data.codeId; | ||||||
|  |     const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash); | ||||||
|  |     const importContent = await import(/* @vite-ignore */ url); | ||||||
|  |     const { render, unmount, ...rest } = importContent || {}; | ||||||
|  |     const _data = { | ||||||
|  |       ...data, | ||||||
|  |       codeId, | ||||||
|  |       hash, | ||||||
|  |       code: { | ||||||
|  |         render, | ||||||
|  |         unmount, | ||||||
|  |         ...rest, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     return _data; | ||||||
|  |   } | ||||||
|  |   return data; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const handleCode = async (data: RenderData[]) => { | ||||||
|  |   const handleData = []; | ||||||
|  |   for (let i = 0; i < data.length; i++) { | ||||||
|  |     const item = data[i]; | ||||||
|  |     const _data = await handleOneCode(item); | ||||||
|  |     handleData.push(_data); | ||||||
|  |   } | ||||||
|  |   return handleData; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type RenderData = { | ||||||
|  |   id: string; // id不会重复 | ||||||
|  |   children?: string[]; | ||||||
|  |   parents?: string[]; | ||||||
|  |   className?: string; | ||||||
|  |   style?: React.CSSProperties | { [key: string]: string }; | ||||||
|  |   code: RenderCode | string; | ||||||
|  |   codeId?: string; | ||||||
|  |   shadowRoot?: boolean; | ||||||
|  |   hash?: string; | ||||||
|  |   [key: string]: any; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type ContainerOpts = { | ||||||
|  |   root?: HTMLDivElement | string; | ||||||
|  |   shadowRoot?: ShadowRoot; | ||||||
|  |   destroy?: () => void; | ||||||
|  |   data?: RenderData[]; | ||||||
|  |   showChild?: boolean; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export class Container { | ||||||
|  |   data: RenderData[]; | ||||||
|  |   root: HTMLDivElement; | ||||||
|  |   globalCss: any; | ||||||
|  |   showChild: boolean; | ||||||
|  |   event: EventEmitter; | ||||||
|  |   entryId: string = ''; | ||||||
|  |   loading: boolean = false; | ||||||
|  |   loaded: boolean = false; | ||||||
|  |   constructor(opts: ContainerOpts) { | ||||||
|  |     const data = opts.data || []; | ||||||
|  |     this.loadData.apply(this, [data]); | ||||||
|  |  | ||||||
|  |     const rootElement = typeof opts.root === 'string' ? document.querySelector(opts.root) : opts.root; | ||||||
|  |     this.root = rootElement || (document.body as any); | ||||||
|  |     this.globalCss = createEmotion({ key: 'css-global', speedy: true }).css; | ||||||
|  |     this.showChild = opts.showChild ?? true; | ||||||
|  |     const event = new EventEmitter(); | ||||||
|  |     this.event = event; | ||||||
|  |     const listening = this.root.dataset.listening; | ||||||
|  |     if (listening !== 'true') { | ||||||
|  |       this.root.addEventListener('onContainer', (e: ContainerEvent) => { | ||||||
|  |         const { type, data } = e.data; | ||||||
|  |         this.event.emit(type, data); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async loadData(data: RenderData[], key: string = '') { | ||||||
|  |     if (data.length === 0) { | ||||||
|  |       this.data = data; | ||||||
|  |       this.loaded = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.loading = true; | ||||||
|  |     this.data = await handleCode(data); | ||||||
|  |     this.loading = false; | ||||||
|  |     this.event.emit('loadedData' + key); | ||||||
|  |     this.loaded = true; | ||||||
|  |   } | ||||||
|  |   async getData({ id, codeId }: { id?: string; codeId?: string }) { | ||||||
|  |     if (id && codeId) { | ||||||
|  |       return this.data.find((item) => item.id === id && item.codeId === codeId); | ||||||
|  |     } | ||||||
|  |     if (id) { | ||||||
|  |       return this.data.find((item) => item.id === id); | ||||||
|  |     } | ||||||
|  |     if (codeId) { | ||||||
|  |       return this.data.find((item) => item.codeId === codeId); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |   async updateDataCode(data: RenderData[]) { | ||||||
|  |     if (this.loading) { | ||||||
|  |       console.warn('loading'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const _data = this.data.map((item) => { | ||||||
|  |       const node = data.find((node) => node.codeId && node.codeId === item.codeId); | ||||||
|  |       if (node) { | ||||||
|  |         return { | ||||||
|  |           ...item, | ||||||
|  |           ...node, | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       return item; | ||||||
|  |     }); | ||||||
|  |     await this.loadData(_data); | ||||||
|  |   } | ||||||
|  |   async updateData(data: RenderData[]) { | ||||||
|  |     if (this.loading) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const _data = this.data.map((item) => { | ||||||
|  |       const node = data.find((node) => node.id === item.id); | ||||||
|  |       if (node) { | ||||||
|  |         return { | ||||||
|  |           ...item, | ||||||
|  |           ...node, | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |       return item; | ||||||
|  |     }); | ||||||
|  |     console.log('updateData', _data.length, data.length); | ||||||
|  |     await this.loadData(_data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   renderChildren(cid: string, parentElement?: any, pid?: any): void | HTMLDivElement { | ||||||
|  |     const data = this.data; | ||||||
|  |     console.log('renderChildren', cid, data); | ||||||
|  |     const globalCss = this.globalCss; | ||||||
|  |     if (!parentElement) { | ||||||
|  |       parentElement = this.root; | ||||||
|  |       this.root.dataset.entryId = cid; | ||||||
|  |       this.entryId = cid; | ||||||
|  |     } | ||||||
|  |     const renderChildren = this.renderChildren.bind(this); | ||||||
|  |     const node = data.find((node: RenderData) => node.id === cid); | ||||||
|  |     const event = this.event; | ||||||
|  |     if (!node) { | ||||||
|  |       console.warn('node not found', cid); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const { style, code } = node; | ||||||
|  |     const el = document.createElement('div'); | ||||||
|  |     const root = parentElement.appendChild(el); | ||||||
|  |     const parentIds = parentElement.dataset.parentIds || ''; | ||||||
|  |     const shadowRoot = node.shadowRoot ? el.attachShadow({ mode: 'open' }) : null; | ||||||
|  |     const { css, sheet, cache } = createEmotion({ key: 'css' }); | ||||||
|  |     el.dataset.cid = cid; | ||||||
|  |     el.dataset.pid = pid; | ||||||
|  |     el.dataset.parentIds = parentIds ? parentIds + ',' + cid : cid; | ||||||
|  |     if (shadowRoot) { | ||||||
|  |       cache.insert = (selector: string, serialized: any, sheet: any, shouldCache: boolean) => { | ||||||
|  |         const style = document.createElement('style'); | ||||||
|  |         style.textContent = selector; | ||||||
|  |         shadowRoot.appendChild(style); | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     if (el.style) { | ||||||
|  |       el.className = !shadowRoot ? globalCss(style as any) : css(style as any); | ||||||
|  |     } | ||||||
|  |     el.classList.add(cid, 'kv-container'); | ||||||
|  |     const { render, unmount } = (code as RenderCode) || {}; | ||||||
|  |     let renderRoot = node.shadowRoot ? shadowRoot : root; | ||||||
|  |     const ctx = { | ||||||
|  |       root: root, | ||||||
|  |       shadowRoot, | ||||||
|  |       renderRoot, | ||||||
|  |       event, | ||||||
|  |       container: this, | ||||||
|  |       code: code as RenderCode, | ||||||
|  |       data: node, | ||||||
|  |       css: shadowRoot ? css : globalCss, | ||||||
|  |     }; | ||||||
|  |     if (render) { | ||||||
|  |       render(ctx); | ||||||
|  |     } else { | ||||||
|  |       // no render, so no insert | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (event) { | ||||||
|  |       const destroy = (id: string) => { | ||||||
|  |         if (id) { | ||||||
|  |           if (id === cid || node?.parents?.find?.((item) => item === id)) { | ||||||
|  |             unmount?.(ctx); // 销毁父亲元素有这个元素的 | ||||||
|  |             event.off('destroy', destroy); // 移除监听 | ||||||
|  |           } else { | ||||||
|  |             // console.warn('destroy id not found, and not find parentIds', id, 'currentid', cid); | ||||||
|  |           } | ||||||
|  |         } else if (!id) { | ||||||
|  |           unmount?.(ctx); //  所有的都销毁 | ||||||
|  |           event.off('destroy', destroy); | ||||||
|  |         } | ||||||
|  |         // 不需要销毁子元素 | ||||||
|  |       }; | ||||||
|  |       event.on('destroy', destroy); | ||||||
|  |     } | ||||||
|  |     if (shadowRoot) { | ||||||
|  |       const slot = document.createElement('slot'); | ||||||
|  |       shadowRoot.appendChild(slot); | ||||||
|  |     } | ||||||
|  |     if (!this.showChild) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const childrenIds = node.children || []; | ||||||
|  |     childrenIds.forEach((childId) => { | ||||||
|  |       renderChildren(childId, root, cid); | ||||||
|  |     }); | ||||||
|  |     return el; | ||||||
|  |   } | ||||||
|  |   async destroy(id?: string) { | ||||||
|  |     if (!id) { | ||||||
|  |       this.root.innerHTML = ''; | ||||||
|  |     } else { | ||||||
|  |       const elements = this.root.querySelectorAll(`[data-cid="${id}"]`); | ||||||
|  |       elements.forEach((element) => element.remove()); | ||||||
|  |     } | ||||||
|  |     this.event.emit('destroy', id); | ||||||
|  |     await new Promise((resolve) => { | ||||||
|  |       setTimeout(() => { | ||||||
|  |         resolve(null); | ||||||
|  |       }, 200); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * 只能用一次 | ||||||
|  |    * @param entryId | ||||||
|  |    * @param data | ||||||
|  |    * @param opts | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async render(entryId: string, data?: RenderData[], opts: { reload?: boolean } = {}) { | ||||||
|  |     if (this.entryId && this.entryId !== entryId) { | ||||||
|  |       await this.destroy(); | ||||||
|  |     } else if (this.entryId) { | ||||||
|  |       this.destroy(entryId); | ||||||
|  |     } | ||||||
|  |     const _data = data || this.data; | ||||||
|  |     this.entryId = entryId; | ||||||
|  |     if (opts?.reload) { | ||||||
|  |       this.loading = true; | ||||||
|  |       await this.loadData.apply(this, [_data]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.loading || !this.loaded) { | ||||||
|  |       this.event.once('loadedData', () => { | ||||||
|  |         this.renderChildren(entryId); | ||||||
|  |       }); | ||||||
|  |       return; | ||||||
|  |     } else { | ||||||
|  |       this.renderChildren(entryId); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async hotReload(id: string) { | ||||||
|  |     await this.destroy(id); | ||||||
|  |     // const node = document.querySelector(`[data-cid="${id}"]`); | ||||||
|  |     if (this.loading) { | ||||||
|  |       this.event.once('loadedData', () => { | ||||||
|  |         this.renderChildren(id); | ||||||
|  |       }); | ||||||
|  |       return; | ||||||
|  |     } else { | ||||||
|  |       this.renderChildren(id); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   async reRender() { | ||||||
|  |     await this.destroy(); | ||||||
|  |     this.renderChildren(this.entryId); | ||||||
|  |   } | ||||||
|  |   async renderId(id: string) { | ||||||
|  |     if (!this.entryId) { | ||||||
|  |       this.render(id); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (id === this.entryId) { | ||||||
|  |       this.reRender(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const node = this.data.find((item) => item.id === id); | ||||||
|  |     if (node?.parents && node.parents.length > 0) { | ||||||
|  |       const parent = node.parents[node.parents.length - 1]; | ||||||
|  |       const parentElement = this.root.querySelector(`[data-cid="${parent}"]`); | ||||||
|  |       await this.destroy(id); | ||||||
|  |       this.renderChildren(id, parentElement, parent); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   close() { | ||||||
|  |     this.destroy(); | ||||||
|  |     const event = this.event; | ||||||
|  |     event && event.removeAllListeners(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | export class ContainerOne extends Container { | ||||||
|  |   constructor(opts: ContainerOpts) { | ||||||
|  |     super(opts); | ||||||
|  |   } | ||||||
|  |   async renderOne({ code: RenderCode }) { | ||||||
|  |     const newData = { codeId: 'test-reneder-one-code-id', id: 'test-render-one', code: RenderCode }; | ||||||
|  |     console.log('loading', this.loading); | ||||||
|  |     if (this.loading) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     console.log('renderOne', newData); | ||||||
|  |     if (this.data.length === 0) { | ||||||
|  |       await this.loadData([newData]); | ||||||
|  |     } else { | ||||||
|  |       await this.updateData([newData]); | ||||||
|  |     } | ||||||
|  |     this.hotReload('test-render-one'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | export const mount = ({ render, unmount }, root: string | HTMLDivElement) => { | ||||||
|  |   let _root = typeof root === 'string' ? document.querySelector(root) : root; | ||||||
|  |   _root = _root || (document.body as any); | ||||||
|  |   render({ root: _root }); | ||||||
|  | }; | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| .kv-container.active { | .kv-container.active { | ||||||
|   background: #000 !important; |   background: #000; | ||||||
|   border: 2px solid #195ca9; |   border: 2px solid #195ca9; | ||||||
|   z-index: 100; |   z-index: 100; | ||||||
| } | } | ||||||
| @@ -11,10 +11,11 @@ | |||||||
| .kv-container.hover { | .kv-container.hover { | ||||||
|   cursor: move; |   cursor: move; | ||||||
| } | } | ||||||
| .kv-container > .resizer, .kv-container > .drag-title { | .kv-container > .resizer, | ||||||
|  | .kv-container > .drag-title { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
| .kv-container.active > .resizer, .kv-container.active > .drag-title { | .kv-container.active > .resizer, | ||||||
|  | .kv-container.active > .drag-title { | ||||||
|   display: block; |   display: block; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										420
									
								
								src/container.ts
									
									
									
									
									
								
							
							
						
						
									
										420
									
								
								src/container.ts
									
									
									
									
									
								
							| @@ -1,130 +1,12 @@ | |||||||
| import createEmotion from '@emotion/css/create-instance'; | import createEmotion from '@emotion/css/create-instance'; | ||||||
| import EventEmitter from 'eventemitter3'; | import EventEmitter from 'eventemitter3'; | ||||||
| import { MD5 } from 'crypto-js'; |  | ||||||
| import { ContainerEvent } from './event/continer'; | import { ContainerEvent } from './event/continer'; | ||||||
| import { RenderCode } from './render'; | import { RenderCode } from './render'; | ||||||
|  | import * as schedule from './scheduler'; | ||||||
|  | import { CodeUrlAdapter } from './handle/adapter'; | ||||||
|  | import { codeUrlManager } from './code-url'; | ||||||
|  |  | ||||||
| type CodeUrl = { | export { createEmotion }; | ||||||
|   url: string; // 地址 |  | ||||||
|   code?: string; // 代码 |  | ||||||
|   id?: string; // codeId |  | ||||||
|   hash?: string; |  | ||||||
| }; |  | ||||||
| export class CodeUrlManager { |  | ||||||
|   urls: CodeUrl[] = []; |  | ||||||
|   urlMap: { [key: string]: CodeUrl } = {}; |  | ||||||
|   constructor() { |  | ||||||
|     this.urls = []; |  | ||||||
|   } |  | ||||||
|   addCode(code: string, id: string, hash?: string) { |  | ||||||
|     const urlMap = this.urlMap; |  | ||||||
|     const resultContent = (content: CodeUrl) => { |  | ||||||
|       const index = this.urls.findIndex((item) => item.id === id); |  | ||||||
|       if (index === -1) { |  | ||||||
|         this.urls.push(content); |  | ||||||
|       } |  | ||||||
|       return content; |  | ||||||
|     }; |  | ||||||
|     if (urlMap[hash]) { |  | ||||||
|       // 可能id不同,但是hash相同。已经加载过了。 |  | ||||||
|       const content = { |  | ||||||
|         ...urlMap[hash], |  | ||||||
|         id, |  | ||||||
|       }; |  | ||||||
|       return resultContent(content); |  | ||||||
|     } |  | ||||||
|     const _hash = hash || MD5(code).toString(); |  | ||||||
|     if (urlMap[_hash]) { |  | ||||||
|       const content = { |  | ||||||
|         ...urlMap[_hash], |  | ||||||
|         id, |  | ||||||
|       }; |  | ||||||
|       return resultContent(content); |  | ||||||
|     } |  | ||||||
|     const index = this.urls.findIndex((item) => item.id === id); |  | ||||||
|     if (index > -1) { |  | ||||||
|       // 没有共同hash的值了,但是还是找了id,这个时候应该移除之前 |  | ||||||
|       const url = this.urls[index]; |  | ||||||
|       const list = this.urls.filter((item) => item.hash === url.hash); |  | ||||||
|       if (list.length <= 1) { |  | ||||||
|         // 这个hash的值没有人用了 |  | ||||||
|         URL.revokeObjectURL(url.url); |  | ||||||
|         this.urlMap[url.hash] = null; |  | ||||||
|       } |  | ||||||
|       this.urls.splice(index, 1); |  | ||||||
|     } |  | ||||||
|     /** |  | ||||||
|      * 全新代码 |  | ||||||
|      * |  | ||||||
|      */ |  | ||||||
|     const url = URL.createObjectURL(new Blob([code], { type: 'application/javascript' })); |  | ||||||
|     const content = { |  | ||||||
|       id, |  | ||||||
|       url, |  | ||||||
|       code, |  | ||||||
|       hash: _hash, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     this.urls.push(content); |  | ||||||
|     urlMap[_hash] = { ...content, id: null }; |  | ||||||
|     return content; |  | ||||||
|   } |  | ||||||
|   removeUrl(id?: string, url?: string) { |  | ||||||
|     const index = this.urls.findIndex((item) => item.url === url || item.id === id); |  | ||||||
|     if (index > -1) { |  | ||||||
|       const url = this.urls[index]; |  | ||||||
|       URL.revokeObjectURL(url.url); |  | ||||||
|       this.urlMap[url.hash] = null; |  | ||||||
|       this.urls.splice(index, 1); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| export const codeUrlManager = new CodeUrlManager(); |  | ||||||
| const handleOneCode = async (data: RenderData) => { |  | ||||||
|   const { code } = data; |  | ||||||
|   const urlsBegin = ['http', 'https', 'blob', '//:']; |  | ||||||
|   if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) { |  | ||||||
|     const importContent = await import(/* @vite-ignore */ code); |  | ||||||
|     const { render, unmount, ...rest } = importContent || {}; |  | ||||||
|     const _data = { |  | ||||||
|       ...data, |  | ||||||
|       code: { |  | ||||||
|         render, |  | ||||||
|         unmount, |  | ||||||
|         ...rest, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|     return _data; |  | ||||||
|   } |  | ||||||
|   if (typeof code === 'string' && data.codeId) { |  | ||||||
|     const codeId = data.codeId; |  | ||||||
|     const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash); |  | ||||||
|     const importContent = await import(/* @vite-ignore */ url); |  | ||||||
|     const { render, unmount, ...rest } = importContent || {}; |  | ||||||
|     const _data = { |  | ||||||
|       ...data, |  | ||||||
|       codeId, |  | ||||||
|       hash, |  | ||||||
|       code: { |  | ||||||
|         render, |  | ||||||
|         unmount, |  | ||||||
|         ...rest, |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|     return _data; |  | ||||||
|   } |  | ||||||
|   return data; |  | ||||||
| }; |  | ||||||
| const handleCode = async (data: RenderData[]) => { |  | ||||||
|   const handleData = []; |  | ||||||
|   for (let i = 0; i < data.length; i++) { |  | ||||||
|     const item = data[i]; |  | ||||||
|     const _data = await handleOneCode(item); |  | ||||||
|     handleData.push(_data); |  | ||||||
|   } |  | ||||||
|   return handleData; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export type RenderData = { | export type RenderData = { | ||||||
|   id: string; // id不会重复 |   id: string; // id不会重复 | ||||||
|   children?: string[]; |   children?: string[]; | ||||||
| @@ -141,31 +23,27 @@ export type RenderData = { | |||||||
| export type ContainerOpts = { | export type ContainerOpts = { | ||||||
|   root?: HTMLDivElement | string; |   root?: HTMLDivElement | string; | ||||||
|   shadowRoot?: ShadowRoot; |   shadowRoot?: ShadowRoot; | ||||||
|   destroy?: () => void; |  | ||||||
|   data?: RenderData[]; |   data?: RenderData[]; | ||||||
|   showChild?: boolean; |   adapter?: any; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export class Container { | export class Container { | ||||||
|   data: RenderData[]; |   data: RenderData[]; | ||||||
|   root: HTMLDivElement; |   root: HTMLDivElement; | ||||||
|   globalCss: any; |   globalCss: any; | ||||||
|   showChild: boolean; |  | ||||||
|   event: EventEmitter; |   event: EventEmitter; | ||||||
|   entryId: string = ''; |   entryId: string = ''; | ||||||
|   loading: boolean = false; |   adapter: CodeUrlAdapter; | ||||||
|   loaded: boolean = false; |  | ||||||
|   constructor(opts: ContainerOpts) { |   constructor(opts: ContainerOpts) { | ||||||
|     const data = opts.data || []; |     const data = opts.data || []; | ||||||
|     this.loadData.apply(this, [data]); |     this.data = data; | ||||||
|  |  | ||||||
|     const rootElement = typeof opts.root === 'string' ? document.querySelector(opts.root) : opts.root; |     const rootElement = typeof opts.root === 'string' ? document.querySelector(opts.root) : opts.root; | ||||||
|     this.root = rootElement || (document.body as any); |     this.root = rootElement || (document.body as any); | ||||||
|     this.globalCss = createEmotion({ key: 'css-global', speedy: true }).css; |     this.globalCss = createEmotion({ key: 'css-global', speedy: true }).css; | ||||||
|     this.showChild = opts.showChild ?? true; |  | ||||||
|     const event = new EventEmitter(); |     const event = new EventEmitter(); | ||||||
|     this.event = event; |     this.event = event; | ||||||
|     const listening = this.root.dataset.listening; |     const listening = this.root.dataset.listening; | ||||||
|  |     this.adapter = opts.adapter || this.createAdapter(); | ||||||
|     if (listening !== 'true') { |     if (listening !== 'true') { | ||||||
|       this.root.addEventListener('onContainer', (e: ContainerEvent) => { |       this.root.addEventListener('onContainer', (e: ContainerEvent) => { | ||||||
|         const { type, data } = e.data; |         const { type, data } = e.data; | ||||||
| @@ -173,68 +51,31 @@ export class Container { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   async loadData(data: RenderData[], key: string = '') { |   protected createAdapter() { | ||||||
|     if (data.length === 0) { |     const adapter = new CodeUrlAdapter({ codeUrlManager }); | ||||||
|       this.data = data; |     this.adapter = adapter; | ||||||
|       this.loaded = true; |     return adapter; | ||||||
|       return; |  | ||||||
|   } |   } | ||||||
|     this.loading = true; |   getData({ id }: { id: string }) { | ||||||
|     this.data = await handleCode(data); |     const data = this.data; | ||||||
|     this.loading = false; |     const node = data.find((node) => node.id === id); | ||||||
|     this.event.emit('loadedData' + key); |     return node; | ||||||
|     this.loaded = true; |   } | ||||||
|   } |   updateData(data: RenderData[]) { | ||||||
|   async getData({ id, codeId }: { id?: string; codeId?: string }) { |     this.data = this.data.map((node) => { | ||||||
|     if (id && codeId) { |       const find = data.find((item) => item.id === node.id); | ||||||
|       return this.data.find((item) => item.id === id && item.codeId === codeId); |       return find || node; | ||||||
|     } |     }); | ||||||
|     if (id) { |   } | ||||||
|       return this.data.find((item) => item.id === id); |   /** | ||||||
|     } |    * 渲染模块 | ||||||
|     if (codeId) { |    * @param cid | ||||||
|       return this.data.find((item) => item.codeId === codeId); |    * @param parentElement | ||||||
|     } |    * @param pid | ||||||
|     return null; |    * @returns | ||||||
|   } |    */ | ||||||
|   async updateDataCode(data: RenderData[]) { |   renderChildren(cid: string, parentElement?: any, pid?: any, isNew = true): void | HTMLDivElement { | ||||||
|     if (this.loading) { |  | ||||||
|       console.warn('loading'); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const _data = this.data.map((item) => { |  | ||||||
|       const node = data.find((node) => node.codeId && node.codeId === item.codeId); |  | ||||||
|       if (node) { |  | ||||||
|         return { |  | ||||||
|           ...item, |  | ||||||
|           ...node, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|       return item; |  | ||||||
|     }); |  | ||||||
|     await this.loadData(_data); |  | ||||||
|   } |  | ||||||
|   async updateData(data: RenderData[]) { |  | ||||||
|     if (this.loading) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const _data = this.data.map((item) => { |  | ||||||
|       const node = data.find((node) => node.id === item.id); |  | ||||||
|       if (node) { |  | ||||||
|         return { |  | ||||||
|           ...item, |  | ||||||
|           ...node, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|       return item; |  | ||||||
|     }); |  | ||||||
|     console.log('updateData', _data.length, data.length); |  | ||||||
|     await this.loadData(_data); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   renderChildren(cid: string, parentElement?: any, pid?: any): void | HTMLDivElement { |  | ||||||
|     const data = this.data; |     const data = this.data; | ||||||
|     console.log('renderChildren', cid, data); |  | ||||||
|     const globalCss = this.globalCss; |     const globalCss = this.globalCss; | ||||||
|     if (!parentElement) { |     if (!parentElement) { | ||||||
|       parentElement = this.root; |       parentElement = this.root; | ||||||
| @@ -243,20 +84,64 @@ export class Container { | |||||||
|     } |     } | ||||||
|     const renderChildren = this.renderChildren.bind(this); |     const renderChildren = this.renderChildren.bind(this); | ||||||
|     const node = data.find((node: RenderData) => node.id === cid); |     const node = data.find((node: RenderData) => node.id === cid); | ||||||
|     const event = this.event; |  | ||||||
|     if (!node) { |     if (!node) { | ||||||
|       console.warn('node not found', cid); |       console.warn('node not found', cid); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     const { style, code } = node; |     let el; | ||||||
|     const el = document.createElement('div'); |     if (isNew) { | ||||||
|     const root = parentElement.appendChild(el); |       el = document.createElement('div'); | ||||||
|  |       parentElement.appendChild(el); | ||||||
|       const parentIds = parentElement.dataset.parentIds || ''; |       const parentIds = parentElement.dataset.parentIds || ''; | ||||||
|     const shadowRoot = node.shadowRoot ? el.attachShadow({ mode: 'open' }) : null; |  | ||||||
|     const { css, sheet, cache } = createEmotion({ key: 'css' }); |  | ||||||
|       el.dataset.cid = cid; |       el.dataset.cid = cid; | ||||||
|       el.dataset.pid = pid; |       el.dataset.pid = pid; | ||||||
|       el.dataset.parentIds = parentIds ? parentIds + ',' + cid : cid; |       el.dataset.parentIds = parentIds ? parentIds + ',' + cid : cid; | ||||||
|  |       // 代码渲染 | ||||||
|  |       const { renderFn } = this.getContext({ renderEl: el, entryId: cid }); | ||||||
|  |       schedule.addTask(renderFn, 'high'); | ||||||
|  |       // 代码渲染结束 | ||||||
|  |     } else { | ||||||
|  |       el = parentElement.querySelector(`[data-cid="${cid}"]`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!el) { | ||||||
|  |       console.warn('when no createNewElement el not found', cid); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const childrenIds = node.children || []; | ||||||
|  |     childrenIds.forEach((childId) => { | ||||||
|  |       renderChildren(childId, el, cid); | ||||||
|  |     }); | ||||||
|  |     return el; | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * 渲染根节点 | ||||||
|  |    * @param entryId | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async renderRoot(entryId: string) { | ||||||
|  |     if (this.entryId && this.entryId !== entryId) { | ||||||
|  |       await this.destroy(); | ||||||
|  |     } else if (this.entryId) { | ||||||
|  |       await this.destroy(entryId); | ||||||
|  |     } | ||||||
|  |     this.renderChildren(entryId); | ||||||
|  |   } | ||||||
|  |   async reRender() { | ||||||
|  |     await this.destroy(); | ||||||
|  |     this.renderChildren(this.entryId); | ||||||
|  |   } | ||||||
|  |   protected getContext({ renderEl, entryId }: { renderEl: HTMLDivElement; entryId: string }) { | ||||||
|  |     const node = this.data.find((node) => node.id === entryId); | ||||||
|  |     const { style, code } = node; | ||||||
|  |     const event = this.event; | ||||||
|  |     const globalCss = this.globalCss; | ||||||
|  |     const root = renderEl; | ||||||
|  |     const adapter = this.adapter; | ||||||
|  |     const containerThis = this; | ||||||
|  |     const shadowRoot = node.shadowRoot ? renderEl.attachShadow({ mode: 'open' }) : null; | ||||||
|  |     const { css, sheet, cache } = createEmotion({ key: 'css' }); | ||||||
|     if (shadowRoot) { |     if (shadowRoot) { | ||||||
|       cache.insert = (selector: string, serialized: any, sheet: any, shouldCache: boolean) => { |       cache.insert = (selector: string, serialized: any, sheet: any, shouldCache: boolean) => { | ||||||
|         const style = document.createElement('style'); |         const style = document.createElement('style'); | ||||||
| @@ -264,19 +149,27 @@ export class Container { | |||||||
|         shadowRoot.appendChild(style); |         shadowRoot.appendChild(style); | ||||||
|       }; |       }; | ||||||
|     } |     } | ||||||
|     if (el.style) { |     const cid = entryId; | ||||||
|       el.className = !shadowRoot ? globalCss(style as any) : css(style as any); |     renderEl.className = ''; | ||||||
|  |     if (renderEl.style) { | ||||||
|  |       renderEl.className = !shadowRoot ? globalCss(style as any) : css(style as any); | ||||||
|     } |     } | ||||||
|     el.classList.add(cid, 'kv-container'); |     renderEl.classList.add(cid, 'kv-container'); | ||||||
|     const { render, unmount } = (code as RenderCode) || {}; |     if (shadowRoot) { | ||||||
|  |       const slot = document.createElement('slot'); | ||||||
|  |       shadowRoot.appendChild(slot); | ||||||
|  |     } | ||||||
|  |     const renderFn = async () => { | ||||||
|  |       const { code: newCode } = await adapter.handle(node as any); | ||||||
|  |       const { render, unmount } = newCode || {}; | ||||||
|       let renderRoot = node.shadowRoot ? shadowRoot : root; |       let renderRoot = node.shadowRoot ? shadowRoot : root; | ||||||
|       const ctx = { |       const ctx = { | ||||||
|         root: root, |         root: root, | ||||||
|         shadowRoot, |         shadowRoot, | ||||||
|         renderRoot, |         renderRoot, | ||||||
|         event, |         event, | ||||||
|       container: this, |         container: containerThis, | ||||||
|       code: code as RenderCode, |         code: newCode as RenderCode, | ||||||
|         data: node, |         data: node, | ||||||
|         css: shadowRoot ? css : globalCss, |         css: shadowRoot ? css : globalCss, | ||||||
|       }; |       }; | ||||||
| @@ -285,7 +178,6 @@ export class Container { | |||||||
|       } else { |       } else { | ||||||
|         // no render, so no insert |         // no render, so no insert | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (event) { |       if (event) { | ||||||
|         const destroy = (id: string) => { |         const destroy = (id: string) => { | ||||||
|           if (id) { |           if (id) { | ||||||
| @@ -303,25 +195,48 @@ export class Container { | |||||||
|         }; |         }; | ||||||
|         event.on('destroy', destroy); |         event.on('destroy', destroy); | ||||||
|       } |       } | ||||||
|     if (shadowRoot) { |     }; | ||||||
|       const slot = document.createElement('slot'); |     return { renderFn, shadowRoot }; | ||||||
|       shadowRoot.appendChild(slot); |  | ||||||
|   } |   } | ||||||
|     if (!this.showChild) { |   // 更新渲染 | ||||||
|  |   async render(entryId: string, el?: HTMLDivElement) { | ||||||
|  |     if (schedule.getLoading()) { | ||||||
|  |       console.log('schedule is loading'); | ||||||
|  |     } | ||||||
|  |     if (!this.entryId) { | ||||||
|  |       console.warn('root is use renderRoot method, not render'); | ||||||
|  |       return this.renderRoot(entryId); | ||||||
|  |     } | ||||||
|  |     const isRoot = this.entryId === entryId; | ||||||
|  |     if (isRoot) { | ||||||
|  |       await this.reRender(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     const childrenIds = node.children || []; |     let renderEl = el; | ||||||
|     childrenIds.forEach((childId) => { |     if (!el) { | ||||||
|       renderChildren(childId, root, cid); |       renderEl = this.root.querySelector(`[data-cid="${entryId}"]`); | ||||||
|     }); |  | ||||||
|     return el; |  | ||||||
|     } |     } | ||||||
|  |     if (!renderEl) { | ||||||
|  |       console.warn('render element not found', entryId); | ||||||
|  |       return; | ||||||
|  |     } else { | ||||||
|  |       await this.destroy(entryId); | ||||||
|  |     } | ||||||
|  |     const parentElement = renderEl.parentElement; | ||||||
|  |     const pid = renderEl.dataset.pid; | ||||||
|  |     const { renderFn } = this.getContext({ renderEl, entryId }); | ||||||
|  |     schedule.addTask(renderFn, 'high'); | ||||||
|  |     this.renderChildren(entryId, parentElement, pid, false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   async destroy(id?: string) { |   async destroy(id?: string) { | ||||||
|     if (!id) { |     if (!id) { | ||||||
|       this.root.innerHTML = ''; |       this.root.innerHTML = ''; | ||||||
|     } else { |     } else { | ||||||
|       const elements = this.root.querySelectorAll(`[data-cid="${id}"]`); |       const elements = this.root.querySelectorAll(`[data-cid="${id}"]`); | ||||||
|       elements.forEach((element) => element.remove()); |       elements.forEach((element) => { | ||||||
|  |         element.innerHTML = ''; | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
|     this.event.emit('destroy', id); |     this.event.emit('destroy', id); | ||||||
|     await new Promise((resolve) => { |     await new Promise((resolve) => { | ||||||
| @@ -330,92 +245,23 @@ export class Container { | |||||||
|       }, 200); |       }, 200); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   /** |  | ||||||
|    * 只能用一次 |  | ||||||
|    * @param entryId |  | ||||||
|    * @param data |  | ||||||
|    * @param opts |  | ||||||
|    * @returns |  | ||||||
|    */ |  | ||||||
|   async render(entryId: string, data?: RenderData[], opts: { reload?: boolean } = {}) { |  | ||||||
|     if (this.entryId && this.entryId !== entryId) { |  | ||||||
|       await this.destroy(); |  | ||||||
|     } else if (this.entryId) { |  | ||||||
|       this.destroy(entryId); |  | ||||||
|     } |  | ||||||
|     const _data = data || this.data; |  | ||||||
|     this.entryId = entryId; |  | ||||||
|     if (opts?.reload) { |  | ||||||
|       this.loading = true; |  | ||||||
|       await this.loadData.apply(this, [_data]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (this.loading || !this.loaded) { |  | ||||||
|       this.event.once('loadedData', () => { |  | ||||||
|         this.renderChildren(entryId); |  | ||||||
|       }); |  | ||||||
|       return; |  | ||||||
|     } else { |  | ||||||
|       this.renderChildren(entryId); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   async hotReload(id: string) { |  | ||||||
|     await this.destroy(id); |  | ||||||
|     // const node = document.querySelector(`[data-cid="${id}"]`); |  | ||||||
|     if (this.loading) { |  | ||||||
|       this.event.once('loadedData', () => { |  | ||||||
|         this.renderChildren(id); |  | ||||||
|       }); |  | ||||||
|       return; |  | ||||||
|     } else { |  | ||||||
|       this.renderChildren(id); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   async reRender() { |  | ||||||
|     await this.destroy(); |  | ||||||
|     this.renderChildren(this.entryId); |  | ||||||
|   } |  | ||||||
|   async renderId(id: string) { |  | ||||||
|     if (!this.entryId) { |  | ||||||
|       this.render(id); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (id === this.entryId) { |  | ||||||
|       this.reRender(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const node = this.data.find((item) => item.id === id); |  | ||||||
|     if (node?.parents && node.parents.length > 0) { |  | ||||||
|       const parent = node.parents[node.parents.length - 1]; |  | ||||||
|       const parentElement = this.root.querySelector(`[data-cid="${parent}"]`); |  | ||||||
|       await this.destroy(id); |  | ||||||
|       this.renderChildren(id, parentElement, parent); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   close() { |   close() { | ||||||
|     this.destroy(); |     this.destroy(); | ||||||
|     const event = this.event; |     const event = this.event; | ||||||
|     event && event.removeAllListeners(); |     event && event.removeAllListeners(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class ContainerOne extends Container { | export class ContainerOne extends Container { | ||||||
|   constructor(opts: ContainerOpts) { |   constructor(opts: ContainerOpts) { | ||||||
|     super(opts); |     super(opts); | ||||||
|   } |   } | ||||||
|   async renderOne({ code: RenderCode }) { |   async renderOne({ code: RenderCode }) { | ||||||
|     const newData = { codeId: 'test-reneder-one-code-id', id: 'test-render-one', code: RenderCode }; |     const newData = { codeId: 'test-reneder-one-code-id', id: 'test-render-one', code: RenderCode }; | ||||||
|     console.log('loading', this.loading); |     this.data = [newData]; | ||||||
|     if (this.loading) { |     setTimeout(() => { | ||||||
|       return; |       this.renderRoot('test-render-one'); | ||||||
|     } |     }, 1000); | ||||||
|     console.log('renderOne', newData); |  | ||||||
|     if (this.data.length === 0) { |  | ||||||
|       await this.loadData([newData]); |  | ||||||
|     } else { |  | ||||||
|       await this.updateData([newData]); |  | ||||||
|     } |  | ||||||
|     this.hotReload('test-render-one'); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| export const mount = ({ render, unmount }, root: string | HTMLDivElement) => { | export const mount = ({ render, unmount }, root: string | HTMLDivElement) => { | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| export * from './container-edit'; | export * from './container-edit'; | ||||||
|  |  | ||||||
| export * from './container'; |  | ||||||
|  |  | ||||||
| export * from './render'; | export * from './render'; | ||||||
|  |  | ||||||
| export * from './event/unload'; | export * from './event/unload'; | ||||||
| @@ -9,3 +7,5 @@ export * from './event/unload'; | |||||||
| export * from './event/emitter'; | export * from './event/emitter'; | ||||||
|  |  | ||||||
| export * from './event/continer'; | export * from './event/continer'; | ||||||
|  |  | ||||||
|  | export * from './code-url'; | ||||||
							
								
								
									
										85
									
								
								src/handle/adapter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/handle/adapter.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | import type { CodeUrlManager } from '../code-url'; | ||||||
|  | type Fn = (args: any) => any; | ||||||
|  | export type RenderCode = { | ||||||
|  |   render: Fn; | ||||||
|  |   unmount?: Fn; | ||||||
|  |   [key: string]: any; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | type AdapterData = { | ||||||
|  |   codeId?: string; | ||||||
|  |   code?: string; | ||||||
|  |   hash?: string; | ||||||
|  |   codeUrl?: string; | ||||||
|  | }; | ||||||
|  | type CodeAdapterOpts<T = any> = { | ||||||
|  |   handle?: any; | ||||||
|  | } & T; | ||||||
|  | export class CodeAdapter { | ||||||
|  |   handle: (data: AdapterData) => Promise<any>; | ||||||
|  |   constructor(opts: CodeAdapterOpts) { | ||||||
|  |     this.handle = opts.handle; | ||||||
|  |   } | ||||||
|  |   setHandle(handle: (data: AdapterData) => Promise<any>) { | ||||||
|  |     this.handle = handle; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | type Opts = { | ||||||
|  |   codeUrlManager: CodeUrlManager; | ||||||
|  | }; | ||||||
|  | export class CodeUrlAdapter extends CodeAdapter { | ||||||
|  |   codeUrlManager: CodeUrlManager; | ||||||
|  |   constructor(opts: CodeAdapterOpts<Opts>) { | ||||||
|  |     super(opts); | ||||||
|  |     this.handle = (data) => handleOneCode(data, this.codeUrlManager); | ||||||
|  |     this.codeUrlManager = opts.codeUrlManager; | ||||||
|  |   } | ||||||
|  |   setCodeUrlManager(codeUrlManager: CodeUrlManager) { | ||||||
|  |     this.codeUrlManager = codeUrlManager; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | export const handleOneCode = async (data: AdapterData, codeUrlManager: CodeUrlManager) => { | ||||||
|  |   const { code, codeId } = data; | ||||||
|  |   const urlsBegin = ['http', 'https', 'blob']; | ||||||
|  |  | ||||||
|  |   let importUrl = ''; | ||||||
|  |   // codeUrl优先级最高 | ||||||
|  |   if (data?.codeUrl) { | ||||||
|  |     importUrl = data.codeUrl; | ||||||
|  |   } else if (typeof code === 'string' && code && urlsBegin.find((item) => code.startsWith(item))) { | ||||||
|  |     importUrl = code; | ||||||
|  |   } | ||||||
|  |   if (importUrl) { | ||||||
|  |     const importContent = await import(/* @vite-ignore */ importUrl); | ||||||
|  |     const { render, unmount, ...rest } = importContent || {}; | ||||||
|  |     const _data = { | ||||||
|  |       ...data, | ||||||
|  |       code: { | ||||||
|  |         render, | ||||||
|  |         unmount, | ||||||
|  |         ...rest, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     return _data; | ||||||
|  |   } | ||||||
|  |   if (typeof code === 'string' && codeId) { | ||||||
|  |     const { url, hash } = codeUrlManager.addCode(code, codeId, data.hash); | ||||||
|  |     const importContent = await import(/* @vite-ignore */ url); | ||||||
|  |     const { render, unmount, ...rest } = importContent || {}; | ||||||
|  |     const _data = { | ||||||
|  |       ...data, | ||||||
|  |       codeId, | ||||||
|  |       hash, | ||||||
|  |       code: { | ||||||
|  |         render, | ||||||
|  |         unmount, | ||||||
|  |         ...rest, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     return _data; | ||||||
|  |   } else if (typeof code === 'string' && !codeId) { | ||||||
|  |     console.error('codeId is required', data); | ||||||
|  |   } | ||||||
|  |   // 无需处理 | ||||||
|  |   return data; | ||||||
|  | }; | ||||||
| @@ -7,3 +7,5 @@ export * from './event/unload'; | |||||||
| export * from './event/emitter'; | export * from './event/emitter'; | ||||||
|  |  | ||||||
| export * from './event/continer'; | export * from './event/continer'; | ||||||
|  |  | ||||||
|  | export * from './code-url'; | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| import EventEmitter from 'eventemitter3'; | import EventEmitter from 'eventemitter3'; | ||||||
| import type { Container } from './container'; | import type { Container } from './container'; | ||||||
| import type { ContainerEdit } from './container-edit'; | // import type { ContainerEdit } from './container-edit'; | ||||||
| import { Emotion } from '@emotion/css/create-instance'; | import { Emotion } from '@emotion/css/create-instance'; | ||||||
|  |  | ||||||
| export type RenderContext<T extends { [key: string]: any } = {}> = { | export type RenderContext<T extends { [key: string]: any } = {}> = { | ||||||
|   root?: HTMLDivElement; |   root?: HTMLDivElement; | ||||||
|   shadowRoot?: ShadowRoot; |   shadowRoot?: ShadowRoot; | ||||||
|   container?: Container | ContainerEdit; |   // container?: Container | ContainerEdit; | ||||||
|  |   container?: Container; | ||||||
|   event?: EventEmitter; |   event?: EventEmitter; | ||||||
|   code?: { |   code?: { | ||||||
|     render: Render; |     render: Render; | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/scheduler/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/scheduler/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | export * from './scheduler'; | ||||||
							
								
								
									
										55
									
								
								src/scheduler/scheduler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/scheduler/scheduler.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // 定义任务队列 | ||||||
|  | const highPriorityTasks = []; | ||||||
|  | const lowPriorityTasks = []; | ||||||
|  | let isSchedulerRunning = false; // 调度器状态 | ||||||
|  |  | ||||||
|  | export const fnvoid = () => {}; | ||||||
|  | export type Priority = 'high' | 'low'; | ||||||
|  | // 添加任务的辅助函数 | ||||||
|  | export const addTask = (callback: any, priority: Priority = 'low') => { | ||||||
|  |   if (priority === 'high') { | ||||||
|  |     highPriorityTasks.push(callback); | ||||||
|  |   } else { | ||||||
|  |     lowPriorityTasks.push(callback); | ||||||
|  |   } | ||||||
|  |   // 如果调度器未运行且有任务,启动调度 | ||||||
|  |   if (!isSchedulerRunning) { | ||||||
|  |     isSchedulerRunning = true; | ||||||
|  |     requestIdleCallback(processTasks); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 调度函数 | ||||||
|  | const processTasks: IdleRequestCallback = (deadline) => { | ||||||
|  |   // 优先处理高优先级任务 | ||||||
|  |   while (highPriorityTasks.length && deadline.timeRemaining() > 0) { | ||||||
|  |     const task = highPriorityTasks.shift(); | ||||||
|  |     task(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 如果有剩余时间,再处理低优先级任务 | ||||||
|  |   while (lowPriorityTasks.length && deadline.timeRemaining() > 0) { | ||||||
|  |     const task = lowPriorityTasks.shift(); | ||||||
|  |     task(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 检查是否还有任务 | ||||||
|  |   if (highPriorityTasks.length || lowPriorityTasks.length) { | ||||||
|  |     requestIdleCallback(processTasks); | ||||||
|  |   } else { | ||||||
|  |     // 没有任务,停止调度器 | ||||||
|  |     isSchedulerRunning = false; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | // 开始调度 | ||||||
|  | requestIdleCallback(processTasks); | ||||||
|  |  | ||||||
|  | // 添加一些测试任务 | ||||||
|  | // addTask(() => console.log('Low priority task 1'), 'low'); | ||||||
|  | // addTask(() => console.log('High priority task 1'), 'high'); | ||||||
|  | // addTask(() => console.log('Low priority task 2'), 'low'); | ||||||
|  | // addTask(() => console.log('High priority task 2'), 'high'); | ||||||
|  |  | ||||||
|  | export const getLoading = () => { | ||||||
|  |   return isSchedulerRunning; | ||||||
|  | }; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { handleCode, RenderData } from '../container'; | import { handleCode, RenderData } from '../container-old'; | ||||||
| import { createStore } from 'zustand/vanilla'; | import { createStore } from 'zustand/vanilla'; | ||||||
| export type ContainerStore = { | export type ContainerStore = { | ||||||
|   loading: boolean; |   loading: boolean; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { RenderData } from '../container'; | import { RenderData } from '../container-old'; | ||||||
|  |  | ||||||
| export const getTree = (data: RenderData[], id: string) => { | export const getTree = (data: RenderData[], id: string) => { | ||||||
|   const node = data.find((node) => node.id === id); |   const node = data.find((node) => node.id === id); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user