kiru 0.44.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +5 -0
- package/package.json +81 -0
- package/src/appContext.ts +186 -0
- package/src/cloneVNode.ts +14 -0
- package/src/constants.ts +146 -0
- package/src/context.ts +56 -0
- package/src/dom.ts +712 -0
- package/src/element.ts +54 -0
- package/src/env.ts +6 -0
- package/src/error.ts +85 -0
- package/src/flags.ts +15 -0
- package/src/form/index.ts +662 -0
- package/src/form/types.ts +261 -0
- package/src/form/utils.ts +19 -0
- package/src/generateId.ts +19 -0
- package/src/globalContext.ts +161 -0
- package/src/globals.ts +21 -0
- package/src/hmr.ts +178 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAsync.ts +136 -0
- package/src/hooks/useCallback.ts +31 -0
- package/src/hooks/useContext.ts +79 -0
- package/src/hooks/useEffect.ts +44 -0
- package/src/hooks/useEffectEvent.ts +24 -0
- package/src/hooks/useId.ts +42 -0
- package/src/hooks/useLayoutEffect.ts +47 -0
- package/src/hooks/useMemo.ts +33 -0
- package/src/hooks/useReducer.ts +50 -0
- package/src/hooks/useRef.ts +40 -0
- package/src/hooks/useState.ts +62 -0
- package/src/hooks/useSyncExternalStore.ts +59 -0
- package/src/hooks/useViewTransition.ts +26 -0
- package/src/hooks/utils.ts +259 -0
- package/src/hydration.ts +67 -0
- package/src/index.ts +61 -0
- package/src/jsx.ts +11 -0
- package/src/lazy.ts +238 -0
- package/src/memo.ts +48 -0
- package/src/portal.ts +43 -0
- package/src/profiling.ts +105 -0
- package/src/props.ts +36 -0
- package/src/reconciler.ts +531 -0
- package/src/renderToString.ts +91 -0
- package/src/router/index.ts +2 -0
- package/src/router/route.ts +51 -0
- package/src/router/router.ts +275 -0
- package/src/router/routerUtils.ts +49 -0
- package/src/scheduler.ts +522 -0
- package/src/signals/base.ts +237 -0
- package/src/signals/computed.ts +139 -0
- package/src/signals/effect.ts +60 -0
- package/src/signals/globals.ts +11 -0
- package/src/signals/index.ts +12 -0
- package/src/signals/jsx.ts +45 -0
- package/src/signals/types.ts +10 -0
- package/src/signals/utils.ts +12 -0
- package/src/signals/watch.ts +151 -0
- package/src/ssr/client.ts +29 -0
- package/src/ssr/hydrationBoundary.ts +63 -0
- package/src/ssr/index.ts +1 -0
- package/src/ssr/server.ts +124 -0
- package/src/store.ts +241 -0
- package/src/swr.ts +360 -0
- package/src/transition.ts +80 -0
- package/src/types.dom.ts +1250 -0
- package/src/types.ts +209 -0
- package/src/types.utils.ts +39 -0
- package/src/utils.ts +581 -0
- package/src/warning.ts +9 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
type ObjectValuesArray<T extends object> = T[keyof T][]
|
|
2
|
+
|
|
3
|
+
type IsObject<T> = T extends object ? (T extends any[] ? false : true) : false
|
|
4
|
+
|
|
5
|
+
type InferKeyWithIndices<T, Prefix extends string = ""> = {
|
|
6
|
+
[K in keyof T & string]: T[K] extends Array<infer U> | undefined
|
|
7
|
+
? IsObject<U> extends true
|
|
8
|
+
? // If it's an array of objects, infer the index and recurse into the object properties
|
|
9
|
+
| `${Prefix}${K}`
|
|
10
|
+
| `${Prefix}${K}.${number}`
|
|
11
|
+
| InferKeyWithIndices<U, `${Prefix}${K}.${number}.`>
|
|
12
|
+
: `${Prefix}${K}` | `${Prefix}${K}.${number}` // For arrays of primitives, only support indices
|
|
13
|
+
: IsObject<T[K]> extends true
|
|
14
|
+
? // If it's an object, recurse into its properties
|
|
15
|
+
`${Prefix}${K}` | InferKeyWithIndices<T[K], `${Prefix}${K}.`>
|
|
16
|
+
: `${Prefix}${K}` // If it's a primitive, return the key
|
|
17
|
+
}[keyof T & string]
|
|
18
|
+
|
|
19
|
+
export type RecordKey<T extends Record<string, unknown>> =
|
|
20
|
+
InferKeyWithIndices<T>
|
|
21
|
+
|
|
22
|
+
type InferValue<
|
|
23
|
+
T,
|
|
24
|
+
Path extends string
|
|
25
|
+
> = Path extends `${infer K}.${infer Rest}`
|
|
26
|
+
? K extends keyof T
|
|
27
|
+
? InferValue<T[K], Rest> // Recursively resolve objects
|
|
28
|
+
: K extends `${number}` // Check if it's a numeric index for an array
|
|
29
|
+
? T extends Array<infer U> | undefined
|
|
30
|
+
? InferValue<U, Rest> // Recursively resolve array elements
|
|
31
|
+
: never
|
|
32
|
+
: never
|
|
33
|
+
: Path extends keyof T
|
|
34
|
+
? T[Path] // Direct lookup for simple keys
|
|
35
|
+
: Path extends `${number}` // If the path is numeric (array index)
|
|
36
|
+
? T extends Array<infer U> | undefined
|
|
37
|
+
? U // Resolve the array element type
|
|
38
|
+
: never
|
|
39
|
+
: never
|
|
40
|
+
|
|
41
|
+
export type InferRecordKeyValue<
|
|
42
|
+
T extends Record<string, unknown>,
|
|
43
|
+
K extends RecordKey<T>
|
|
44
|
+
> = InferValue<T, K>
|
|
45
|
+
|
|
46
|
+
export type ValidatorContext<T> = {
|
|
47
|
+
value: T
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type FormFieldValidator<T extends unknown> = (
|
|
51
|
+
context: ValidatorContext<T>
|
|
52
|
+
) => any
|
|
53
|
+
|
|
54
|
+
export type AsyncValidatorContext<T> = {
|
|
55
|
+
value: T
|
|
56
|
+
abortSignal: AbortSignal
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type AsyncFormFieldValidator<T extends unknown> = (
|
|
60
|
+
context: AsyncValidatorContext<T>
|
|
61
|
+
) => Promise<any>
|
|
62
|
+
|
|
63
|
+
export type FormFieldValidators<RecordKey extends string, Value> = {
|
|
64
|
+
dependentOn: RecordKey[]
|
|
65
|
+
onBlur: FormFieldValidator<Value>
|
|
66
|
+
onChange: FormFieldValidator<Value>
|
|
67
|
+
onChangeAsyncDebounceMs: number
|
|
68
|
+
onChangeAsync: AsyncFormFieldValidator<Value>
|
|
69
|
+
onMount: FormFieldValidator<Value>
|
|
70
|
+
onSubmit: FormFieldValidator<Value>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type Falsy = false | null | undefined
|
|
74
|
+
|
|
75
|
+
type InferValidatorReturn<T> = T extends Falsy
|
|
76
|
+
? never
|
|
77
|
+
: T extends Promise<infer U>
|
|
78
|
+
? InferValidatorReturn<U>
|
|
79
|
+
: T
|
|
80
|
+
|
|
81
|
+
type InferFormFieldErrors<
|
|
82
|
+
RecordKey extends string,
|
|
83
|
+
T extends FormFieldValidators<RecordKey, any>
|
|
84
|
+
> = ObjectValuesArray<{
|
|
85
|
+
[K in keyof T]: T[K] extends FormFieldValidator<any>
|
|
86
|
+
? InferValidatorReturn<ReturnType<T[K]>>
|
|
87
|
+
: never
|
|
88
|
+
}>
|
|
89
|
+
|
|
90
|
+
export type FormFieldContext<
|
|
91
|
+
T extends Record<string, unknown>,
|
|
92
|
+
Name extends RecordKey<T>,
|
|
93
|
+
Validators extends FormFieldValidators<
|
|
94
|
+
RecordKey<T>,
|
|
95
|
+
InferRecordKeyValue<T, Name>
|
|
96
|
+
>,
|
|
97
|
+
IsArray extends Boolean
|
|
98
|
+
> = {
|
|
99
|
+
name: Name
|
|
100
|
+
state: {
|
|
101
|
+
value: InferRecordKeyValue<T, Name>
|
|
102
|
+
errors: InferFormFieldErrors<RecordKey<T>, Validators>
|
|
103
|
+
isTouched: boolean
|
|
104
|
+
isValidating: boolean
|
|
105
|
+
}
|
|
106
|
+
handleChange: (value: InferRecordKeyValue<T, Name>) => void
|
|
107
|
+
handleBlur: () => void
|
|
108
|
+
} & (IsArray extends true
|
|
109
|
+
? {
|
|
110
|
+
items: {
|
|
111
|
+
replace: (
|
|
112
|
+
index: number,
|
|
113
|
+
value: InferRecordKeyValue<T, Name> extends Array<infer U> ? U : any
|
|
114
|
+
) => void
|
|
115
|
+
push: (
|
|
116
|
+
value: InferRecordKeyValue<T, Name> extends Array<infer U> ? U : any
|
|
117
|
+
) => void
|
|
118
|
+
remove: (index: number) => void
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
: {})
|
|
122
|
+
|
|
123
|
+
export type AnyFormFieldContext<
|
|
124
|
+
T extends Record<string, any> = Record<string, any>
|
|
125
|
+
> = FormFieldContext<T, any, any, any>
|
|
126
|
+
|
|
127
|
+
export type FormFieldState<
|
|
128
|
+
T extends Record<string, any>,
|
|
129
|
+
K extends RecordKey<T>
|
|
130
|
+
> = {
|
|
131
|
+
value: InferRecordKeyValue<T, K>
|
|
132
|
+
errors: InferFormFieldErrors<
|
|
133
|
+
K,
|
|
134
|
+
FormFieldValidators<K, InferRecordKeyValue<T, K>>
|
|
135
|
+
>
|
|
136
|
+
isTouched: boolean
|
|
137
|
+
isValidating: boolean
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type FormFieldProps<
|
|
141
|
+
T extends Record<string, unknown>,
|
|
142
|
+
Name extends RecordKey<T>,
|
|
143
|
+
Validators extends FormFieldValidators<
|
|
144
|
+
RecordKey<T>,
|
|
145
|
+
InferRecordKeyValue<T, Name>
|
|
146
|
+
>,
|
|
147
|
+
IsArray extends boolean
|
|
148
|
+
> = {
|
|
149
|
+
name: Name
|
|
150
|
+
validators?: Partial<Validators>
|
|
151
|
+
|
|
152
|
+
array?: IsArray
|
|
153
|
+
children: (
|
|
154
|
+
context: FormFieldContext<T, Name, Validators, IsArray>
|
|
155
|
+
) => JSX.Element
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type FormFieldComponent<T extends Record<string, unknown>> = <
|
|
159
|
+
Name extends RecordKey<T>,
|
|
160
|
+
Validators extends FormFieldValidators<
|
|
161
|
+
RecordKey<T>,
|
|
162
|
+
InferRecordKeyValue<T, Name>
|
|
163
|
+
>,
|
|
164
|
+
IsArray extends boolean
|
|
165
|
+
>(
|
|
166
|
+
props: FormFieldProps<T, Name, Validators, IsArray>
|
|
167
|
+
) => JSX.Element
|
|
168
|
+
|
|
169
|
+
export type SelectorState<T extends Record<string, unknown>> = {
|
|
170
|
+
values: T
|
|
171
|
+
canSubmit: boolean
|
|
172
|
+
isSubmitting: boolean
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type FormSubscribeProps<
|
|
176
|
+
T extends Record<string, unknown>,
|
|
177
|
+
SelectorFunction extends (state: SelectorState<T>) => unknown,
|
|
178
|
+
U
|
|
179
|
+
> = {
|
|
180
|
+
selector: SelectorFunction
|
|
181
|
+
children: (selection: U) => JSX.Element
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type FormSubscribeComponent<T extends Record<string, unknown>> = <
|
|
185
|
+
Selector extends (state: SelectorState<T>) => unknown
|
|
186
|
+
>(
|
|
187
|
+
props: FormSubscribeProps<T, Selector, ReturnType<Selector>>
|
|
188
|
+
) => JSX.Element
|
|
189
|
+
|
|
190
|
+
export type UseFormState<T extends Record<string, unknown>> = {
|
|
191
|
+
Field: FormFieldComponent<T>
|
|
192
|
+
Subscribe: FormSubscribeComponent<T>
|
|
193
|
+
getFieldState: <K extends RecordKey<T>>(name: K) => FormFieldState<T, K>
|
|
194
|
+
handleSubmit: () => Promise<void>
|
|
195
|
+
reset: () => void
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export type FormContext<T extends Record<string, unknown>> = {
|
|
199
|
+
state: T
|
|
200
|
+
validateForm: () => Promise<any[]>
|
|
201
|
+
resetField: (name: RecordKey<T>) => void
|
|
202
|
+
deleteField: (name: RecordKey<T>) => void
|
|
203
|
+
setFieldValue: <K extends RecordKey<T>>(
|
|
204
|
+
name: K,
|
|
205
|
+
value: InferRecordKeyValue<T, K>
|
|
206
|
+
) => void
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export type UseFormConfig<T extends Record<string, unknown>> = {
|
|
210
|
+
initialValues?: T
|
|
211
|
+
onSubmit?: (ctx: FormContext<T>) => void | Promise<void>
|
|
212
|
+
onSubmitInvalid?: (ctx: FormContext<T>) => void | Promise<void>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface FormStateSubscriber<T extends Record<string, unknown>> {
|
|
216
|
+
selector: (state: SelectorState<T>) => unknown
|
|
217
|
+
selection: ReturnType<this["selector"]>
|
|
218
|
+
update: () => void
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type UseFormInternalState<T extends Record<string, unknown>> = {
|
|
222
|
+
Field: FormFieldComponent<T>
|
|
223
|
+
Subscribe: FormSubscribeComponent<T>
|
|
224
|
+
formController: FormController<T>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export type FormController<T extends Record<string, unknown>> = {
|
|
228
|
+
subscribers: Set<FormStateSubscriber<T>>
|
|
229
|
+
state: T
|
|
230
|
+
validateField: (
|
|
231
|
+
name: RecordKey<T>,
|
|
232
|
+
type: "onChange" | "onSubmit" | "onMount" | "onBlur"
|
|
233
|
+
) => Promise<void>
|
|
234
|
+
getFieldState: <K extends RecordKey<T>>(name: K) => FormFieldState<T, K>
|
|
235
|
+
setFieldValue: (
|
|
236
|
+
name: RecordKey<T>,
|
|
237
|
+
value: InferRecordKeyValue<T, RecordKey<T>>
|
|
238
|
+
) => void
|
|
239
|
+
arrayFieldReplace: <K extends RecordKey<T>>(
|
|
240
|
+
name: K,
|
|
241
|
+
index: number,
|
|
242
|
+
value: InferRecordKeyValue<T, K> extends Array<infer U> ? U : any
|
|
243
|
+
) => void
|
|
244
|
+
arrayFieldPush: <K extends RecordKey<T>>(
|
|
245
|
+
name: K,
|
|
246
|
+
value: InferRecordKeyValue<T, K> extends Array<infer U> ? U : any
|
|
247
|
+
) => void
|
|
248
|
+
arrayFieldRemove: (name: RecordKey<T>, index: number) => void
|
|
249
|
+
connectField: (name: RecordKey<T>, update: () => void) => void
|
|
250
|
+
disconnectField: (name: RecordKey<T>, update: () => void) => void
|
|
251
|
+
setFieldValidators: <K extends RecordKey<T>>(
|
|
252
|
+
name: K,
|
|
253
|
+
validators: Partial<
|
|
254
|
+
FormFieldValidators<RecordKey<T>, InferRecordKeyValue<T, K>>
|
|
255
|
+
>
|
|
256
|
+
) => void
|
|
257
|
+
getSelectorState: () => any
|
|
258
|
+
validateForm: () => Promise<string[]>
|
|
259
|
+
getFormContext: () => FormContext<T>
|
|
260
|
+
reset: (values?: T) => void
|
|
261
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const objSet = (
|
|
2
|
+
obj: Record<string, any>,
|
|
3
|
+
path: string[],
|
|
4
|
+
value: any
|
|
5
|
+
) => {
|
|
6
|
+
let o = obj
|
|
7
|
+
for (let i = 0; i < path.length; i++) {
|
|
8
|
+
const key = path[i]
|
|
9
|
+
if (i === path.length - 1) {
|
|
10
|
+
o[key] = value
|
|
11
|
+
} else {
|
|
12
|
+
o = o[key]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const objGet = <T>(obj: Record<string, any>, path: string[]): T => {
|
|
18
|
+
return path.reduce((o, key) => o[key], obj) as T
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DEFAULT_CHARACTERS =
|
|
2
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-" as const
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a random id
|
|
6
|
+
* @param {number} [size=10] size of the id (in number of characters)
|
|
7
|
+
* @param {string} [characterSet=defaultCharacterSet] set of characters to be used in the generation of the id
|
|
8
|
+
* @returns {string} random id of length {@link size}
|
|
9
|
+
*/
|
|
10
|
+
export function generateRandomID(
|
|
11
|
+
size: number = 10,
|
|
12
|
+
characterSet: string = DEFAULT_CHARACTERS
|
|
13
|
+
): string {
|
|
14
|
+
let id = ""
|
|
15
|
+
for (let i = 0; i < size; i++) {
|
|
16
|
+
id += characterSet[(Math.random() * characterSet.length) | 0] // bitwise OR `|` is faster than `Math.floor()`
|
|
17
|
+
}
|
|
18
|
+
return id
|
|
19
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { AppContext } from "./appContext"
|
|
2
|
+
import { __DEV__ } from "./env.js"
|
|
3
|
+
import { createHMRContext } from "./hmr.js"
|
|
4
|
+
import { createProfilingContext } from "./profiling.js"
|
|
5
|
+
import { Store } from "./store"
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
createKaiokenGlobalContext,
|
|
9
|
+
type GlobalKaiokenEvent,
|
|
10
|
+
type KaiokenGlobalContext,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ReactiveMap<V> {
|
|
14
|
+
add(key: string, value: V): void
|
|
15
|
+
delete(key: string): void
|
|
16
|
+
subscribe(cb: (value: Record<string, V>) => void): () => void
|
|
17
|
+
readonly size: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createReactiveMap<V>(): ReactiveMap<V> {
|
|
21
|
+
const map = new Map<string, V>()
|
|
22
|
+
const listeners = new Set<(value: Record<string, V>) => void>()
|
|
23
|
+
|
|
24
|
+
function add(key: string, value: V): void {
|
|
25
|
+
if (map.has(key)) return
|
|
26
|
+
map.set(key, value)
|
|
27
|
+
notify()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function deleteKey(key: string): void {
|
|
31
|
+
if (!map.has(key)) return
|
|
32
|
+
map.delete(key)
|
|
33
|
+
notify()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function notify(): void {
|
|
37
|
+
const val = Object.fromEntries(map)
|
|
38
|
+
listeners.forEach((cb) => cb(val))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function subscribe(cb: (value: Record<string, V>) => void): () => void {
|
|
42
|
+
listeners.add(cb)
|
|
43
|
+
cb(Object.fromEntries(map))
|
|
44
|
+
return () => listeners.delete(cb)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
add,
|
|
49
|
+
delete: deleteKey,
|
|
50
|
+
subscribe,
|
|
51
|
+
get size() {
|
|
52
|
+
return map.size
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Evt =
|
|
58
|
+
| {
|
|
59
|
+
name: "mount"
|
|
60
|
+
data?: undefined
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
name: "unmount"
|
|
64
|
+
data?: undefined
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
name: "update"
|
|
68
|
+
data?: undefined
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
name: "error"
|
|
72
|
+
data: Error
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type GlobalKaiokenEvent = Evt["name"]
|
|
76
|
+
|
|
77
|
+
interface KaiokenGlobalContext {
|
|
78
|
+
readonly apps: AppContext[]
|
|
79
|
+
stores?: ReactiveMap<Store<any, any>>
|
|
80
|
+
HMRContext?: ReturnType<typeof createHMRContext>
|
|
81
|
+
profilingContext?: ReturnType<typeof createProfilingContext>
|
|
82
|
+
globalState: Record<symbol, any>
|
|
83
|
+
emit<T extends Evt>(event: T["name"], ctx: AppContext, data?: T["data"]): void
|
|
84
|
+
on<T extends Evt>(
|
|
85
|
+
event: T["name"],
|
|
86
|
+
callback: (ctx: AppContext, data: T["data"]) => void
|
|
87
|
+
): void
|
|
88
|
+
off<T extends Evt>(
|
|
89
|
+
event: T["name"],
|
|
90
|
+
callback: (ctx: AppContext, data?: T["data"]) => void
|
|
91
|
+
): void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createKaiokenGlobalContext(): KaiokenGlobalContext {
|
|
95
|
+
const contexts = new Set<AppContext>()
|
|
96
|
+
const listeners = new Map<
|
|
97
|
+
GlobalKaiokenEvent,
|
|
98
|
+
Set<(ctx: AppContext, data?: Evt["data"]) => void>
|
|
99
|
+
>()
|
|
100
|
+
const globalState: Record<symbol, any> = {}
|
|
101
|
+
|
|
102
|
+
let stores: ReactiveMap<Store<any, any>> | undefined
|
|
103
|
+
let HMRContext: ReturnType<typeof createHMRContext> | undefined
|
|
104
|
+
let profilingContext: ReturnType<typeof createProfilingContext> | undefined
|
|
105
|
+
|
|
106
|
+
function emit<T extends Evt>(
|
|
107
|
+
event: T["name"],
|
|
108
|
+
ctx: AppContext,
|
|
109
|
+
data?: T["data"]
|
|
110
|
+
): void {
|
|
111
|
+
listeners.get(event)?.forEach((cb) => cb(ctx, data))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function on<T extends Evt>(
|
|
115
|
+
event: T["name"],
|
|
116
|
+
callback: (ctx: AppContext, data: T["data"]) => void
|
|
117
|
+
): void {
|
|
118
|
+
if (!listeners.has(event)) {
|
|
119
|
+
listeners.set(event, new Set())
|
|
120
|
+
}
|
|
121
|
+
listeners.get(event)!.add(callback)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function off<T extends Evt>(
|
|
125
|
+
event: T["name"],
|
|
126
|
+
callback: (ctx: AppContext, data?: T["data"]) => void
|
|
127
|
+
): void {
|
|
128
|
+
listeners.get(event)?.delete(callback)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const globalContext: KaiokenGlobalContext = {
|
|
132
|
+
get apps() {
|
|
133
|
+
return Array.from(contexts)
|
|
134
|
+
},
|
|
135
|
+
get stores() {
|
|
136
|
+
return stores
|
|
137
|
+
},
|
|
138
|
+
get HMRContext() {
|
|
139
|
+
return HMRContext
|
|
140
|
+
},
|
|
141
|
+
get profilingContext() {
|
|
142
|
+
return profilingContext
|
|
143
|
+
},
|
|
144
|
+
globalState,
|
|
145
|
+
emit,
|
|
146
|
+
on,
|
|
147
|
+
off,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Initialize event listeners
|
|
151
|
+
on("mount", (ctx) => contexts.add(ctx))
|
|
152
|
+
on("unmount", (ctx) => contexts.delete(ctx))
|
|
153
|
+
|
|
154
|
+
if (__DEV__) {
|
|
155
|
+
HMRContext = createHMRContext()
|
|
156
|
+
profilingContext = createProfilingContext()
|
|
157
|
+
stores = createReactiveMap()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return globalContext
|
|
161
|
+
}
|
package/src/globals.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AppContext } from "./appContext"
|
|
2
|
+
|
|
3
|
+
export { node, hookIndex, ctx, renderMode, nodeToCtxMap }
|
|
4
|
+
|
|
5
|
+
const node = {
|
|
6
|
+
current: null as Kaioken.VNode | null,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const hookIndex = {
|
|
10
|
+
current: 0,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ctx = {
|
|
14
|
+
current: null! as AppContext<any>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const renderMode = {
|
|
18
|
+
current: ("window" in globalThis ? "dom" : "string") as Kaioken.RenderMode,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const nodeToCtxMap = new WeakMap<Kaioken.VNode, AppContext<any>>()
|
package/src/hmr.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Store } from "./store"
|
|
2
|
+
import type { WatchEffect } from "./signals/watch"
|
|
3
|
+
import { $HMR_ACCEPT } from "./constants.js"
|
|
4
|
+
import { __DEV__ } from "./env.js"
|
|
5
|
+
import { Signal } from "./signals/base.js"
|
|
6
|
+
import { traverseApply } from "./utils.js"
|
|
7
|
+
import type { AppContext } from "./appContext"
|
|
8
|
+
|
|
9
|
+
export type HMRAccept<T = {}> = {
|
|
10
|
+
provide: () => T
|
|
11
|
+
inject: (prev: T) => void
|
|
12
|
+
destroy: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type GenericHMRAcceptor<T = {}> = {
|
|
16
|
+
[$HMR_ACCEPT]: HMRAccept<T>
|
|
17
|
+
}
|
|
18
|
+
type HotVar = Kaioken.FC | Store<any, any> | Signal<any> | Kaioken.Context<any>
|
|
19
|
+
|
|
20
|
+
type HotVarDesc = {
|
|
21
|
+
type: string
|
|
22
|
+
value: HotVar
|
|
23
|
+
hooks?: Array<{ name: string; args: string }>
|
|
24
|
+
link: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isGenericHmrAcceptor(
|
|
28
|
+
thing: unknown
|
|
29
|
+
): thing is GenericHMRAcceptor<any> {
|
|
30
|
+
return (
|
|
31
|
+
!!thing &&
|
|
32
|
+
(typeof thing === "object" || typeof thing === "function") &&
|
|
33
|
+
$HMR_ACCEPT in thing &&
|
|
34
|
+
typeof thing[$HMR_ACCEPT] === "object" &&
|
|
35
|
+
!!thing[$HMR_ACCEPT]
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ModuleMemory = {
|
|
40
|
+
hotVars: Map<string, HotVarDesc>
|
|
41
|
+
unnamedWatchers: Array<WatchEffect>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type HotVarRegistrationEntry = {
|
|
45
|
+
type: string
|
|
46
|
+
value: HotVar
|
|
47
|
+
link: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createHMRContext() {
|
|
51
|
+
type FilePath = string
|
|
52
|
+
const moduleMap = new Map<FilePath, ModuleMemory>()
|
|
53
|
+
let currentModuleMemory: ModuleMemory | null = null
|
|
54
|
+
let isModuleReplacementExecution = false
|
|
55
|
+
const isReplacement = () => isModuleReplacementExecution
|
|
56
|
+
let isWaitingForNextWatchCall = false
|
|
57
|
+
let tmpUnnamedWatchers: WatchEffect[] = []
|
|
58
|
+
|
|
59
|
+
const onHmrCallbacks: Array<() => void> = []
|
|
60
|
+
const onHmr = (callback: () => void) => {
|
|
61
|
+
onHmrCallbacks.push(callback)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const prepare = (filePath: string) => {
|
|
65
|
+
let mod = moduleMap.get(filePath)
|
|
66
|
+
isModuleReplacementExecution = !!mod
|
|
67
|
+
if (!mod) {
|
|
68
|
+
mod = {
|
|
69
|
+
hotVars: new Map(),
|
|
70
|
+
unnamedWatchers: [],
|
|
71
|
+
}
|
|
72
|
+
moduleMap.set(filePath, mod)
|
|
73
|
+
} else {
|
|
74
|
+
while (onHmrCallbacks.length) onHmrCallbacks.shift()!()
|
|
75
|
+
}
|
|
76
|
+
currentModuleMemory = mod!
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const register = (
|
|
80
|
+
hotVarRegistrationEntries: Record<string, HotVarRegistrationEntry>
|
|
81
|
+
) => {
|
|
82
|
+
if (currentModuleMemory === null)
|
|
83
|
+
throw new Error("[kaioken]: HMR could not register: No active module")
|
|
84
|
+
|
|
85
|
+
let dirtiedApps: Set<AppContext> = new Set()
|
|
86
|
+
for (const [name, newEntry] of Object.entries(hotVarRegistrationEntries)) {
|
|
87
|
+
const oldEntry = currentModuleMemory.hotVars.get(name)
|
|
88
|
+
|
|
89
|
+
// @ts-ignore - this is how we tell devtools what file the hotvar is from
|
|
90
|
+
newEntry.value.__devtoolsFileLink = newEntry.link
|
|
91
|
+
|
|
92
|
+
if (typeof newEntry.value === "function") {
|
|
93
|
+
if (oldEntry?.value) {
|
|
94
|
+
/**
|
|
95
|
+
* this is how, when the previous function has been stored somewhere else (eg. in a Map, or by Vike),
|
|
96
|
+
* we can trace it to its latest version
|
|
97
|
+
*/
|
|
98
|
+
// @ts-ignore
|
|
99
|
+
oldEntry.value.__next = newEntry.value
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (newEntry.type === "createStore") {
|
|
104
|
+
window.__kaioken!.stores!.add(name, newEntry.value as Store<any, any>)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
currentModuleMemory.hotVars.set(name, newEntry)
|
|
108
|
+
if (!oldEntry) continue
|
|
109
|
+
if (
|
|
110
|
+
isGenericHmrAcceptor(oldEntry.value) &&
|
|
111
|
+
isGenericHmrAcceptor(newEntry.value)
|
|
112
|
+
) {
|
|
113
|
+
newEntry.value[$HMR_ACCEPT].inject(
|
|
114
|
+
oldEntry.value[$HMR_ACCEPT].provide()
|
|
115
|
+
)
|
|
116
|
+
oldEntry.value[$HMR_ACCEPT].destroy()
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (oldEntry.type === "component" && newEntry.type === "component") {
|
|
120
|
+
window.__kaioken!.apps.forEach((ctx) => {
|
|
121
|
+
if (!ctx.mounted || !ctx.rootNode) return
|
|
122
|
+
traverseApply(ctx.rootNode, (vNode) => {
|
|
123
|
+
if (vNode.type === oldEntry.value) {
|
|
124
|
+
vNode.type = newEntry.value as any
|
|
125
|
+
dirtiedApps.add(ctx)
|
|
126
|
+
vNode.hmrUpdated = true
|
|
127
|
+
if (vNode.prev) {
|
|
128
|
+
vNode.prev.type = newEntry.value as any
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
dirtiedApps.forEach((ctx) => ctx.requestUpdate())
|
|
136
|
+
isModuleReplacementExecution = false
|
|
137
|
+
|
|
138
|
+
if (tmpUnnamedWatchers.length) {
|
|
139
|
+
let i = 0
|
|
140
|
+
for (; i < tmpUnnamedWatchers.length; i++) {
|
|
141
|
+
const newWatcher = tmpUnnamedWatchers[i]
|
|
142
|
+
const oldWatcher = currentModuleMemory.unnamedWatchers[i]
|
|
143
|
+
if (oldWatcher) {
|
|
144
|
+
newWatcher[$HMR_ACCEPT]!.inject(oldWatcher[$HMR_ACCEPT]!.provide())
|
|
145
|
+
oldWatcher[$HMR_ACCEPT]!.destroy()
|
|
146
|
+
}
|
|
147
|
+
currentModuleMemory.unnamedWatchers[i] = newWatcher
|
|
148
|
+
}
|
|
149
|
+
for (; i < currentModuleMemory.unnamedWatchers.length; i++) {
|
|
150
|
+
const oldWatcher = currentModuleMemory.unnamedWatchers[i]
|
|
151
|
+
oldWatcher[$HMR_ACCEPT]!.destroy()
|
|
152
|
+
}
|
|
153
|
+
currentModuleMemory.unnamedWatchers.length = tmpUnnamedWatchers.length
|
|
154
|
+
tmpUnnamedWatchers.length = 0
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const signals = {
|
|
159
|
+
registerNextWatch() {
|
|
160
|
+
isWaitingForNextWatchCall = true
|
|
161
|
+
},
|
|
162
|
+
isWaitingForNextWatchCall() {
|
|
163
|
+
return isWaitingForNextWatchCall
|
|
164
|
+
},
|
|
165
|
+
pushWatch(watch: WatchEffect) {
|
|
166
|
+
tmpUnnamedWatchers.push(watch)
|
|
167
|
+
isWaitingForNextWatchCall = false
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
register,
|
|
173
|
+
prepare,
|
|
174
|
+
isReplacement,
|
|
175
|
+
signals,
|
|
176
|
+
onHmr,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./useAsync.js"
|
|
2
|
+
export * from "./useCallback.js"
|
|
3
|
+
export * from "./useContext.js"
|
|
4
|
+
export * from "./useEffect.js"
|
|
5
|
+
export * from "./useEffectEvent.js"
|
|
6
|
+
export * from "./useId.js"
|
|
7
|
+
export * from "./useLayoutEffect.js"
|
|
8
|
+
export * from "./useMemo.js"
|
|
9
|
+
export * from "./useReducer.js"
|
|
10
|
+
export * from "./useRef.js"
|
|
11
|
+
export * from "./useState.js"
|
|
12
|
+
export * from "./useSyncExternalStore.js"
|
|
13
|
+
export * from "./useViewTransition.js"
|
|
14
|
+
export * from "./utils.js"
|