@vobs/runtime 1.0.0
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 +21 -0
- package/README.md +79 -0
- package/package.json +20 -0
- package/src/async-boundary.ts +97 -0
- package/src/bind.ts +47 -0
- package/src/boundaries.test.ts +59 -0
- package/src/boundary.ts +93 -0
- package/src/debug.ts +146 -0
- package/src/dynamic.test.ts +228 -0
- package/src/dynamic.ts +266 -0
- package/src/error-boundary.ts +35 -0
- package/src/error.test.ts +28 -0
- package/src/error.ts +175 -0
- package/src/events.test.ts +21 -0
- package/src/fragment.ts +81 -0
- package/src/hmr.test.ts +55 -0
- package/src/hmr.ts +123 -0
- package/src/index.ts +81 -0
- package/src/ops.ts +377 -0
- package/src/profiler.ts +45 -0
- package/src/props.test.ts +54 -0
- package/src/ref.test.ts +23 -0
- package/src/ref.ts +50 -0
- package/src/renderer.ts +21 -0
package/src/error.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Shared error protocol used by the runtime, compiler and DevTools. */
|
|
2
|
+
export type VobsErrorSeverity = 'error' | 'warning' | 'info'
|
|
3
|
+
export type VobsErrorLayer =
|
|
4
|
+
| 'compiler'
|
|
5
|
+
| 'runtime'
|
|
6
|
+
| 'constraint'
|
|
7
|
+
| 'permission'
|
|
8
|
+
| 'ssr'
|
|
9
|
+
| 'http'
|
|
10
|
+
| 'resource'
|
|
11
|
+
|
|
12
|
+
export interface VobsErrorLocation {
|
|
13
|
+
readonly file: string
|
|
14
|
+
readonly line: number
|
|
15
|
+
readonly column: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface VobsErrorOptions {
|
|
19
|
+
readonly code: string
|
|
20
|
+
readonly message: string
|
|
21
|
+
readonly severity?: VobsErrorSeverity
|
|
22
|
+
readonly layer?: VobsErrorLayer
|
|
23
|
+
readonly cause?: unknown
|
|
24
|
+
readonly fix?: string
|
|
25
|
+
readonly location?: VobsErrorLocation
|
|
26
|
+
readonly trace?: readonly string[]
|
|
27
|
+
readonly example?: string
|
|
28
|
+
readonly docs?: string
|
|
29
|
+
readonly codeFrame?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VobsErrorDefaults {
|
|
33
|
+
readonly code?: string
|
|
34
|
+
readonly severity?: VobsErrorSeverity
|
|
35
|
+
readonly layer?: VobsErrorLayer
|
|
36
|
+
readonly fix?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A framework error with stable machine-readable metadata. */
|
|
40
|
+
export class VobsError extends Error {
|
|
41
|
+
readonly code: string
|
|
42
|
+
readonly severity: VobsErrorSeverity
|
|
43
|
+
readonly layer: VobsErrorLayer
|
|
44
|
+
readonly cause?: unknown
|
|
45
|
+
readonly fix?: string
|
|
46
|
+
readonly location?: VobsErrorLocation
|
|
47
|
+
readonly trace?: readonly string[]
|
|
48
|
+
readonly example?: string
|
|
49
|
+
readonly docs?: string
|
|
50
|
+
readonly codeFrame?: string
|
|
51
|
+
|
|
52
|
+
constructor(options: VobsErrorOptions) {
|
|
53
|
+
super(options.message)
|
|
54
|
+
this.name = 'VobsError'
|
|
55
|
+
this.code = options.code
|
|
56
|
+
this.severity = options.severity ?? 'error'
|
|
57
|
+
this.layer = options.layer ?? 'runtime'
|
|
58
|
+
this.cause = options.cause
|
|
59
|
+
this.fix = options.fix
|
|
60
|
+
this.location = options.location
|
|
61
|
+
this.trace = options.trace
|
|
62
|
+
this.example = options.example
|
|
63
|
+
this.docs = options.docs
|
|
64
|
+
this.codeFrame = options.codeFrame
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createVobsError(options: VobsErrorOptions): VobsError {
|
|
69
|
+
return new VobsError(options)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isVobsError(value: unknown): value is VobsError {
|
|
73
|
+
return value instanceof VobsError
|
|
74
|
+
|| Boolean(value && typeof value === 'object'
|
|
75
|
+
&& typeof (value as { code?: unknown }).code === 'string'
|
|
76
|
+
&& typeof (value as { message?: unknown }).message === 'string'
|
|
77
|
+
&& typeof (value as { layer?: unknown }).layer === 'string')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Convert thrown strings and third-party errors without losing their message. */
|
|
81
|
+
export function normalizeVobsError(value: unknown, defaults: VobsErrorDefaults = {}): VobsError {
|
|
82
|
+
if (value instanceof VobsError) return value
|
|
83
|
+
if (value instanceof Error) {
|
|
84
|
+
// Preserve the original Error identity so boundaries and DevTools can
|
|
85
|
+
// correlate event/effect phases without creating duplicate diagnostics.
|
|
86
|
+
const metadata = value as Error & {
|
|
87
|
+
readonly vobsCode?: unknown
|
|
88
|
+
readonly vobsHint?: unknown
|
|
89
|
+
readonly vobsSource?: unknown
|
|
90
|
+
}
|
|
91
|
+
const code = defaults.code ?? (typeof metadata.vobsCode === 'string' ? metadata.vobsCode : undefined)
|
|
92
|
+
if (code) defineErrorMetadata(value, 'code', code)
|
|
93
|
+
defineErrorMetadata(value, 'severity', defaults.severity ?? 'error')
|
|
94
|
+
defineErrorMetadata(value, 'layer', defaults.layer ?? 'runtime')
|
|
95
|
+
const fix = defaults.fix ?? (typeof metadata.vobsHint === 'string' ? metadata.vobsHint : undefined)
|
|
96
|
+
if (fix) defineErrorMetadata(value, 'fix', fix)
|
|
97
|
+
const source = metadata.vobsSource
|
|
98
|
+
if (source && typeof source === 'object'
|
|
99
|
+
&& typeof (source as { file?: unknown }).file === 'string'
|
|
100
|
+
&& typeof (source as { line?: unknown }).line === 'number'
|
|
101
|
+
&& typeof (source as { column?: unknown }).column === 'number') {
|
|
102
|
+
defineErrorMetadata(value, 'location', source)
|
|
103
|
+
}
|
|
104
|
+
return value as VobsError
|
|
105
|
+
}
|
|
106
|
+
if (isVobsError(value)) {
|
|
107
|
+
const candidate = value as VobsErrorOptions & { severity?: VobsErrorSeverity; layer?: VobsErrorLayer }
|
|
108
|
+
return new VobsError({
|
|
109
|
+
code: candidate.code,
|
|
110
|
+
message: candidate.message,
|
|
111
|
+
severity: candidate.severity ?? defaults.severity,
|
|
112
|
+
layer: candidate.layer ?? defaults.layer,
|
|
113
|
+
cause: candidate.cause,
|
|
114
|
+
fix: candidate.fix ?? defaults.fix,
|
|
115
|
+
location: candidate.location,
|
|
116
|
+
trace: candidate.trace,
|
|
117
|
+
example: candidate.example,
|
|
118
|
+
docs: candidate.docs,
|
|
119
|
+
codeFrame: candidate.codeFrame
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
const message = String(value)
|
|
123
|
+
return new VobsError({
|
|
124
|
+
code: defaults.code ?? 'VOBS_UNKNOWN',
|
|
125
|
+
message,
|
|
126
|
+
severity: defaults.severity ?? 'error',
|
|
127
|
+
layer: defaults.layer ?? 'runtime',
|
|
128
|
+
cause: undefined,
|
|
129
|
+
fix: defaults.fix
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function defineErrorMetadata(target: Error, key: string, value: unknown): void {
|
|
134
|
+
if (key in target) return
|
|
135
|
+
try {
|
|
136
|
+
Object.defineProperty(target, key, { configurable: true, enumerable: false, value, writable: true })
|
|
137
|
+
} catch {
|
|
138
|
+
// Frozen third-party errors still retain their original message and stack.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface FormatVobsErrorOptions {
|
|
143
|
+
readonly environment?: 'development' | 'production'
|
|
144
|
+
readonly includeStack?: boolean
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Format a concise, actionable message for terminals and dev overlays. */
|
|
148
|
+
export function formatVobsError(
|
|
149
|
+
value: unknown,
|
|
150
|
+
options: FormatVobsErrorOptions = {}
|
|
151
|
+
): string {
|
|
152
|
+
const error = normalizeVobsError(value)
|
|
153
|
+
const code = error.code || 'VOBS_UNKNOWN'
|
|
154
|
+
const severity = error.severity || 'error'
|
|
155
|
+
if (options.environment === 'production') return `[Vobs ${code}] ${error.message}`
|
|
156
|
+
|
|
157
|
+
const lines = [`[Vobs ${capitalize(severity)}] ${error.message}`, `Code: ${code}`]
|
|
158
|
+
if (error.location) lines.push(`Location: ${error.location.file}:${error.location.line}:${error.location.column}`)
|
|
159
|
+
if (error.codeFrame) lines.push('', error.codeFrame)
|
|
160
|
+
if (error.cause !== undefined) lines.push(`Cause: ${formatCause(error.cause)}`)
|
|
161
|
+
if (error.trace?.length) lines.push('', `Trace: ${error.trace.join(' → ')}`)
|
|
162
|
+
if (error.fix) lines.push('', `Fix: ${error.fix}`)
|
|
163
|
+
if (error.example) lines.push('', `Example:\n${error.example}`)
|
|
164
|
+
if (error.docs) lines.push(`Docs: ${error.docs}`)
|
|
165
|
+
if (options.includeStack && error.stack) lines.push('', error.stack)
|
|
166
|
+
return lines.join('\n')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatCause(value: unknown): string {
|
|
170
|
+
return value instanceof Error ? `${value.name}: ${value.message}` : String(value)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function capitalize(value: string): string {
|
|
174
|
+
return value.slice(0, 1).toUpperCase() + value.slice(1)
|
|
175
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { createDOMRenderer, setRenderer } from '@vobs/vobs'
|
|
4
|
+
import { addEventListener, createElement, removeEventListener } from './index'
|
|
5
|
+
|
|
6
|
+
describe('event binding dedupe', () => {
|
|
7
|
+
it('replaces a previous binding for the same node and event', () => {
|
|
8
|
+
setRenderer(createDOMRenderer())
|
|
9
|
+
const node = createElement('button')
|
|
10
|
+
let first = 0
|
|
11
|
+
let second = 0
|
|
12
|
+
addEventListener(node, 'click', () => { first++ })
|
|
13
|
+
addEventListener(node, 'click', () => { second++ })
|
|
14
|
+
node.dispatchEvent(new MouseEvent('click'))
|
|
15
|
+
expect(first).toBe(0)
|
|
16
|
+
expect(second).toBe(1)
|
|
17
|
+
removeEventListener(node, 'click', (() => undefined) as EventListener)
|
|
18
|
+
node.dispatchEvent(new MouseEvent('click'))
|
|
19
|
+
expect(second).toBe(1)
|
|
20
|
+
})
|
|
21
|
+
})
|
package/src/fragment.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getCurrentOwner } from '@vobs/reactivity'
|
|
2
|
+
import { createComment, getRenderer } from './ops'
|
|
3
|
+
|
|
4
|
+
export interface VobsFragment {
|
|
5
|
+
readonly kind: 'vobs-fragment'
|
|
6
|
+
readonly start: Node
|
|
7
|
+
readonly end: Node
|
|
8
|
+
readonly mount: (parent: Node, anchor: Node | null) => void
|
|
9
|
+
readonly unmount: (parent: Node) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type VobsNode = Node | VobsFragment
|
|
13
|
+
export type FragmentFactory = (parent: Node, anchor: Node) => void
|
|
14
|
+
|
|
15
|
+
export function createFragment(factory: FragmentFactory): VobsFragment {
|
|
16
|
+
const start = createComment('vobs:fragment:start')
|
|
17
|
+
const end = createComment('vobs:fragment:end')
|
|
18
|
+
let parent: Node | null = null
|
|
19
|
+
let initialized = false
|
|
20
|
+
const owner = getCurrentOwner()
|
|
21
|
+
|
|
22
|
+
const fragment: VobsFragment = {
|
|
23
|
+
kind: 'vobs-fragment',
|
|
24
|
+
start,
|
|
25
|
+
end,
|
|
26
|
+
mount(nextParent, anchor): void {
|
|
27
|
+
if (parent && parent !== nextParent) {
|
|
28
|
+
throw new Error('Vobs Fragment: 不能跨父节点移动 Fragment')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (initialized) {
|
|
32
|
+
moveRange(nextParent, start, end, anchor)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const renderer = getRenderer()
|
|
37
|
+
renderer.insertBefore(nextParent, start, anchor)
|
|
38
|
+
renderer.insertBefore(nextParent, end, anchor)
|
|
39
|
+
parent = nextParent
|
|
40
|
+
initialized = true
|
|
41
|
+
if (owner) owner.run(() => factory(nextParent, end))
|
|
42
|
+
else factory(nextParent, end)
|
|
43
|
+
},
|
|
44
|
+
unmount(nextParent): void {
|
|
45
|
+
if (!initialized || parent !== nextParent) {
|
|
46
|
+
throw new Error('Vobs Fragment: Fragment 不属于指定父节点')
|
|
47
|
+
}
|
|
48
|
+
const renderer = getRenderer()
|
|
49
|
+
let current = renderer.nextSibling(start)
|
|
50
|
+
while (current && current !== end) {
|
|
51
|
+
const next = renderer.nextSibling(current)
|
|
52
|
+
renderer.removeChild(nextParent, current)
|
|
53
|
+
current = next
|
|
54
|
+
}
|
|
55
|
+
renderer.removeChild(nextParent, start)
|
|
56
|
+
renderer.removeChild(nextParent, end)
|
|
57
|
+
parent = null
|
|
58
|
+
initialized = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return fragment
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isVobsFragment(value: unknown): value is VobsFragment {
|
|
65
|
+
return Boolean(value) && typeof value === 'object' && (value as VobsFragment).kind === 'vobs-fragment'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function moveRange(parent: Node, start: Node, end: Node, anchor: Node | null): void {
|
|
69
|
+
const renderer = getRenderer()
|
|
70
|
+
const nodes: Node[] = [start]
|
|
71
|
+
let current = renderer.nextSibling(start)
|
|
72
|
+
while (current) {
|
|
73
|
+
nodes.push(current)
|
|
74
|
+
if (current === end) break
|
|
75
|
+
current = renderer.nextSibling(current)
|
|
76
|
+
}
|
|
77
|
+
if (nodes[nodes.length - 1] !== end) {
|
|
78
|
+
throw new Error('Vobs Fragment: 找不到结束锚点')
|
|
79
|
+
}
|
|
80
|
+
for (const node of nodes) renderer.insertBefore(parent, node, anchor)
|
|
81
|
+
}
|
package/src/hmr.test.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
createHmrStateStore,
|
|
4
|
+
disposeHmrModule,
|
|
5
|
+
resolveComponent,
|
|
6
|
+
updateHmrModule
|
|
7
|
+
} from './hmr'
|
|
8
|
+
import { createComponent, insertBefore, setRenderer } from './ops'
|
|
9
|
+
|
|
10
|
+
describe('runtime HMR', () => {
|
|
11
|
+
it('刷新已挂载的组件节点时复用原 Owner', () => {
|
|
12
|
+
setRenderer<Node, Text, Element, Comment>({
|
|
13
|
+
createText: content => document.createTextNode(content),
|
|
14
|
+
createElement: tag => document.createElement(tag),
|
|
15
|
+
createComment: content => document.createComment(content),
|
|
16
|
+
insertBefore: (parent, child, anchor) => { parent.insertBefore(child, anchor) },
|
|
17
|
+
removeChild: (parent, child) => { parent.removeChild(child) },
|
|
18
|
+
setTextContent: (node, content) => { node.textContent = content },
|
|
19
|
+
setProperty: (node, key, value) => { (node as unknown as Record<string, unknown>)[key] = value },
|
|
20
|
+
setAttribute: (node, key, value) => { node.setAttribute(key, value) },
|
|
21
|
+
addEventListener: (node, event, handler) => { node.addEventListener(event, handler) },
|
|
22
|
+
removeEventListener: (node, event, handler) => { node.removeEventListener(event, handler) },
|
|
23
|
+
nextSibling: node => node.nextSibling,
|
|
24
|
+
clear: container => {
|
|
25
|
+
while (container.childNodes.length > 0) {
|
|
26
|
+
const child = container.childNodes[0]
|
|
27
|
+
if (child) container.removeChild(child)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
const moduleId = `hmr-mounted-${Date.now()}-${Math.random()}`
|
|
32
|
+
const first = resolveComponent(() => document.createTextNode('first'), moduleId, 'Panel')
|
|
33
|
+
const node = createComponent(first, {})
|
|
34
|
+
const parent = document.createElement('div')
|
|
35
|
+
insertBefore(parent, node, null)
|
|
36
|
+
|
|
37
|
+
updateHmrModule(moduleId, { Panel: () => document.createTextNode('second') })
|
|
38
|
+
|
|
39
|
+
expect(parent.textContent).toBe('second')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('更新组件代理时保留已有引用,并保留模块状态', () => {
|
|
43
|
+
const moduleId = `hmr-test-${Date.now()}-${Math.random()}`
|
|
44
|
+
const first = resolveComponent(() => ({ textContent: 'first' } as unknown as Node), moduleId, 'Panel')
|
|
45
|
+
const store = createHmrStateStore(moduleId)
|
|
46
|
+
store.set('count', 3)
|
|
47
|
+
|
|
48
|
+
updateHmrModule(moduleId, { Panel: () => ({ textContent: 'second' } as unknown as Node) })
|
|
49
|
+
|
|
50
|
+
expect(first({})).toHaveProperty('textContent', 'second')
|
|
51
|
+
expect(store.get('count', 0)).toBe(3)
|
|
52
|
+
disposeHmrModule(moduleId)
|
|
53
|
+
expect(createHmrStateStore(moduleId).get('count', 0)).toBe(3)
|
|
54
|
+
})
|
|
55
|
+
})
|
package/src/hmr.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { VobsNode } from './fragment'
|
|
2
|
+
|
|
3
|
+
export type HmrComponent<Props extends object = Record<string, unknown>> =
|
|
4
|
+
(props: Props) => VobsNode
|
|
5
|
+
|
|
6
|
+
export interface HmrStateStore {
|
|
7
|
+
get<T>(key: string, initial: T | (() => T)): T
|
|
8
|
+
set<T>(key: string, value: T): void
|
|
9
|
+
has(key: string): boolean
|
|
10
|
+
delete(key: string): void
|
|
11
|
+
clear(): void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface HmrInstance {
|
|
15
|
+
node: VobsNode
|
|
16
|
+
parent: Node | null
|
|
17
|
+
refresh(): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface HmrModuleState {
|
|
21
|
+
readonly components: Map<string, HmrComponent>
|
|
22
|
+
readonly state: Map<string, unknown>
|
|
23
|
+
readonly instances: Set<HmrInstance>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface HmrGlobal {
|
|
27
|
+
modules: Map<string, HmrModuleState>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const globalTarget = globalThis as typeof globalThis & { __VOBS_HMR__?: HmrGlobal }
|
|
31
|
+
const hmrGlobal = globalTarget.__VOBS_HMR__ ?? { modules: new Map<string, HmrModuleState>() }
|
|
32
|
+
globalTarget.__VOBS_HMR__ = hmrGlobal
|
|
33
|
+
|
|
34
|
+
export function resolveComponent<Props extends object>(
|
|
35
|
+
component: HmrComponent<Props>,
|
|
36
|
+
moduleId: string,
|
|
37
|
+
exportName: string
|
|
38
|
+
): HmrComponent<Props> {
|
|
39
|
+
const module = getModule(moduleId)
|
|
40
|
+
const existing = module.components.get(exportName)
|
|
41
|
+
if (existing) return existing as HmrComponent<Props>
|
|
42
|
+
|
|
43
|
+
const proxy = ((props: Props) => {
|
|
44
|
+
const current = (proxy as HmrComponent<Props> & { current: HmrComponent<Props> }).current
|
|
45
|
+
return current(props)
|
|
46
|
+
}) as HmrComponent<Props> & { current: HmrComponent<Props> }
|
|
47
|
+
proxy.current = component
|
|
48
|
+
Object.defineProperties(proxy, {
|
|
49
|
+
displayName: { configurable: true, value: component.name || exportName },
|
|
50
|
+
hmrKey: { configurable: false, value: `${moduleId}:${exportName}` }
|
|
51
|
+
})
|
|
52
|
+
module.components.set(exportName, proxy as HmrComponent)
|
|
53
|
+
return proxy
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function updateHmrModule(_moduleId: string, nextModule: Record<string, unknown>): void {
|
|
57
|
+
const modules = [...hmrGlobal.modules.values()]
|
|
58
|
+
for (const module of modules) {
|
|
59
|
+
let changed = false
|
|
60
|
+
for (const [name, proxy] of module.components) {
|
|
61
|
+
const next = nextModule[name]
|
|
62
|
+
if (typeof next !== 'function') continue
|
|
63
|
+
const hmrProxy = proxy as HmrComponent & { current: HmrComponent; displayName?: string }
|
|
64
|
+
hmrProxy.current = next as HmrComponent
|
|
65
|
+
Object.defineProperty(hmrProxy, 'displayName', { configurable: true, value: next.name || name })
|
|
66
|
+
changed = true
|
|
67
|
+
}
|
|
68
|
+
if (!changed) continue
|
|
69
|
+
for (const instance of module.instances) {
|
|
70
|
+
try {
|
|
71
|
+
instance.refresh()
|
|
72
|
+
} catch {
|
|
73
|
+
// HMR failures remain application errors on the next normal render.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function disposeHmrModule(_moduleId: string): void {
|
|
80
|
+
// State and component proxies intentionally survive module disposal.
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createHmrStateStore(moduleId: string): HmrStateStore {
|
|
84
|
+
const state = getModule(moduleId).state
|
|
85
|
+
return {
|
|
86
|
+
get<T>(key: string, initial: T | (() => T)): T {
|
|
87
|
+
if (!state.has(key)) state.set(key, typeof initial === 'function' ? (initial as () => T)() : initial)
|
|
88
|
+
return state.get(key) as T
|
|
89
|
+
},
|
|
90
|
+
set<T>(key: string, value: T): void {
|
|
91
|
+
state.set(key, value)
|
|
92
|
+
},
|
|
93
|
+
has: key => state.has(key),
|
|
94
|
+
delete: key => { state.delete(key) },
|
|
95
|
+
clear: () => { state.clear() }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function registerHmrInstance(moduleId: string, instance: HmrInstance): () => void {
|
|
100
|
+
const instances = getModule(moduleId).instances
|
|
101
|
+
instances.add(instance)
|
|
102
|
+
return () => instances.delete(instance)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function markHmrInstanceMounted(node: VobsNode, parent: Node): void {
|
|
106
|
+
const instance = hmrInstances.get(node as object)
|
|
107
|
+
if (instance) instance.parent = parent
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getModule(moduleId: string): HmrModuleState {
|
|
111
|
+
let module = hmrGlobal.modules.get(moduleId)
|
|
112
|
+
if (!module) {
|
|
113
|
+
module = { components: new Map(), state: new Map(), instances: new Set() }
|
|
114
|
+
hmrGlobal.modules.set(moduleId, module)
|
|
115
|
+
}
|
|
116
|
+
return module
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hmrInstances = new WeakMap<object, HmrInstance>()
|
|
120
|
+
|
|
121
|
+
export function associateHmrInstance(node: VobsNode, instance: HmrInstance): void {
|
|
122
|
+
hmrInstances.set(node as object, instance)
|
|
123
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type { VobsRenderer } from './renderer'
|
|
2
|
+
export {
|
|
3
|
+
getRuntimeDebugContext,
|
|
4
|
+
getRuntimeDebugHooks,
|
|
5
|
+
invokeRuntimeDebug,
|
|
6
|
+
pushRuntimeDebugContext,
|
|
7
|
+
runWithRuntimeDebugContext,
|
|
8
|
+
setRuntimeDebugHooks,
|
|
9
|
+
describeDebugNode
|
|
10
|
+
} from './debug'
|
|
11
|
+
export type {
|
|
12
|
+
RuntimeDebugContext,
|
|
13
|
+
RuntimeDebugEnvironment,
|
|
14
|
+
RuntimeDebugHooks,
|
|
15
|
+
RuntimeDomMutation,
|
|
16
|
+
RuntimeDomMutationOperation,
|
|
17
|
+
RuntimeErrorEvent,
|
|
18
|
+
RuntimeHydrationMismatch
|
|
19
|
+
} from './debug'
|
|
20
|
+
export { createFragment, isVobsFragment } from './fragment'
|
|
21
|
+
export type { FragmentFactory, VobsFragment, VobsNode } from './fragment'
|
|
22
|
+
export {
|
|
23
|
+
setRenderer,
|
|
24
|
+
getRenderer,
|
|
25
|
+
createText,
|
|
26
|
+
createElement,
|
|
27
|
+
createComment,
|
|
28
|
+
insertBefore,
|
|
29
|
+
removeChild,
|
|
30
|
+
setTextContent,
|
|
31
|
+
setProperty,
|
|
32
|
+
setAttribute,
|
|
33
|
+
spreadProps,
|
|
34
|
+
setStaticProps,
|
|
35
|
+
addEventListener,
|
|
36
|
+
removeEventListener,
|
|
37
|
+
clear,
|
|
38
|
+
createComponent,
|
|
39
|
+
createBlock,
|
|
40
|
+
disposeNodeOwner
|
|
41
|
+
} from './ops'
|
|
42
|
+
export { bindText, bindAttribute, bindProperty } from './bind'
|
|
43
|
+
export type { ValueSource } from './bind'
|
|
44
|
+
export { ref, setRef } from './ref'
|
|
45
|
+
export type { Ref, RefTarget } from './ref'
|
|
46
|
+
export { insertDynamic, insertDynamicValue, insertList, normalizeDynamicChild } from './dynamic'
|
|
47
|
+
export type { DynamicChild, NodeFactory } from './dynamic'
|
|
48
|
+
export type { VobsLocatedError, VobsSourceLocation } from './ops'
|
|
49
|
+
export {
|
|
50
|
+
VobsError,
|
|
51
|
+
createVobsError,
|
|
52
|
+
formatVobsError,
|
|
53
|
+
isVobsError,
|
|
54
|
+
normalizeVobsError
|
|
55
|
+
} from './error'
|
|
56
|
+
export type {
|
|
57
|
+
FormatVobsErrorOptions,
|
|
58
|
+
VobsErrorDefaults,
|
|
59
|
+
VobsErrorLayer,
|
|
60
|
+
VobsErrorLocation,
|
|
61
|
+
VobsErrorOptions,
|
|
62
|
+
VobsErrorSeverity
|
|
63
|
+
} from './error'
|
|
64
|
+
export {
|
|
65
|
+
createHmrStateStore,
|
|
66
|
+
disposeHmrModule,
|
|
67
|
+
markHmrInstanceMounted,
|
|
68
|
+
registerHmrInstance,
|
|
69
|
+
resolveComponent,
|
|
70
|
+
updateHmrModule
|
|
71
|
+
} from './hmr'
|
|
72
|
+
export type { HmrComponent, HmrInstance, HmrStateStore } from './hmr'
|
|
73
|
+
export { insertErrorBoundary } from './error-boundary'
|
|
74
|
+
export { ErrorBoundary } from './error-boundary'
|
|
75
|
+
export type { ErrorBoundaryFallback, ErrorBoundaryOptions, ErrorBoundaryProps } from './error-boundary'
|
|
76
|
+
export { insertBoundary } from './boundary'
|
|
77
|
+
export type { BoundaryFallback, BoundaryOptions, BoundaryRetry } from './boundary'
|
|
78
|
+
export { insertAsyncBoundary, AsyncBoundary } from './async-boundary'
|
|
79
|
+
export type { AsyncBoundaryFallback, AsyncBoundaryOptions, AsyncBoundaryProps, AsyncBoundaryView } from './async-boundary'
|
|
80
|
+
export { insertProfiler, Profiler } from './profiler'
|
|
81
|
+
export type { ProfilerOptions, ProfilerProps, ProfilerRenderInfo } from './profiler'
|