From e9be87bb807ee24736e5347e6fb9e8550c0efaab Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 23 Dec 2021 09:07:30 +0800 Subject: [PATCH 1/3] Match-id-e03cd1c3dfea5cf3911509808c3bea7d0a42cb68 --- .../renderer/components/BaseClassComponent.ts | 25 ++++ .../src/renderer/components/CreatePortal.ts | 15 ++ .../src/renderer/components/CreateRef.ts | 7 + .../src/renderer/components/ForwardRef.ts | 8 ++ libs/horizon/src/renderer/components/Lazy.ts | 66 +++++++++ libs/horizon/src/renderer/components/Memo.ts | 9 ++ .../components/context/CompatibleContext.ts | 101 +++++++++++++ .../renderer/components/context/Context.ts | 31 ++++ .../components/context/CreateContext.ts | 20 +++ .../src/renderer/taskExecutor/BrowserAsync.ts | 66 +++++++++ .../src/renderer/taskExecutor/RenderQueue.ts | 68 +++++++++ .../src/renderer/taskExecutor/TaskExecutor.ts | 135 ++++++++++++++++++ .../src/renderer/taskExecutor/TaskQueue.ts | 58 ++++++++ 13 files changed, 609 insertions(+) create mode 100644 libs/horizon/src/renderer/components/BaseClassComponent.ts create mode 100644 libs/horizon/src/renderer/components/CreatePortal.ts create mode 100644 libs/horizon/src/renderer/components/CreateRef.ts create mode 100644 libs/horizon/src/renderer/components/ForwardRef.ts create mode 100644 libs/horizon/src/renderer/components/Lazy.ts create mode 100644 libs/horizon/src/renderer/components/Memo.ts create mode 100644 libs/horizon/src/renderer/components/context/CompatibleContext.ts create mode 100644 libs/horizon/src/renderer/components/context/Context.ts create mode 100644 libs/horizon/src/renderer/components/context/CreateContext.ts create mode 100644 libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts create mode 100644 libs/horizon/src/renderer/taskExecutor/RenderQueue.ts create mode 100644 libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts create mode 100644 libs/horizon/src/renderer/taskExecutor/TaskQueue.ts diff --git a/libs/horizon/src/renderer/components/BaseClassComponent.ts b/libs/horizon/src/renderer/components/BaseClassComponent.ts new file mode 100644 index 00000000..12dab04e --- /dev/null +++ b/libs/horizon/src/renderer/components/BaseClassComponent.ts @@ -0,0 +1,25 @@ +/** + * Component的api setState和forceUpdate在实例生成阶段实现 + */ +class Component { + props; + context; + + constructor(props, context) { + this.props = props; + this.context = context; + } +} + +// 兼容三方件 react-lifecycles-compat,它会读取 isReactComponent 属性值,不添加会导致 eview-ui 官网白屏 +Component.prototype.isReactComponent = true; +/** + * 支持PureComponent + */ +class PureComponent extends Component { + constructor(props, context) { + super(props, context); + } +} + +export { Component, PureComponent }; diff --git a/libs/horizon/src/renderer/components/CreatePortal.ts b/libs/horizon/src/renderer/components/CreatePortal.ts new file mode 100644 index 00000000..3bdb4b17 --- /dev/null +++ b/libs/horizon/src/renderer/components/CreatePortal.ts @@ -0,0 +1,15 @@ +import {TYPE_PORTAL} from '../utils/elementType'; +import type {PortalType} from '../Types'; + +export function createPortal( + children: any, + outerDom: any, + key: string = null, +): PortalType { + return { + vtype: TYPE_PORTAL, + key: key == null ? null : '' + key, + children, + outerDom, + }; +} diff --git a/libs/horizon/src/renderer/components/CreateRef.ts b/libs/horizon/src/renderer/components/CreateRef.ts new file mode 100644 index 00000000..38496640 --- /dev/null +++ b/libs/horizon/src/renderer/components/CreateRef.ts @@ -0,0 +1,7 @@ +import type {RefType} from '../Types'; + +export function createRef(): RefType { + return { + current: null, + }; +} diff --git a/libs/horizon/src/renderer/components/ForwardRef.ts b/libs/horizon/src/renderer/components/ForwardRef.ts new file mode 100644 index 00000000..5baef940 --- /dev/null +++ b/libs/horizon/src/renderer/components/ForwardRef.ts @@ -0,0 +1,8 @@ +import {TYPE_FORWARD_REF} from '../utils/elementType'; + +export function forwardRef(render: Function) { + return { + vtype: TYPE_FORWARD_REF, + render, + }; +} diff --git a/libs/horizon/src/renderer/components/Lazy.ts b/libs/horizon/src/renderer/components/Lazy.ts new file mode 100644 index 00000000..55a276c5 --- /dev/null +++ b/libs/horizon/src/renderer/components/Lazy.ts @@ -0,0 +1,66 @@ +import type {PromiseType} from '../Types'; + +import {TYPE_LAZY} from '../utils/elementType'; + +enum LayStatus { + UnProcessed = 'UnProcessed', + Pending = 'Pending', + Fulfilled = 'Fulfilled', + Rejected = 'Rejected', +} + +type LazyContent = { + _status: string, + _value: () => PromiseType<{default: T}> | PromiseType | T | any +}; + +export type LazyComponent = { + vtype: Symbol | number, + _content: P, + _load: (content: P) => T, +}; + +// lazyContent随着阶段改变,_value改变: +// 1. 未初始化 -> promiseCtor: () => promise +// 2. pending -> promise +// 3. fulfilled -> module +// 4. rejected -> error +function lazyLoader(lazyContent: LazyContent): any { + if (lazyContent._status === LayStatus.UnProcessed) { + // 执行动态导入组件import + const promise = lazyContent._value(); + lazyContent._status = LayStatus.Pending; + lazyContent._value = promise; + promise.then( + module => { + if (lazyContent._status === LayStatus.Pending) { + const defaultExport = module.default; + lazyContent._status = LayStatus.Fulfilled; + lazyContent._value = defaultExport; + } + }, + error => { + if (lazyContent._status === LayStatus.Pending) { + lazyContent._status = LayStatus.Rejected; + lazyContent._value = error; + } + }, + ); + } + if (lazyContent._status === LayStatus.Fulfilled) { + return lazyContent._value; + } else { + throw lazyContent._value; + } +} + +export function lazy(promiseCtor: () => PromiseType<{default: T}>): LazyComponent> { + return { + vtype: TYPE_LAZY, + _content: { + _status: LayStatus.UnProcessed, + _value: promiseCtor, + }, + _load: lazyLoader, + }; +} diff --git a/libs/horizon/src/renderer/components/Memo.ts b/libs/horizon/src/renderer/components/Memo.ts new file mode 100644 index 00000000..90d3c140 --- /dev/null +++ b/libs/horizon/src/renderer/components/Memo.ts @@ -0,0 +1,9 @@ +import {TYPE_MEMO} from '../utils/elementType'; + +export function memo(type, compare?: (oldProps: Props, newProps: Props) => boolean) { + return { + vtype: TYPE_MEMO, + type: type, + compare: compare === undefined ? null : compare, + }; +} diff --git a/libs/horizon/src/renderer/components/context/CompatibleContext.ts b/libs/horizon/src/renderer/components/context/CompatibleContext.ts new file mode 100644 index 00000000..0df1f5a2 --- /dev/null +++ b/libs/horizon/src/renderer/components/context/CompatibleContext.ts @@ -0,0 +1,101 @@ +import type {VNode} from '../../Types'; + +import { + setOldContextCtx, + setContextChangeCtx, + getOldContextCtx, + resetOldContextCtx, + resetContextChangeCtx, + setOldPreviousContextCtx, + getOldPreviousContextCtx, + setVNodeOldContext, + getVNodeOldContext, + setVNodeOldPreviousContext, + getVNodeOldPreviousContext, +} from '../../ContextSaver'; + +const emptyObject = {}; + +// 判断是否是过时的context的提供者 +export function isOldProvider(comp: Function): boolean { + // @ts-ignore + const childContextTypes = comp.childContextTypes; + return childContextTypes !== null && childContextTypes !== undefined; +} + +// 判断是否是过时的context的消费者 +export function isOldConsumer(comp: Function): boolean { + // @ts-ignore + const contextTypes = comp.contextTypes; + return contextTypes !== null && contextTypes !== undefined; +} + +// 如果是旧版context提供者,则缓存两个全局变量,上一个提供者提供的context和当前提供者提供的context +export function cacheOldCtx(processing: VNode, hasOldContext: any): void { + // 每一个context提供者都会更新ctxOldContext + if (hasOldContext) { + setOldPreviousContextCtx(getOldContextCtx()); + + const vNodeContext = getVNodeOldContext(processing) || emptyObject; + setOldContextCtx(processing, vNodeContext); + } +} + +// 获取当前组件可以消费的context +export function getOldContext(processing: VNode, clazz: Function, ifProvider: boolean) { + const type = processing.type; + // 不是context消费者, 则直接返回空对象 + if (!isOldConsumer(type)) { + return emptyObject; + } + + // 当组件既是提供者,也是消费者时,取上一个context,不能直接取最新context,因为已经被更新为当前组件的context; + // 当组件只是消费者时,则取最新context + const parentContext = ((ifProvider && isOldProvider(clazz))) ? + getOldPreviousContextCtx() : + getOldContextCtx(); + + // 除非父级context更改,否则不需要重新创建子context,直接取对应节点上存的。 + if (getVNodeOldPreviousContext(processing) === parentContext) { + return getVNodeOldContext(processing); + } + + // 从父的context中取出子定义的context + const context = {}; + for (const key in type.contextTypes) { + context[key] = parentContext[key]; + } + + // 缓存当前组件的context,最近祖先传递下来context,当前可消费的context + setVNodeOldPreviousContext(processing, parentContext); + setVNodeOldContext(processing, context); + + return context; +} + +// 重置context +export function resetOldCtx(vNode: VNode): void { + resetOldContextCtx(vNode); + resetContextChangeCtx(vNode); +} + +// 当前组件是提供者,则需要合并祖先context和当前组件提供的context +function handleContext(vNode: VNode, parentContext: Object): Object { + const instance = vNode.realNode; + + if (typeof instance.getChildContext !== 'function') { + return parentContext; + } + + // 合并祖先提供的context和当前组件提供的context + return {...parentContext, ...instance.getChildContext()}; +} + +// 当前组件是context提供者,更新时,需要合并祖先context和当前组件提供的context +export function updateOldContext(vNode: VNode): void { + const ctx = handleContext(vNode, getOldPreviousContextCtx()); + // 更新context,给子组件用的context + setOldContextCtx(vNode, ctx); + // 标记更改 + setContextChangeCtx(vNode, true); +} diff --git a/libs/horizon/src/renderer/components/context/Context.ts b/libs/horizon/src/renderer/components/context/Context.ts new file mode 100644 index 00000000..bcbc4f83 --- /dev/null +++ b/libs/horizon/src/renderer/components/context/Context.ts @@ -0,0 +1,31 @@ +import type {VNode, ContextType} from '../../Types'; +import {getHookStage} from '../../hooks/HookStage'; +import {throwNotInFuncError} from '../../hooks/BaseHook'; + +// 重置依赖 +export function resetDepContexts(vNode: VNode): void { + vNode.depContexts = []; +} + +// 收集依赖 +function collectDeps(vNode: VNode, context: ContextType) { + const depContexts = vNode.depContexts; + if (!depContexts.length) { + vNode.isDepContextChange = false; + } + if (!depContexts.includes(context)) { + depContexts.push(context); + } +} + +export function getNewContext(vNode: VNode, ctx: ContextType, isUseContext: boolean = false): T { + // 如果来自于useContext,则需要在函数组件中调用 + if (isUseContext && getHookStage() === null) { + throwNotInFuncError(); + } + + // 调用到这个方法,说明当前vNode依赖了这个context,所以需要收集起来 + collectDeps(vNode, ctx); + + return ctx.value; +} diff --git a/libs/horizon/src/renderer/components/context/CreateContext.ts b/libs/horizon/src/renderer/components/context/CreateContext.ts new file mode 100644 index 00000000..45d97039 --- /dev/null +++ b/libs/horizon/src/renderer/components/context/CreateContext.ts @@ -0,0 +1,20 @@ +import type {ContextType} from '../../Types'; +import {TYPE_PROVIDER, TYPE_CONTEXT} from '../../utils/elementType'; + +export function createContext(val: T): ContextType { + const context: ContextType = { + vtype: TYPE_CONTEXT, + value: val, + Provider: null, + Consumer: null, + }; + + context.Provider = { + vtype: TYPE_PROVIDER, + _context: context, + }; + + context.Consumer = context; + + return context; +} diff --git a/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts b/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts new file mode 100644 index 00000000..185ec51d --- /dev/null +++ b/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts @@ -0,0 +1,66 @@ +/** + * 浏览器相关实现 + */ + +export let now; + +if (typeof performance === 'object' && typeof performance.now === 'function') { + const localPerformance = performance; + now = () => localPerformance.now(); +} else { + const localDate = Date; + const initialTime = localDate.now(); + now = () => localDate.now() - initialTime; +} + +let isMessageLoopRunning = false; +let browserCallback = null; + +// 默认每次只运行5ms +const runTime = 5; +let deadline = 0; + +export function isOverTime() { + return now() >= deadline; +} + +// 1、设置deadline;2、回调TaskExecutor传过来的browserCallback +const callRenderTasks = () => { + if (browserCallback == null) { + return; + } + + const currentTime = now(); + // 计算deadline + deadline = currentTime + runTime; + try { + // 执行callback + const hasMoreTask = browserCallback( + currentTime, + ); + + if (!hasMoreTask) { // 没有更多task + isMessageLoopRunning = false; + browserCallback = null; + } else { + // 还有task,继续调用 + port.postMessage(null); + } + } catch (error) { + port.postMessage(null); + throw error; + } +}; + +const channel = new MessageChannel(); +const port = channel.port2; +channel.port1.onmessage = callRenderTasks; + +export function requestBrowserCallback(callback) { + browserCallback = callback; + + if (!isMessageLoopRunning) { + isMessageLoopRunning = true; + port.postMessage(null); + } +} diff --git a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts new file mode 100644 index 00000000..125f0a17 --- /dev/null +++ b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts @@ -0,0 +1,68 @@ +/** + * 利用TaskExecutor的异步任务,封装一个renderQueue来执行同步的渲染callback + */ + +import {runAsync, cancelTask, ImmediatePriority} from './TaskExecutor'; + +type RenderCallback = () => RenderCallback | null; + +let renderQueue: Array | null = null; +// 保存正在等待的异步Task,可以用于取消 +let callingQueueTask: any | null = null; +// 防止重入 +let isCallingRenderQueue = false; + +export function pushRenderCallback(callback: RenderCallback) { + if (renderQueue === null) { + renderQueue = [callback]; + // 高优先级的异步调度 + callingQueueTask = runAsync(callRenderQueue, ImmediatePriority); + } else { + // 不需要调度,在syncQueue创建的时候已经调度了 + renderQueue.push(callback); + } + + // 返回一个空对象,用于区别null + return {}; +} + +export function callRenderQueueImmediate() { + if (callingQueueTask !== null) { + // 取消异步调度 + cancelTask(callingQueueTask); + callingQueueTask = null; + } + + callRenderQueue(); +} + +// 执行render回调 +function callRenderQueue() { + if (!isCallingRenderQueue && renderQueue !== null) { + // 防止重入 + isCallingRenderQueue = true; + + let i = 0; + try { + for (; i < renderQueue.length; i++) { + let callback = renderQueue[i]; + do { + callback = callback(); + } while (callback !== null); + } + renderQueue = null; + } catch (error) { + // 如果有异常抛出,请将剩余的回调留在队列中 + if (renderQueue !== null) { + renderQueue = renderQueue.slice(i + 1); + } + + // 在下一个异步中再调用 + runAsync(callRenderQueueImmediate, ImmediatePriority); + + throw error; + } finally { + isCallingRenderQueue = false; + } + } +} diff --git a/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts b/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts new file mode 100644 index 00000000..49e0c98d --- /dev/null +++ b/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts @@ -0,0 +1,135 @@ +/** + * 调度器的核心实现 + */ + +import { + requestBrowserCallback, + isOverTime, + now, +} from './BrowserAsync'; + +import {add, shift, first} from './TaskQueue'; + +const ImmediatePriority = 1; +const NormalPriority = 10; + +// 用于控制插入任务的顺序 +let idCounter = 1; + +let currentPriorityLevel = NormalPriority; + +// 正在执行task +let isProcessing = false; + +// 调度中,等待浏览器回调 +let isScheduling = false; + +function runSync(callback, priorityLevel = NormalPriority) { + const previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + + try { + return callback(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } +} + +function runAsync(callback, priorityLevel= NormalPriority ) { + let timeout; + switch (priorityLevel) { + case ImmediatePriority: + timeout = -1; + break; + case NormalPriority: + default: + timeout = 5000; + break; + } + + const task = { + id: idCounter++, + callback, + priorityLevel, + expirationTime: now() + timeout, + }; + + add(task); + + if (!isScheduling && !isProcessing) { + isScheduling = true; + requestBrowserCallback(callTasks); + } + + return task; +} + +function callTasks(initialTime) { + isScheduling = false; + isProcessing = true; + + let task = null; + const previousPriorityLevel = currentPriorityLevel; + try { + let currentTime = initialTime; + task = first(); + + // 循环执行task + while (task !== null) { + if ( + task.expirationTime > currentTime && + isOverTime() + ) { + // 没到deadline + break; + } + + const callback = task.callback; + if (typeof callback === 'function') { + task.callback = null; + currentPriorityLevel = task.priorityLevel; + const didUserCallbackTimeout = task.expirationTime <= currentTime; + + const continuationCallback = callback(didUserCallbackTimeout); + currentTime = now(); + // 执行callback返回函数,重置callback + if (typeof continuationCallback === 'function') { + task.callback = continuationCallback; + } else { + if (task === first()) { + shift(); + } + } + } else { + shift(); + } + + task = first(); + } + + // 返回是否还有任务,如果有,说明是被中断了 + return task !== null; + } finally { + task = null; + currentPriorityLevel = previousPriorityLevel; + isProcessing = false; + } +} + +function cancelTask(task) { + task.callback = null; +} + +function getCurrentPriorityLevel() { + return currentPriorityLevel; +} + +export { + ImmediatePriority, + NormalPriority, + runSync, + runAsync, + cancelTask, + getCurrentPriorityLevel, + now, +}; diff --git a/libs/horizon/src/renderer/taskExecutor/TaskQueue.ts b/libs/horizon/src/renderer/taskExecutor/TaskQueue.ts new file mode 100644 index 00000000..708e9bdf --- /dev/null +++ b/libs/horizon/src/renderer/taskExecutor/TaskQueue.ts @@ -0,0 +1,58 @@ +/** + * 任务列表的实现 + */ + +type Queue = Array; +type Node = { + id: number; + expirationTime: number; +}; + +// 任务队列 +const taskQueue: Queue = []; + +export function add(node: Node): void { + // 查找第一个大于等于 value 的下标,都比 value 小则返回 -1 + const idx = getBiggerIdx(node); + + if (idx === 0) { + taskQueue.unshift(node); + } else if (idx === -1) { + taskQueue.push(node); + } else { + taskQueue.splice(idx, 0, node); + } +} + +// 二分法查找第一个大于等于 value 的下标,都比 value 小则返回 -1,时间复杂度O(logn) +function getBiggerIdx(node: Node) { + let left = 0; + let right = taskQueue.length - 1; + + while (left <= right) { + const middle = left + ((right - left) >> 1); + + if (compare(taskQueue[middle], node) > 0) + right = middle - 1; + else + left = middle + 1; + } + + return (left < taskQueue.length) ? left : -1; +} + +export function first(): Node | null { + const val = taskQueue[0]; + return val !== undefined ? val : null; +} + +export function shift(): Node | null { + const val = taskQueue.shift(); + return val !== undefined ? val : null; +} + +function compare(a: Node, b: Node) { + // 优先先用index排序,其次用id + const diff = a.expirationTime - b.expirationTime; + return diff !== 0 ? diff : a.id - b.id; +} From 6b719e793bf6c12099cea90454a51fda0a669a7e Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 23 Dec 2021 09:59:28 +0800 Subject: [PATCH 2/3] Match-id-2707e0365eead012bcfc2abc205c21065e350060 --- libs/horizon/src/renderer/hooks/BaseHook.ts | 71 +++++++++ .../src/renderer/hooks/EffectConstant.js | 6 + .../src/renderer/hooks/HookExternal.ts | 76 ++++++++++ libs/horizon/src/renderer/hooks/HookMain.ts | 78 ++++++++++ .../horizon/src/renderer/hooks/HookMapping.ts | 21 +++ libs/horizon/src/renderer/hooks/HookStage.ts | 15 ++ libs/horizon/src/renderer/hooks/HookType.ts | 46 ++++++ .../src/renderer/hooks/UseCallbackHook.ts | 32 ++++ .../src/renderer/hooks/UseEffectHook.ts | 83 +++++++++++ .../src/renderer/hooks/UseImperativeHook.ts | 42 ++++++ .../horizon/src/renderer/hooks/UseMemoHook.ts | 35 +++++ .../src/renderer/hooks/UseReducerHook.ts | 140 ++++++++++++++++++ libs/horizon/src/renderer/hooks/UseRefHook.ts | 20 +++ .../src/renderer/hooks/UseStateHook.ts | 11 ++ scripts/gen3rdLib.js | 34 +++++ scripts/webpack/webpack.base.js | 27 ++++ scripts/webpack/webpack.config.js | 4 + scripts/webpack/webpack.dev.js | 43 ++++++ scripts/webpack/webpack.pro.js | 59 ++++++++ 19 files changed, 843 insertions(+) create mode 100644 libs/horizon/src/renderer/hooks/BaseHook.ts create mode 100644 libs/horizon/src/renderer/hooks/EffectConstant.js create mode 100644 libs/horizon/src/renderer/hooks/HookExternal.ts create mode 100644 libs/horizon/src/renderer/hooks/HookMain.ts create mode 100644 libs/horizon/src/renderer/hooks/HookMapping.ts create mode 100644 libs/horizon/src/renderer/hooks/HookStage.ts create mode 100644 libs/horizon/src/renderer/hooks/HookType.ts create mode 100644 libs/horizon/src/renderer/hooks/UseCallbackHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseEffectHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseImperativeHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseMemoHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseReducerHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseRefHook.ts create mode 100644 libs/horizon/src/renderer/hooks/UseStateHook.ts create mode 100644 scripts/gen3rdLib.js create mode 100644 scripts/webpack/webpack.base.js create mode 100644 scripts/webpack/webpack.config.js create mode 100644 scripts/webpack/webpack.dev.js create mode 100644 scripts/webpack/webpack.pro.js diff --git a/libs/horizon/src/renderer/hooks/BaseHook.ts b/libs/horizon/src/renderer/hooks/BaseHook.ts new file mode 100644 index 00000000..ffbd3e6a --- /dev/null +++ b/libs/horizon/src/renderer/hooks/BaseHook.ts @@ -0,0 +1,71 @@ +import type {VNode} from '../Types'; +import type {Hook} from './HookType'; + +let processingVNode: VNode = null; + +let activatedHook: Hook | null = null; + +// 当前hook函数对应的hook对象 +let currentHook: Hook | null = null; + +export function getProcessingVNode() { + return processingVNode; +} + +export function setProcessingVNode(vNode: VNode) { + processingVNode = vNode; +} + +export function getActivatedHook() { + return activatedHook; +} + +export function setActivatedHook(hook: Hook) { + activatedHook = hook; +} + +export function setCurrentHook(hook: Hook) { + currentHook = hook; +} + +export function throwNotInFuncError() { + throw Error( + 'Hooks should be used inside function component.', + ); +} + +// 新建一个hook,并放到vNode.hooks中 +export function createHook(state: any = null): Hook { + const newHook: Hook = { + state: state, + hIndex: processingVNode.hooks.length, + }; + + currentHook = newHook; + processingVNode.hooks.push(newHook); + + return currentHook; +} + +export function getNextHook(hook: Hook, vNode: VNode) { + return vNode.hooks[hook.hIndex + 1] || null; +} + +// 获取当前hook函数对应的hook对象。 +// processing中的hook和activated中的hook,需要同时往前走, +// 原因:1.比对hook的数量有没有变化(非必要);2.从activated中的hook获取removeEffect +export function getCurrentHook(): Hook { + currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode) : (processingVNode.hooks[0] || null); + const activated = processingVNode.twins; + activatedHook = activatedHook !== null ? getNextHook(activatedHook, activated) : ((activated && activated.hooks[0]) || null); + + if (currentHook === null) { + if (activatedHook === null) { + throw Error('Hooks are more than expected, please check whether the hook is written in the condition.'); + } + + createHook(activatedHook.state); + } + + return currentHook; +} diff --git a/libs/horizon/src/renderer/hooks/EffectConstant.js b/libs/horizon/src/renderer/hooks/EffectConstant.js new file mode 100644 index 00000000..ba459e76 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/EffectConstant.js @@ -0,0 +1,6 @@ +export const EffectConstant = { + NoEffect: 0, + DepsChange: 1, // dependence发生了改变 + LayoutEffect: 2, // 同步触发的effect + Effect: 4, // 异步触发的effect +}; diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/horizon/src/renderer/hooks/HookExternal.ts new file mode 100644 index 00000000..fc2805ab --- /dev/null +++ b/libs/horizon/src/renderer/hooks/HookExternal.ts @@ -0,0 +1,76 @@ +import type {ContextType} from '../Types'; + +import hookMapping from './HookMapping'; +import {useRefImpl} from './UseRefHook'; +import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook'; +import {useCallbackImpl} from './UseCallbackHook'; +import {useMemoImpl} from './UseMemoHook'; +import {useImperativeHandleImpl} from './UseImperativeHook'; + +const { + UseContextHookMapping, + UseReducerHookMapping, + UseStateHookMapping +} = hookMapping; + +type BasicStateAction = ((S) => S) | S; +type Dispatch = (A) => void; + + +export function useContext( + Context: ContextType, +): T { + return UseContextHookMapping.val.useContext(Context); +} + +export function useState(initialState: (() => S) | S,): [S, Dispatch>] { + return UseStateHookMapping.val.useState(initialState); +} + +export function useReducer( + reducer: (S, A) => S, + initialArg: I, + init?: (I) => S, +): [S, Dispatch] { + return UseReducerHookMapping.val.useReducer(reducer, initialArg, init); +} + +export function useRef(initialValue: T): {current: T} { + return useRefImpl(initialValue); +} + +export function useEffect( + create: () => (() => void) | void, + deps?: Array | null, +): void { + return useEffectImpl(create, deps); +} + +export function useLayoutEffect( + create: () => (() => void) | void, + deps?: Array | null, +): void { + return useLayoutEffectImpl(create, deps); +} + +export function useCallback( + callback: T, + deps?: Array | null, +): T { + return useCallbackImpl(callback, deps); +} + +export function useMemo( + create: () => T, + deps?: Array | null, +): T { + return useMemoImpl(create, deps); +} + +export function useImperativeHandle( + ref: {current: T | null} | ((inst: T | null) => any) | null | void, + create: () => T, + deps?: Array | null, +): void { + return useImperativeHandleImpl(ref, create, deps); +} diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts new file mode 100644 index 00000000..713696d3 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -0,0 +1,78 @@ +import type {VNode} from '../Types'; +import hookMapping from './HookMapping'; + +const { + UseStateHookMapping, + UseReducerHookMapping, + UseContextHookMapping, +} = hookMapping; + +import {getNewContext} from '../components/context/Context'; +import { + getActivatedHook, + getProcessingVNode, + setActivatedHook, + setProcessingVNode, + setCurrentHook, getNextHook +} from './BaseHook'; +import {useStateImpl,} from './UseStateHook'; +import {useReducerImpl,} from './UseReducerHook'; +import {HookStage, setHookStage} from './HookStage'; + +// hook对外入口 +export function exeFunctionHook( + funcComp: (props: Object, arg: Object) => any, + props: Object, + arg: Object, + activated: VNode | null, + processing: VNode, +): any { + // 重置全局变量 + resetGlobalVariable(); + + // 初始化hook实现函数 + initHookMapping(); + + setProcessingVNode(processing); + + processing.hooks = []; + processing.effectList = []; + + // 设置hook阶段 + if (activated === null || !activated.hooks.length) { + setHookStage(HookStage.Init); + } else { + setHookStage(HookStage.Update); + } + + let comp = funcComp(props, arg); + + // 设置hook阶段为null,用于判断hook是否在函数组件中调用 + setHookStage(null); + + // 判断hook是否写在了if条件中,如果在if中会出现数量不对等的情况 + const activatedHook = getActivatedHook(); + if (activatedHook !== null) { + if (getNextHook(getActivatedHook(), activated) !== null) { + throw Error('Hooks are less than expected, please check whether the hook is written in the condition.'); + } + } + + // 重置全局变量 + resetGlobalVariable(); + + return comp; +} + +function resetGlobalVariable() { + setHookStage(null); + setProcessingVNode(null); + setActivatedHook(null); + setCurrentHook(null); +} + +export function initHookMapping() { + UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)}; + UseReducerHookMapping.val = {useReducer: useReducerImpl}; + UseStateHookMapping.val = {useState: useStateImpl}; +} diff --git a/libs/horizon/src/renderer/hooks/HookMapping.ts b/libs/horizon/src/renderer/hooks/HookMapping.ts new file mode 100644 index 00000000..2205d029 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/HookMapping.ts @@ -0,0 +1,21 @@ +/** + * 暂时用于解决测试代码无法运行问题,估计是:测试代码会循环或者重复依赖 + */ + +import type { + UseContextHookType, + UseReducerHookType, + UseStateHookType +} from '../Types'; + +const UseStateHookMapping: {val: (null | UseStateHookType)} = {val: null}; +const UseReducerHookMapping: {val: (null | UseReducerHookType)} = {val: null}; +const UseContextHookMapping: {val: (null | UseContextHookType)} = {val: null}; + +const hookMapping = { + UseStateHookMapping, + UseReducerHookMapping, + UseContextHookMapping, +} + +export default hookMapping; diff --git a/libs/horizon/src/renderer/hooks/HookStage.ts b/libs/horizon/src/renderer/hooks/HookStage.ts new file mode 100644 index 00000000..65a96424 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/HookStage.ts @@ -0,0 +1,15 @@ +// hooks阶段 +export enum HookStage { + Init = 1, + Update = 2, +} + +let hookStage: HookStage = null; + +export function getHookStage() { + return hookStage; +} + +export function setHookStage(phase: HookStage) { + hookStage = phase; +} diff --git a/libs/horizon/src/renderer/hooks/HookType.ts b/libs/horizon/src/renderer/hooks/HookType.ts new file mode 100644 index 00000000..984b5ecd --- /dev/null +++ b/libs/horizon/src/renderer/hooks/HookType.ts @@ -0,0 +1,46 @@ +import {EffectConstant} from './EffectConstant'; +import {VNode} from '../Types'; + +export interface Hook { + state: Reducer | Effect | Memo | CallBack | Ref; + hIndex: number; +} + +export interface Reducer { + stateValue: S | null; + trigger: Trigger | null; + reducer: ((S, A) => S) | null; + updates: Array> | null; + isUseState: boolean; +} + +export type Update = { + action: A; + didCalculated: boolean; + state: S | null; +}; + +export type EffectList = Array | null; + +export type Effect = { + effect: () => (() => void) | void; + removeEffect: (() => void) | void; + dependencies: Array | null; + effectConstant: typeof EffectConstant; +}; + +export type Memo = { + result: V | null; + dependencies: Array | null; +}; + +export type CallBack = { + func: F | null; + dependencies: Array | null; +}; + +export type Ref = { + current: V | null; +}; + +export type Trigger = (A) => void; diff --git a/libs/horizon/src/renderer/hooks/UseCallbackHook.ts b/libs/horizon/src/renderer/hooks/UseCallbackHook.ts new file mode 100644 index 00000000..8545bd62 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseCallbackHook.ts @@ -0,0 +1,32 @@ +import { + createHook, + getCurrentHook, + throwNotInFuncError +} from './BaseHook'; +import {getHookStage, HookStage} from './HookStage'; +import {isArrayEqual} from '../utils/compare'; + +export function useCallbackImpl(func: F, dependencies?: Array | null): F { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + let hook; + const deps = dependencies !== undefined ? dependencies : null; + if (stage === HookStage.Init) { + hook = createHook(); + hook.state = {func, dependencies: deps}; + } else if (stage === HookStage.Update) { + hook = getCurrentHook(); + + const lastState = hook.state; + // 判断dependencies是否相同,不同就不更新state + if (lastState !== null && deps !== null && isArrayEqual(deps, lastState.dependencies)) { + return lastState.func; + } + hook.state = {func, dependencies: deps}; + } + + return func; +} diff --git a/libs/horizon/src/renderer/hooks/UseEffectHook.ts b/libs/horizon/src/renderer/hooks/UseEffectHook.ts new file mode 100644 index 00000000..c777f860 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseEffectHook.ts @@ -0,0 +1,83 @@ +import { + createHook, + getCurrentHook, + getActivatedHook, + getProcessingVNode, throwNotInFuncError +} from './BaseHook'; +import {FlagUtils} from '../vnode/VNodeFlags'; +import {EffectConstant} from './EffectConstant'; +import type {Effect, EffectList} from './HookType'; +import {getHookStage, HookStage} from './HookStage'; +import {isArrayEqual} from '../utils/compare'; + +export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null,): void { + // 异步触发的effect + useEffect(effectFunc, deps, EffectConstant.Effect); +} + +export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { + // 同步触发的effect + useEffect(effectFunc, deps, EffectConstant.LayoutEffect); +} + +function useEffect( + effectFunc: () => (() => void) | void, + deps: Array | void | null, + effectType: number +): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + if (stage === HookStage.Init) { + return useEffectForInit(effectFunc, deps, effectType); + } else if (stage === HookStage.Update) { + return useEffectForUpdate(effectFunc, deps, effectType); + } +} + +export function useEffectForInit(effectFunc, deps, effectType): void { + const hook = createHook(); + const nextDeps = deps !== undefined ? deps : null; + FlagUtils.markUpdate(getProcessingVNode()); + + // 初始阶段,设置DepsChange标记位; 构造EffectList数组,并赋值给state + hook.state = createEffect(effectFunc, undefined, nextDeps, EffectConstant.DepsChange | effectType); +} + +export function useEffectForUpdate(effectFunc, deps, effectType): void { + const hook = getCurrentHook(); + const nextDeps = deps !== undefined ? deps : null; + let removeFunc; + + if (getActivatedHook() !== null) { + const effect = getActivatedHook().state as Effect; + // removeEffect是通过执行effect返回的,所以需要在activated中获取 + removeFunc = effect.removeEffect; + const lastDeps = effect.dependencies; + + // 判断dependencies是否相同,不同就不设置DepsChange标记位 + if (nextDeps !== null && isArrayEqual(nextDeps, lastDeps)) { + hook.state = createEffect(effectFunc, removeFunc, nextDeps, effectType); + return; + } + } + + FlagUtils.markUpdate(getProcessingVNode()); + // 设置DepsChange标记位,构造Effect,并赋值给state + hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType); +} + +function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect { + const effect: Effect = { + effect: effectFunc, + removeEffect: removeFunc, + dependencies: deps, + effectConstant: effectConstant, + }; + + getProcessingVNode().effectList.push(effect); + + return effect; +} diff --git a/libs/horizon/src/renderer/hooks/UseImperativeHook.ts b/libs/horizon/src/renderer/hooks/UseImperativeHook.ts new file mode 100644 index 00000000..9dce6fed --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseImperativeHook.ts @@ -0,0 +1,42 @@ +import {useLayoutEffectImpl} from './UseEffectHook'; +import {getHookStage} from './HookStage'; +import {throwNotInFuncError} from './BaseHook'; +import type {Ref} from './HookType'; + +export function useImperativeHandleImpl( + ref: { current: R | null } | ((any) => any) | null | void, + func: () => R, + dependencies?: Array | null, +): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null; + useLayoutEffectImpl(effectFunc.bind(null, func, ref), params); +} + +function isNotNull(object: any): boolean { + return object !== null && object !== undefined; +} + +function effectFunc( + func: () => R, + ref: Ref | ((any) => any) | null, +): (() => void) | void { + if (typeof ref === 'function') { + const value = func(); + ref(value); + return () => { + ref(null); + }; + } + + if (isNotNull(ref)) { + ref.current = func(); + return () => { + ref.current = null; + }; + } +} diff --git a/libs/horizon/src/renderer/hooks/UseMemoHook.ts b/libs/horizon/src/renderer/hooks/UseMemoHook.ts new file mode 100644 index 00000000..2411872b --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseMemoHook.ts @@ -0,0 +1,35 @@ +import { + createHook, + getCurrentHook, + throwNotInFuncError +} from './BaseHook'; +import {getHookStage, HookStage} from './HookStage'; +import {isArrayEqual} from '../utils/compare'; + +export function useMemoImpl(fun: () => V, deps?: Array | null,): V { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + let hook; + let result; + const nextDeps = deps === undefined ? null : deps; + + if (stage === HookStage.Init) { + hook = createHook(); + result = fun(); + } else if (stage === HookStage.Update) { + hook = getCurrentHook(); + + const lastState = hook.state; + // dependencies相同,不更新state + if (lastState !== null && nextDeps !== null && isArrayEqual(nextDeps, lastState.dependencies)) { + return lastState.result; + } + result = fun(); + } + + hook.state = {result, dependencies: nextDeps}; + return hook.state.result; +} diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts new file mode 100644 index 00000000..844afb0a --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -0,0 +1,140 @@ +import type {Hook, Reducer, Trigger, Update} from './HookType'; +import { + createHook, + getCurrentHook, + getProcessingVNode, + throwNotInFuncError +} from './BaseHook'; +import { + launchUpdateFromVNode +} from '../TreeBuilder'; +import {isSame} from '../utils/compare'; +import {setStateChange} from '../render/FunctionComponent'; +import {getHookStage, HookStage} from './HookStage'; +import type {VNode} from '../Types'; + +export function useReducerImpl(reducer: (S, A) => S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger] { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + if (stage === HookStage.Init) { + return useReducerForInit(reducer, initArg, init, isUseState); + } else if (stage === HookStage.Update) { + // 获取当前的hook + const currentHook = getCurrentHook(); + // 获取currentHook的更新数组 + const currentHookUpdates = (currentHook.state as Reducer).updates; + + return updateReducerHookState(currentHookUpdates, currentHook, reducer); + } +} + +// 构造新的Update数组 +function insertUpdate(action: A, hook: Hook): Update { + const newUpdate: Update = { + action, + state: null, + didCalculated: false, + }; + + let updates = (hook.state as Reducer).updates; + // 更新updates数组,newUpdate添加至数组尾部 + if (updates === null) { + updates = [newUpdate]; + (hook.state as Reducer).updates = updates; + } else { + updates.push(newUpdate); + } + + return newUpdate; +} + +// setState, setReducer触发函数 +export function TriggerAction(vNode: VNode, hook: Hook, action: A) { + const newUpdate = insertUpdate(action, hook); + const twins = vNode.twins; + + // 判断是否需要刷新 + if (!vNode.shouldUpdate && (twins === null || !twins.shouldUpdate)) { + const reducerObj = hook.state as Reducer; + const { stateValue, reducer } = reducerObj; + + // 在进入render阶段前reducer没有变化,可以复用state值,提升性能 + newUpdate.state = reducer(stateValue, action); + // 标记为已经计算过,不需要重新计算了 + newUpdate.didCalculated = true; + + if (isSame(newUpdate.state, stateValue)) { + return; + } + } + + // 执行vNode节点渲染 + launchUpdateFromVNode(vNode); +} + +export function useReducerForInit(reducer, initArg, init, isUseState?: boolean): [S, Trigger] { + // 计算初始stateValue + let stateValue; + if (typeof initArg === 'function') { + stateValue = initArg(); + } else if (typeof init === 'function') { + stateValue = init(initArg); + } else { + stateValue = initArg; + } + + const hook = createHook(); + // 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState} + hook.state = { + stateValue: stateValue, + trigger: TriggerAction.bind(null, getProcessingVNode(), hook), + reducer, + updates: null, + isUseState + } as Reducer; + + return [hook.state.stateValue, hook.state.trigger]; +} + +// 更新hook.state +function updateReducerHookState(currentHookUpdates, currentHook, reducer): [S, Trigger] { + if (currentHookUpdates !== null) { + // 循环遍历更新数组,计算新的状态值 + const newState = calculateNewState(currentHookUpdates, currentHook, reducer); + if (!isSame(newState, currentHook.state.stateValue)) { + setStateChange(true); + } + + // 更新hook对象状态值 + currentHook.state.stateValue = newState; + // 重置更新数组为null + currentHook.state.updates = null; + } + + currentHook.state.reducer = reducer; + return [currentHook.state.stateValue, currentHook.state.trigger]; +} + +// 计算stateValue值 +function calculateNewState(currentHookUpdates: Array>, currentHook, reducer: (S, A) => S) { + let reducerObj = currentHook.state; + let state = reducerObj.stateValue; + + // 循环遍历更新数组,计算新的状态值 + currentHookUpdates.forEach(update => { + // 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState + if (update.didCalculated && reducerObj.isUseState) { + state = update.state; + } else { + const action = update.action; + state = reducer(state, action); + } + }); + + return state; +} + + diff --git a/libs/horizon/src/renderer/hooks/UseRefHook.ts b/libs/horizon/src/renderer/hooks/UseRefHook.ts new file mode 100644 index 00000000..754a16d2 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseRefHook.ts @@ -0,0 +1,20 @@ +import {createHook, getCurrentHook, throwNotInFuncError} from './BaseHook'; +import {getHookStage, HookStage} from './HookStage'; +import type {Ref} from './HookType'; + +export function useRefImpl(value: V): Ref { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + let hook; + if (stage === HookStage.Init) { + hook = createHook(); + hook.state = {current: value}; + } else if (stage === HookStage.Update) { + hook = getCurrentHook(); + } + + return hook.state; +} diff --git a/libs/horizon/src/renderer/hooks/UseStateHook.ts b/libs/horizon/src/renderer/hooks/UseStateHook.ts new file mode 100644 index 00000000..4a4b7136 --- /dev/null +++ b/libs/horizon/src/renderer/hooks/UseStateHook.ts @@ -0,0 +1,11 @@ +import type {Trigger} from './HookType'; +import {useReducerImpl} from './UseReducerHook'; + +function defaultReducer(state: S, action: ((S) => S) | S): S { + // @ts-ignore + return typeof action === 'function' ? action(state) : action; +} + +export function useStateImpl(initArg: (() => S) | S): [S, Trigger<((S) => S) | S>] { + return useReducerImpl(defaultReducer, initArg, undefined, true); +} diff --git a/scripts/gen3rdLib.js b/scripts/gen3rdLib.js new file mode 100644 index 00000000..66b54d69 --- /dev/null +++ b/scripts/gen3rdLib.js @@ -0,0 +1,34 @@ +'use strict' +const ejs = require('ejs'); +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); +const console = require('console'); +const rimRaf = require('rimRaf'); +const argv = require('minimist')(process.argv.slice(2)); + +const libPathPrefix = '../build'; +const suffix = argv.dev ? 'development.js' : 'production.js'; + +const readLib = (lib) => { + const libName = lib.split('.')[0]; + const libPath = path.resolve(__dirname, `${libPathPrefix}/${libName}/umd/${lib}`); + if (fs.existsSync(libPath)) { + return fs.readFileSync(libPath,'utf-8'); + } else { + console.log(chalk.red(`Error: "${libPath}" 文件不存在\n先运行 npm run build`)) + } +}; + +ejs.renderFile(path.resolve(__dirname, './template.ejs'), { + Horizon: readLib(`horizon.${suffix}`), +}, null, function(err, result) { + const common3rdLibPath = path.resolve(__dirname, `${libPathPrefix}/common3rdlib.min.js`) + rimRaf(common3rdLibPath, e => { + if (e) { + console.log(e) + } + fs.writeFileSync(common3rdLibPath, result); + console.log(chalk.green(`成功生成: ${common3rdLibPath}`)) + }) +}); diff --git a/scripts/webpack/webpack.base.js b/scripts/webpack/webpack.base.js new file mode 100644 index 00000000..4895ebda --- /dev/null +++ b/scripts/webpack/webpack.base.js @@ -0,0 +1,27 @@ +'use strict'; +const path = require('path'); + +const libPath = path.join(__dirname, '../../libs'); +const baseConfig = { + entry: path.resolve(libPath, 'index.ts'), + module: { + rules: [ + { + test: /\.(js)|ts$/, + exclude: /node_modules/, + use: [ + { loader: 'babel-loader' } + ] + }, + ] + }, + resolve: { + extensions: ['.js', '.ts'], + alias: { + 'horizon-external': path.join(libPath, './horizon-external'), + 'horizon': path.join(libPath, './horizon'), + } + }, +}; + +module.exports = baseConfig; diff --git a/scripts/webpack/webpack.config.js b/scripts/webpack/webpack.config.js new file mode 100644 index 00000000..ca762982 --- /dev/null +++ b/scripts/webpack/webpack.config.js @@ -0,0 +1,4 @@ +const dev = require('./webpack.dev'); +const pro = require('./webpack.pro'); + +module.exports = [...dev, ...pro]; diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js new file mode 100644 index 00000000..5ab3cd33 --- /dev/null +++ b/scripts/webpack/webpack.dev.js @@ -0,0 +1,43 @@ +const webpack = require('webpack'); +const ESLintPlugin = require('eslint-webpack-plugin'); +const baseConfig = require('./webpack.base'); +const path = require('path'); + +const mode = 'development'; +const devtool = 'inline-source-map'; +const filename = 'horizon.development.js'; + +const plugins = [ + new ESLintPlugin({fix: true}), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"development"', + __DEV__: 'true', + }), +]; + +const umd = { + ...baseConfig, + mode, + devtool, + output: { + path: path.resolve(__dirname, '../../build/horizon/umd'), + filename, + libraryTarget: 'umd', + library: 'Horizon', + }, + plugins, +}; + +const cjs = { + ...baseConfig, + mode, + devtool, + output: { + path: path.resolve(__dirname, '../../build/horizon/cjs'), + filename, + libraryTarget: 'commonjs', + }, + plugins, +}; + +module.exports = [umd, cjs]; diff --git a/scripts/webpack/webpack.pro.js b/scripts/webpack/webpack.pro.js new file mode 100644 index 00000000..cec1e307 --- /dev/null +++ b/scripts/webpack/webpack.pro.js @@ -0,0 +1,59 @@ +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const baseConfig = require('./webpack.base'); +const path = require('path'); + +const mode = 'production'; +const devtool = 'none'; +const filename = 'horizon.production.js'; + +const plugins = [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"production"', + __DEV__: 'false', + }), +]; + +const proBaseConfig = { + ...baseConfig, + mode, + devtool, + plugins, + optimization: { + minimize: true + }, +}; + +const umd = { + ...proBaseConfig, + output: { + path: path.resolve(__dirname, '../../build/horizon/umd'), + filename, + libraryTarget: 'umd', + library: 'Horizon', + }, +}; + +const cjs = { + ...proBaseConfig, + output: { + path: path.resolve(__dirname, '../../build/horizon/cjs'), + filename, + libraryTarget: 'commonjs', + }, + plugins: [ + ...plugins, + new CopyWebpackPlugin([ + { + from: path.join(__dirname, '../../libs/index.js'), + to: path.join(__dirname, '../../build/horizon/index.js'), + }, + { + from: path.join(__dirname, '../../libs/package.json'), + to: path.join(__dirname, '../../build/horizon/package.json'), + } + ]) + ] +}; + +module.exports = [umd, cjs]; From 2c1bd56e0488e0d673d365c8c8c75fab8b6cb083 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 23 Dec 2021 10:11:27 +0800 Subject: [PATCH 3/3] Match-id-3fd0890a9ad3e60aa197810f957dde415bbaa17a --- scripts/template.ejs | 742 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 scripts/template.ejs diff --git a/scripts/template.ejs b/scripts/template.ejs new file mode 100644 index 00000000..7ead2d31 --- /dev/null +++ b/scripts/template.ejs @@ -0,0 +1,742 @@ +if(!window["Horizon"]) { + <%- Horizon %> +} + + +!function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.ie=r():t.ie=r()}(window,(function(){return function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,r){if(1&r&&(t=e(t)),8&r)return t;if(4&r&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&r&&"string"!=typeof t)for(var o in t)e.d(n,o,function(r){return t[r]}.bind(null,o));return n},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=159)}([function(t,r,e){var n=e(2),o=e(13).f,i=e(16),a=e(14),u=e(84),c=e(113),f=e(55);t.exports=function(t,r){var e,s,l,h,p,v=t.target,d=t.global,g=t.stat;if(e=d?n:g?n[v]||u(v,{}):(n[v]||{}).prototype)for(s in r){if(h=r[s],l=t.noTargetGet?(p=o(e,s))&&p.value:e[s],!f(d?s:v+(g?".":"#")+s,t.forced)&&void 0!==l){if(typeof h==typeof l)continue;c(h,l)}(t.sham||l&&l.sham)&&i(h,"sham",!0),a(e,s,h,t)}}},function(t,r){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,r,e){(function(r){var e=function(t){return t&&t.Math==Math&&t};t.exports=e("object"==typeof globalThis&&globalThis)||e("object"==typeof window&&window)||e("object"==typeof self&&self)||e("object"==typeof r&&r)||Function("return this")()}).call(this,e(163))},function(t,r){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,r,e){var n=e(3);t.exports=function(t){if(!n(t))throw TypeError(String(t)+" is not an object");return t}},function(t,r,e){var n=e(1);t.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(t,r,e){var n=e(2),o=e(86),i=e(11),a=e(51),u=e(90),c=e(115),f=o("wks"),s=n.Symbol,l=c?s:s&&s.withoutSetter||a;t.exports=function(t){return i(f,t)||(u&&i(s,t)?f[t]=s[t]:f[t]=l("Symbol."+t)),f[t]}},function(t,r,e){var n=e(27),o=Math.min;t.exports=function(t){return t>0?o(n(t),9007199254740991):0}},function(t,r,e){"use strict";var n,o=e(108),i=e(5),a=e(2),u=e(3),c=e(11),f=e(61),s=e(16),l=e(14),h=e(9).f,p=e(30),v=e(45),d=e(6),g=e(51),y=a.Int8Array,m=y&&y.prototype,x=a.Uint8ClampedArray,b=x&&x.prototype,w=y&&p(y),S=m&&p(m),E=Object.prototype,A=E.isPrototypeOf,O=d("toStringTag"),T=g("TYPED_ARRAY_TAG"),R=o&&!!v&&"Opera"!==f(a.opera),I=!1,j={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},M=function(t){return u(t)&&c(j,f(t))};for(n in j)a[n]||(R=!1);if((!R||"function"!=typeof w||w===Function.prototype)&&(w=function(){throw TypeError("Incorrect invocation")},R))for(n in j)a[n]&&v(a[n],w);if((!R||!S||S===E)&&(S=w.prototype,R))for(n in j)a[n]&&v(a[n].prototype,S);if(R&&p(b)!==S&&v(b,S),i&&!c(S,O))for(n in I=!0,h(S,O,{get:function(){return u(this)?this[T]:void 0}}),j)a[n]&&s(a[n],T,n);t.exports={NATIVE_ARRAY_BUFFER_VIEWS:R,TYPED_ARRAY_TAG:I&&T,aTypedArray:function(t){if(M(t))return t;throw TypeError("Target is not a typed array")},aTypedArrayConstructor:function(t){if(v){if(A.call(w,t))return t}else for(var r in j)if(c(j,n)){var e=a[r];if(e&&(t===e||A.call(e,t)))return t}throw TypeError("Target is not a typed array constructor")},exportTypedArrayMethod:function(t,r,e){if(i){if(e)for(var n in j){var o=a[n];o&&c(o.prototype,t)&&delete o.prototype[t]}S[t]&&!e||l(S,t,e?r:R&&m[t]||r)}},exportTypedArrayStaticMethod:function(t,r,e){var n,o;if(i){if(v){if(e)for(n in j)(o=a[n])&&c(o,t)&&delete o[t];if(w[t]&&!e)return;try{return l(w,t,e?r:R&&y[t]||r)}catch(t){}}for(n in j)!(o=a[n])||o[t]&&!e||l(o,t,r)}},isView:function(t){var r=f(t);return"DataView"===r||c(j,r)},isTypedArray:M,TypedArray:w,TypedArrayPrototype:S}},function(t,r,e){var n=e(5),o=e(110),i=e(4),a=e(28),u=Object.defineProperty;r.f=n?u:function(t,r,e){if(i(t),r=a(r,!0),i(e),o)try{return u(t,r,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported");return"value"in e&&(t[r]=e.value),t}},function(t,r,e){var n=e(15);t.exports=function(t){return Object(n(t))}},function(t,r){var e={}.hasOwnProperty;t.exports=function(t,r){return e.call(t,r)}},function(t,r,e){var n=e(38),o=e(50),i=e(10),a=e(7),u=e(57),c=[].push,f=function(t){var r=1==t,e=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l;return function(p,v,d,g){for(var y,m,x=i(p),b=o(x),w=n(v,d,3),S=a(b.length),E=0,A=g||u,O=r?A(p,S):e?A(p,0):void 0;S>E;E++)if((h||E in b)&&(m=w(y=b[E],E,x),t))if(r)O[E]=m;else if(m)switch(t){case 3:return!0;case 5:return y;case 6:return E;case 2:c.call(O,y)}else if(s)return!1;return l?-1:f||s?s:O}};t.exports={forEach:f(0),map:f(1),filter:f(2),some:f(3),every:f(4),find:f(5),findIndex:f(6)}},function(t,r,e){var n=e(5),o=e(64),i=e(36),a=e(21),u=e(28),c=e(11),f=e(110),s=Object.getOwnPropertyDescriptor;r.f=n?s:function(t,r){if(t=a(t),r=u(r,!0),f)try{return s(t,r)}catch(t){}if(c(t,r))return i(!o.f.call(t,r),t[r])}},function(t,r,e){var n=e(2),o=e(16),i=e(11),a=e(84),u=e(85),c=e(17),f=c.get,s=c.enforce,l=String(String).split("String");(t.exports=function(t,r,e,u){var c=!!u&&!!u.unsafe,f=!!u&&!!u.enumerable,h=!!u&&!!u.noTargetGet;"function"==typeof e&&("string"!=typeof r||i(e,"name")||o(e,"name",r),s(e).source=l.join("string"==typeof r?r:"")),t!==n?(c?!h&&t[r]&&(f=!0):delete t[r],f?t[r]=e:o(t,r,e)):f?t[r]=e:a(r,e)})(Function.prototype,"toString",(function(){return"function"==typeof this&&f(this).source||u(this)}))},function(t,r){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,r,e){var n=e(5),o=e(9),i=e(36);t.exports=n?function(t,r,e){return o.f(t,r,i(1,e))}:function(t,r,e){return t[r]=e,t}},function(t,r,e){var n,o,i,a=e(112),u=e(2),c=e(3),f=e(16),s=e(11),l=e(65),h=e(52),p=u.WeakMap;if(a){var v=new p,d=v.get,g=v.has,y=v.set;n=function(t,r){return y.call(v,t,r),r},o=function(t){return d.call(v,t)||{}},i=function(t){return g.call(v,t)}}else{var m=l("state");h[m]=!0,n=function(t,r){return f(t,m,r),r},o=function(t){return s(t,m)?t[m]:{}},i=function(t){return s(t,m)}}t.exports={set:n,get:o,has:i,enforce:function(t){return i(t)?o(t):n(t,{})},getterFor:function(t){return function(r){var e;if(!c(r)||(e=o(r)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return e}}}},function(t,r,e){var n=e(53),o=e(11),i=e(118),a=e(9).f;t.exports=function(t){var r=n.Symbol||(n.Symbol={});o(r,t)||a(r,t,{value:i.f(t)})}},function(t,r){t.exports=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t}},function(t,r,e){var n=e(5),o=e(1),i=e(11),a=Object.defineProperty,u={},c=function(t){throw t};t.exports=function(t,r){if(i(u,t))return u[t];r||(r={});var e=[][t],f=!!i(r,"ACCESSORS")&&r.ACCESSORS,s=i(r,0)?r[0]:c,l=i(r,1)?r[1]:void 0;return u[t]=!!e&&!o((function(){if(f&&!n)return!0;var t={length:-1};f?a(t,1,{enumerable:!0,get:c}):t[1]=1,e.call(t,s,l)}))}},function(t,r,e){var n=e(50),o=e(15);t.exports=function(t){return n(o(t))}},function(t,r,e){var n=e(15),o=/"/g;t.exports=function(t,r,e,i){var a=String(n(t)),u="<"+r;return""!==e&&(u+=" "+e+'="'+String(i).replace(o,""")+'"'),u+">"+a+""}},function(t,r,e){var n=e(1);t.exports=function(t){return n((function(){var r=""[t]('"');return r!==r.toLowerCase()||r.split('"').length>3}))}},,function(t,r){var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},function(t,r,e){var n=e(53),o=e(2),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,r){return arguments.length<2?i(n[t])||i(o[t]):n[t]&&n[t][r]||o[t]&&o[t][r]}},function(t,r){var e=Math.ceil,n=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?n:e)(t)}},function(t,r,e){var n=e(3);t.exports=function(t,r){if(!n(t))return t;var e,o;if(r&&"function"==typeof(e=t.toString)&&!n(o=e.call(t)))return o;if("function"==typeof(e=t.valueOf)&&!n(o=e.call(t)))return o;if(!r&&"function"==typeof(e=t.toString)&&!n(o=e.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,r){t.exports=!1},function(t,r,e){var n=e(11),o=e(10),i=e(65),a=e(94),u=i("IE_PROTO"),c=Object.prototype;t.exports=a?Object.getPrototypeOf:function(t){return t=o(t),n(t,u)?t[u]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?c:null}},function(t,r,e){var n=e(9).f,o=e(11),i=e(6)("toStringTag");t.exports=function(t,r,e){t&&!o(t=e?t:t.prototype,i)&&n(t,i,{configurable:!0,value:r})}},function(t,r,e){var n,o=e(4),i=e(91),a=e(88),u=e(52),c=e(116),f=e(83),s=e(65),l=s("IE_PROTO"),h=function(){},p=function(t){return"