From 6b719e793bf6c12099cea90454a51fda0a669a7e Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 23 Dec 2021 09:59:28 +0800 Subject: [PATCH] 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];