Skip to content

Commit

Permalink
lib - refactor symbol names, implement GenericHMRAcceptor for stores
Browse files Browse the repository at this point in the history
  • Loading branch information
LankyMoose committed Oct 4, 2024
1 parent 6436d4a commit e80609b
Show file tree
Hide file tree
Showing 18 changed files with 128 additions and 67 deletions.
11 changes: 5 additions & 6 deletions packages/lib/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
export const signalSymbol = Symbol.for("kaioken.signal")
export const componentSymbol = Symbol.for("kaioken.component")
export const contextProviderSymbol = Symbol.for("kaioken.contextProvider")
export const fragmentSymbol = Symbol.for("kaioken.fragment")
export const kaiokenErrorSymbol = Symbol.for("kaioken.error")
export const $SIGNAL = Symbol.for("kaioken.signal")
export const $CONTEXT_PROVIDER = Symbol.for("kaioken.contextProvider")
export const $FRAGMENT = Symbol.for("kaioken.fragment")
export const $KAIOKEN_ERROR = Symbol.for("kaioken.error")
export const $HMR_ACCEPTOR = Symbol.for("kaioken.hrmAcceptor")

export const ELEMENT_ID_BASE = 16
export const CONSECUTIVE_DIRTY_LIMIT = 50

export const FLAG = {
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { contextProviderSymbol } from "./constants.js"
import { $CONTEXT_PROVIDER } from "./constants.js"
import { createElement } from "./element.js"

export function createContext<T>(defaultValue: T): Kaioken.Context<T> {
const ctx: Kaioken.Context<T> = {
Provider: ({ value, children }: Kaioken.ProviderProps<T>) => {
return createElement(
contextProviderSymbol,
$CONTEXT_PROVIDER,
{ value, ctx },
typeof children === "function" ? children(value) : children
)
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fragmentSymbol } from "./constants.js"
import { $FRAGMENT } from "./constants.js"
import { isValidElementKeyProp, isValidElementRefProp } from "./props.js"

export function createElement<T extends Kaioken.VNode["type"]>(
Expand Down Expand Up @@ -40,5 +40,5 @@ export function Fragment({
children: JSX.Children
key?: JSX.ElementKey
}): Kaioken.VNode {
return createElement(fragmentSymbol, key ? { key } : null, children)
return createElement($FRAGMENT, key ? { key } : null, children)
}
7 changes: 3 additions & 4 deletions packages/lib/src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { kaiokenErrorSymbol } from "./constants.js"
import { $KAIOKEN_ERROR } from "./constants.js"
import { __DEV__ } from "./env.js"
import { findParent, noop } from "./utils.js"

Expand All @@ -13,7 +13,7 @@ type KaiokenErrorOptions =
}

export class KaiokenError extends Error {
[kaiokenErrorSymbol] = true
[$KAIOKEN_ERROR] = true
/** Indicates whether the error is fatal and should crash the application */
fatal?: boolean
/** Present if vNode is provided */
Expand All @@ -34,8 +34,7 @@ export class KaiokenError extends Error {

static isKaiokenError(error: unknown): error is KaiokenError {
return (
error instanceof Error &&
(error as KaiokenError)[kaiokenErrorSymbol] === true
error instanceof Error && (error as KaiokenError)[$KAIOKEN_ERROR] === true
)
}
}
Expand Down
34 changes: 22 additions & 12 deletions packages/lib/src/globalContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { AppContext } from "./appContext"
import type { Store } from "./store"
import { $HMR_ACCEPTOR } from "./constants.js"
import { __DEV__ } from "./env.js"
import { isGenericHmrAcceptor } from "./hmr.js"
import { traverseApply } from "./utils.js"

export { KaiokenGlobalContext, type GlobalKaiokenEvent }
Expand All @@ -24,6 +27,8 @@ type Evt =

type GlobalKaiokenEvent = Evt["name"]

type HotVar = Kaioken.FC | Store<any, any>

class KaiokenGlobalContext {
#contexts: Set<AppContext> = new Set()
private listeners: Map<
Expand All @@ -37,24 +42,29 @@ class KaiokenGlobalContext {
}

HMRContext = {
register: (filePath: string, componentMap: Record<string, Kaioken.FC>) => {
register: (filePath: string, hotVars: Record<string, HotVar>) => {
if (__DEV__) {
const components = this.moduleMap.get(filePath)
if (!components) {
this.moduleMap.set(filePath, new Map(Object.entries(componentMap)))
const mod = this.moduleMap.get(filePath)
if (!mod) {
this.moduleMap.set(filePath, new Map(Object.entries(hotVars)))
return
}
for (const [name, newFn] of Object.entries(componentMap)) {
const oldFn = components.get(name)
components.set(name, newFn)
if (!oldFn) continue
for (const [name, newVal] of Object.entries(hotVars)) {
const oldVal = mod.get(name)
mod.set(name, newVal)
if (!oldVal) continue
if (isGenericHmrAcceptor(oldVal) && isGenericHmrAcceptor(newVal)) {
newVal[$HMR_ACCEPTOR].inject(oldVal[$HMR_ACCEPTOR].provide())
oldVal[$HMR_ACCEPTOR].destroy()
continue
}
this.#contexts.forEach((ctx) => {
if (!ctx.mounted || !ctx.rootNode) return
traverseApply(ctx.rootNode, (vNode) => {
if (vNode.type === oldFn) {
vNode.type = newFn
if (vNode.type === oldVal) {
vNode.type = newVal
if (vNode.prev) {
vNode.prev.type = newFn
vNode.prev.type = newVal
}
ctx.requestUpdate(vNode)
}
Expand Down Expand Up @@ -90,5 +100,5 @@ class KaiokenGlobalContext {
this.listeners.get(event)!.delete(callback)
}

private moduleMap = new Map<string, Map<string, Kaioken.FC>>()
private moduleMap = new Map<string, Map<string, HotVar>>()
}
21 changes: 21 additions & 0 deletions packages/lib/src/hmr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { $HMR_ACCEPTOR } from "./constants.js"

export type GenericHMRAcceptor<T = {}> = {
[$HMR_ACCEPTOR]: {
provide: () => T
inject: (prev: T) => void
destroy: () => void
}
}

export function isGenericHmrAcceptor(
thing: unknown
): thing is GenericHMRAcceptor<any> {
return (
!!thing &&
(typeof thing === "object" || typeof thing === "function") &&
$HMR_ACCEPTOR in thing &&
typeof thing[$HMR_ACCEPTOR] === "object" &&
!!thing[$HMR_ACCEPTOR]
)
}
6 changes: 3 additions & 3 deletions packages/lib/src/hooks/useContext.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type HookCallbackState, useHook } from "./utils.js"
import { __DEV__ } from "../env.js"
import { contextProviderSymbol } from "../constants.js"
import { $CONTEXT_PROVIDER } from "../constants.js"

type ContextProviderNode<T> = Kaioken.VNode & {
type: typeof contextProviderSymbol
type: typeof $CONTEXT_PROVIDER
props: { value: T; ctx: Kaioken.Context<T> }
}

Expand Down Expand Up @@ -43,7 +43,7 @@ const useContextCallback = <T>({

let n = vNode.parent
while (n) {
if (n.type === contextProviderSymbol) {
if (n.type === $CONTEXT_PROVIDER) {
const ctxNode = n as ContextProviderNode<T>
if (ctxNode.props.ctx === hook.context) {
hook.ctxNode = ctxNode
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export * from "./portal.js"
export * from "./renderToString.js"
export * from "./router/index.js"
export * from "./signal.js"
export * from "./store.js"
export { createStore, type Store, type MethodFactory } from "./store.js"
export * from "./transition.js"

if ("window" in globalThis) {
Expand Down
8 changes: 4 additions & 4 deletions packages/lib/src/reconciler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AppContext } from "./appContext"
import { ELEMENT_TYPE, FLAG, fragmentSymbol } from "./constants.js"
import { ELEMENT_TYPE, FLAG, $FRAGMENT } from "./constants.js"
import { ctx } from "./globals.js"
import { isVNode } from "./utils.js"
import { Signal } from "./signal.js"
Expand Down Expand Up @@ -211,7 +211,7 @@ function updateTextNode(parent: VNode, oldNode: VNode | null, content: string) {

function updateNode(parent: VNode, oldNode: VNode | null, newNode: VNode) {
const nodeType = newNode.type
if (nodeType === fragmentSymbol) {
if (nodeType === $FRAGMENT) {
return updateFragment(
parent,
oldNode,
Expand Down Expand Up @@ -239,8 +239,8 @@ function updateFragment(
children: unknown[],
newProps = {}
) {
if (oldNode === null || oldNode.type !== fragmentSymbol) {
const el = createElement(fragmentSymbol, { children, ...newProps })
if (oldNode === null || oldNode.type !== $FRAGMENT) {
const el = createElement($FRAGMENT, { children, ...newProps })
el.parent = parent
el.depth = parent.depth + 1
return el
Expand Down
8 changes: 2 additions & 6 deletions packages/lib/src/renderToString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import {
selfClosingTags,
} from "./utils.js"
import { Signal } from "./signal.js"
import {
contextProviderSymbol,
ELEMENT_TYPE,
fragmentSymbol,
} from "./constants.js"
import { $CONTEXT_PROVIDER, ELEMENT_TYPE, $FRAGMENT } from "./constants.js"
import { assertValidElementProps } from "./props.js"

export function renderToString<T extends Record<string, unknown>>(
Expand Down Expand Up @@ -56,7 +52,7 @@ function renderToString_internal(
const type = el.type
if (type === ELEMENT_TYPE.text)
return encodeHtmlEntities(props.nodeValue ?? "")
if (type === fragmentSymbol || type === contextProviderSymbol) {
if (type === $FRAGMENT || type === $CONTEXT_PROVIDER) {
if (!Array.isArray(children))
return renderToString_internal(children, el, idx)
return children.map((c, i) => renderToString_internal(c, el, i)).join("")
Expand Down
6 changes: 3 additions & 3 deletions packages/lib/src/signal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { signalSymbol } from "./constants.js"
import { $SIGNAL } from "./constants.js"
import { __DEV__ } from "./env.js"
import { node } from "./globals.js"
import { useHook } from "./hooks/utils.js"
Expand Down Expand Up @@ -91,7 +91,7 @@ export interface SignalLike<T> {
type SignalSubscriber = Kaioken.VNode | Function

export class Signal<T> {
[signalSymbol] = true
[$SIGNAL] = true
#value: T
#subscribers = new Set<SignalSubscriber>()
displayName?: string
Expand Down Expand Up @@ -148,7 +148,7 @@ export class Signal<T> {
}

static isSignal(x: any): x is Signal<any> {
return typeof x === "object" && !!x && signalSymbol in x
return typeof x === "object" && !!x && $SIGNAL in x
}

static unsubscribe(sub: SignalSubscriber, signal: Signal<any>) {
Expand Down
8 changes: 2 additions & 6 deletions packages/lib/src/ssr/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ import {
selfClosingTags,
} from "../utils.js"
import { Signal } from "../signal.js"
import {
contextProviderSymbol,
ELEMENT_TYPE,
fragmentSymbol,
} from "../constants.js"
import { $CONTEXT_PROVIDER, ELEMENT_TYPE, $FRAGMENT } from "../constants.js"
import { assertValidElementProps } from "../props.js"

type RequestState = {
Expand Down Expand Up @@ -84,7 +80,7 @@ function renderToStream_internal(
state.stream.push(encodeHtmlEntities(props.nodeValue ?? ""))
return
}
if (type === fragmentSymbol || type === contextProviderSymbol) {
if (type === $FRAGMENT || type === $CONTEXT_PROVIDER) {
if (!Array.isArray(children))
return renderToStream_internal(state, children, el, idx)
return children.forEach((c, i) => renderToStream_internal(state, c, el, i))
Expand Down
29 changes: 27 additions & 2 deletions packages/lib/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Prettify } from "./types.utils.js"
import { __DEV__ } from "./env.js"
import { sideEffectsEnabled, useAppContext, useHook } from "./hooks/utils.js"
import { getVNodeAppContext, shallowCompare } from "./utils.js"
import { $HMR_ACCEPTOR } from "./constants.js"
import { GenericHMRAcceptor } from "./hmr.js"

export { createStore }
export type { Store, MethodFactory }
Expand Down Expand Up @@ -45,7 +47,7 @@ function createStore<T, U extends MethodFactory<T>>(
let state = initial
let stateIteration = 0
const subscribers = new Set<Kaioken.VNode | Function>()
const nodeToSliceComputeMap = new WeakMap<Kaioken.VNode, NodeSliceCompute[]>()
let nodeToSliceComputeMap = new WeakMap<Kaioken.VNode, NodeSliceCompute[]>()

const getState = () => state
const setState = (setter: Kaioken.StateSetter<T>) => {
Expand Down Expand Up @@ -126,7 +128,7 @@ function createStore<T, U extends MethodFactory<T>>(
)
}

return Object.assign(useStore, {
const store = Object.assign(useStore, {
getState,
setState,
methods,
Expand All @@ -135,4 +137,27 @@ function createStore<T, U extends MethodFactory<T>>(
return (() => (subscribers.delete(fn), void 0)) as () => void
},
})

if (__DEV__) {
return Object.assign(store, {
[$HMR_ACCEPTOR]: {
provide: () => ({ state, subscribers, nodeToSliceComputeMap }),
inject: (prev) => {
prev.subscribers.forEach((sub) => subscribers.add(sub))
nodeToSliceComputeMap = prev.nodeToSliceComputeMap
setState(prev.state)
},
destroy: () => {
subscribers.clear()
nodeToSliceComputeMap = new WeakMap()
},
},
} satisfies GenericHMRAcceptor<{
state: T
subscribers: Set<Kaioken.VNode | Function>
nodeToSliceComputeMap: WeakMap<Kaioken.VNode, NodeSliceCompute[]>
}>)
}

return store
}
4 changes: 2 additions & 2 deletions packages/lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
Signal as SignalClass,
SignalLike,
} from "./signal"
import type { contextProviderSymbol, fragmentSymbol } from "./constants"
import type { $CONTEXT_PROVIDER, $FRAGMENT } from "./constants"
import type { KaiokenGlobalContext } from "./globalContext"
import type {
EventAttributes,
Expand Down Expand Up @@ -171,7 +171,7 @@ declare global {

type Signal<T> = SignalClass<T> | ReadonlySignal<T>

type ExoticSymbol = typeof fragmentSymbol | typeof contextProviderSymbol
type ExoticSymbol = typeof $FRAGMENT | typeof $CONTEXT_PROVIDER

type VNode = {
type: string | Function | ExoticSymbol
Expand Down
4 changes: 1 addition & 3 deletions packages/lib/src/types.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { contextProviderSymbol, fragmentSymbol } from "./constants"

export type SomeElement = HTMLElement | SVGElement
export type SomeDom = HTMLElement | SVGElement | Text
export type MaybeDom = SomeDom | undefined
Expand All @@ -8,7 +6,7 @@ type VNode = Kaioken.VNode

export type FunctionVNode = VNode & { type: (...args: any) => any }
export type ExoticVNode = VNode & {
type: typeof contextProviderSymbol | typeof fragmentSymbol
type: Kaioken.ExoticSymbol
}
export type ElementVNode = VNode & { dom: SomeElement }
export type DomVNode = VNode & { dom: SomeDom }
Expand Down
Loading

0 comments on commit e80609b

Please sign in to comment.