@vobs/vobs 0.2.0 → 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 +1 -1
- package/README.md +89 -0
- package/package.json +15 -46
- package/src/app.test.ts +245 -0
- package/src/app.ts +264 -0
- package/src/context.ts +54 -0
- package/src/index.ts +88 -0
- package/src/jsx-dev-runtime.ts +58 -0
- package/src/jsx-runtime.ts +1 -0
- package/src/jsx.d.ts +96 -0
- package/src/types.test.ts +26 -0
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -11
- package/dist/reactivity.d.ts +0 -9
- package/dist/reactivity.js +0 -9
- package/dist/router.d.ts +0 -9
- package/dist/router.js +0 -9
- package/dist/runtime-dom.d.ts +0 -9
- package/dist/runtime-dom.js +0 -9
package/LICENSE
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @vobs/vobs
|
|
2
|
+
|
|
3
|
+
The umbrella package of the vobs framework: reactivity + runtime + DOM renderer in a single import, plus application bootstrap (`createVobs`), context (`provide`/`inject`), and the JSX runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vobs/vobs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Pair with [`@vobs/vite-plugin`](../vite-plugin) to compile TSX.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { createVobs, createInjectionKey, state, inject, provide } from '@vobs/vobs'
|
|
17
|
+
|
|
18
|
+
const GreetingKey = createInjectionKey<string>('greeting')
|
|
19
|
+
|
|
20
|
+
function Hello() {
|
|
21
|
+
const greeting = injectRequired(GreetingKey)
|
|
22
|
+
return <h1>{greeting}</h1>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const app = createVobs({
|
|
26
|
+
render: () => <Hello />,
|
|
27
|
+
plugins: []
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
app.mount('#app') // or app.hydrate('#app') for SSR markup
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Plugins receive an app context and can provide values, register router/auth/etc., and return a cleanup function that runs on `app.destroy()`.
|
|
34
|
+
|
|
35
|
+
### Context
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { createInjectionKey, provide, inject } from '@vobs/vobs'
|
|
39
|
+
|
|
40
|
+
const ThemeKey = createInjectionKey<'dark' | 'light'>('theme')
|
|
41
|
+
|
|
42
|
+
function Toolbar() {
|
|
43
|
+
provide(ThemeKey, 'dark') // scoped to the current owner subtree
|
|
44
|
+
const theme = inject(ThemeKey, 'light') // read with fallback
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Reactivity and rendering
|
|
49
|
+
|
|
50
|
+
Everything from `@vobs/reactivity` (`state`, `memo`, `effect`, `batch`, owner APIs) and the rendering primitives from `@vobs/runtime` (`insertDynamic`, `insertList`, boundaries, refs) are re-exported, with the DOM renderer pre-registered:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { state, ErrorBoundary } from '@vobs/vobs'
|
|
54
|
+
|
|
55
|
+
function Counter() {
|
|
56
|
+
const count = state(0)
|
|
57
|
+
return <button onClick={() => count.value++}>Count: {count.value}</button>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
<ErrorBoundary fallback={error => <span>failed: {error.message}</span>}>
|
|
61
|
+
<Counter />
|
|
62
|
+
</ErrorBoundary>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## App lifecycle
|
|
66
|
+
|
|
67
|
+
| Member | Description |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `app.mount(target)` | Clears the target and renders into it. |
|
|
70
|
+
| `app.hydrate(target)` | Claims server-rendered markup instead of clearing it; mismatched nodes are replaced with a reported error. |
|
|
71
|
+
| `app.update()` | Flushes pending scheduler work synchronously (used by HMR and tests). |
|
|
72
|
+
| `app.use(plugin)` | Installs a plugin before or after mount. |
|
|
73
|
+
| `app.provide(key, value, options?)` | Provides a value into the root owner. |
|
|
74
|
+
| `app.destroy()` | Runs plugin cleanups, then disposes the owner tree and clears the container. |
|
|
75
|
+
|
|
76
|
+
## API highlights
|
|
77
|
+
|
|
78
|
+
| Signature | Description |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `createVobs(options)` | Creates an app (`render`, `plugins`). |
|
|
81
|
+
| `createInjectionKey(description)` | Typed injection key (`Symbol`-based). |
|
|
82
|
+
| `provide(key, value, options?)` / `inject(key, fallback?)` / `injectRequired(key)` | Owner-scoped context. |
|
|
83
|
+
| `Fragment` / `Vobs` | JSX fragment and runtime entry points (`jsx-runtime`, `jsx-dev-runtime`). |
|
|
84
|
+
| `createDOMRenderer()` | The built-in DOM renderer. |
|
|
85
|
+
| re-exports | Full `@vobs/reactivity` + runtime ops, bindings, lists, boundaries, HMR. |
|
|
86
|
+
|
|
87
|
+
## Types
|
|
88
|
+
|
|
89
|
+
`VobsApp`, `VobsPlugin`, `VobsPluginContext`, `InjectionKey<T>`, plus all re-exported reactivity/runtime types.
|
package/package.json
CHANGED
|
@@ -1,54 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@vobs/vobs",
|
|
3
|
-
"version": "0.2.0",
|
|
4
|
-
"description": "The default application entry for the vobs frontend framework.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"publishConfig": {
|
|
7
|
-
"access": "public"
|
|
8
|
-
},
|
|
9
2
|
"license": "MIT",
|
|
10
|
-
"author": "vobsjs",
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/vobsjs/vobs.git",
|
|
14
|
-
"directory": "packages/framework/vobs"
|
|
15
|
-
},
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/vobsjs/vobs/issues"
|
|
18
|
-
},
|
|
19
|
-
"homepage": "https://github.com/vobsjs/vobs#readme",
|
|
20
|
-
"dependencies": {
|
|
21
|
-
"@vobs/router": "0.1.0",
|
|
22
|
-
"@vobs/reactivity": "0.1.0",
|
|
23
|
-
"@vobs/runtime-dom": "0.1.0"
|
|
24
|
-
},
|
|
25
3
|
"files": [
|
|
26
|
-
"
|
|
4
|
+
"src",
|
|
5
|
+
"README.md",
|
|
6
|
+
"LICENSE"
|
|
27
7
|
],
|
|
8
|
+
"name": "@vobs/vobs",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "src/index.ts",
|
|
12
|
+
"types": "src/index.ts",
|
|
28
13
|
"exports": {
|
|
29
|
-
".":
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
"./reactivity": {
|
|
34
|
-
"types": "./dist/reactivity.d.ts",
|
|
35
|
-
"import": "./dist/reactivity.js"
|
|
36
|
-
},
|
|
37
|
-
"./router": {
|
|
38
|
-
"types": "./dist/router.d.ts",
|
|
39
|
-
"import": "./dist/router.js"
|
|
40
|
-
},
|
|
41
|
-
"./runtime-dom": {
|
|
42
|
-
"types": "./dist/runtime-dom.d.ts",
|
|
43
|
-
"import": "./dist/runtime-dom.js"
|
|
44
|
-
},
|
|
45
|
-
"./package.json": "./package.json"
|
|
14
|
+
".": "./src/index.ts",
|
|
15
|
+
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
16
|
+
"./jsx-dev-runtime": "./src/jsx-dev-runtime.ts"
|
|
46
17
|
},
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=20.19.0"
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@vobs/reactivity": "1.0.0",
|
|
20
|
+
"@vobs/dom": "1.0.0",
|
|
21
|
+
"@vobs/runtime": "1.0.0"
|
|
53
22
|
}
|
|
54
23
|
}
|
package/src/app.test.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { effect } from '@vobs/reactivity'
|
|
3
|
+
import {
|
|
4
|
+
createDOMRenderer,
|
|
5
|
+
createComponent,
|
|
6
|
+
createInjectionKey,
|
|
7
|
+
createElement,
|
|
8
|
+
createText,
|
|
9
|
+
createVobs,
|
|
10
|
+
inject,
|
|
11
|
+
injectRequired,
|
|
12
|
+
provide,
|
|
13
|
+
setRenderer,
|
|
14
|
+
addEventListener,
|
|
15
|
+
type VobsLocatedError,
|
|
16
|
+
type VobsPlugin
|
|
17
|
+
} from './index'
|
|
18
|
+
|
|
19
|
+
describe('createVobs', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
setRenderer(createDOMRenderer())
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('没有 render 时抛错', () => {
|
|
25
|
+
expect(() => createVobs({} as any)).toThrow()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('支持应用级错误观察器且不会覆盖原始异常', () => {
|
|
29
|
+
const errors: unknown[] = []
|
|
30
|
+
const app = createVobs({
|
|
31
|
+
render: () => { throw new Error('render failed') },
|
|
32
|
+
onError: error => errors.push(error)
|
|
33
|
+
})
|
|
34
|
+
expect(() => app.mount(document.createElement('div'))).toThrow('render failed')
|
|
35
|
+
expect(errors[0]).toBeInstanceOf(Error)
|
|
36
|
+
expect((errors[0] as Error).message).toBe('render failed')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('挂载后 mounted 为 true', () => {
|
|
40
|
+
const app = createVobs({
|
|
41
|
+
render: () => {
|
|
42
|
+
return createText('hello')
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const container = document.createElement('div')
|
|
47
|
+
app.mount(container)
|
|
48
|
+
|
|
49
|
+
expect(app.mounted).toBe(true)
|
|
50
|
+
expect(container.textContent).toBe('hello')
|
|
51
|
+
|
|
52
|
+
app.destroy()
|
|
53
|
+
expect(app.mounted).toBe(false)
|
|
54
|
+
expect(container.innerHTML).toBe('')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('按依赖顺序安装插件,并逆序清理', () => {
|
|
58
|
+
const calls: string[] = []
|
|
59
|
+
const dependency = {
|
|
60
|
+
name: 'dependency',
|
|
61
|
+
install: () => {
|
|
62
|
+
calls.push('install-dependency')
|
|
63
|
+
return () => calls.push('cleanup-dependency')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const plugin: VobsPlugin = {
|
|
67
|
+
name: 'plugin',
|
|
68
|
+
requires: [dependency],
|
|
69
|
+
install: () => {
|
|
70
|
+
calls.push('install-plugin')
|
|
71
|
+
return () => calls.push('cleanup-plugin')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const app = createVobs({ render: () => createText(''), plugins: [plugin] })
|
|
76
|
+
app.destroy()
|
|
77
|
+
|
|
78
|
+
expect(calls).toEqual([
|
|
79
|
+
'install-dependency',
|
|
80
|
+
'install-plugin',
|
|
81
|
+
'cleanup-plugin',
|
|
82
|
+
'cleanup-dependency'
|
|
83
|
+
])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('注入项默认不可被静默覆盖', () => {
|
|
87
|
+
const key = createInjectionKey<string>('test.value')
|
|
88
|
+
const plugin: VobsPlugin = {
|
|
89
|
+
name: 'context',
|
|
90
|
+
install: context => {
|
|
91
|
+
context.provide(key, 'first')
|
|
92
|
+
expect(() => context.provide(key, 'second')).toThrow('已存在')
|
|
93
|
+
context.provide(key, 'second', { override: true })
|
|
94
|
+
expect(context.inject(key)).toBe('second')
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
createVobs({ render: () => createText(''), plugins: [plugin] }).destroy()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('插件可访问 app、注册销毁回调并声明可选依赖', () => {
|
|
102
|
+
const calls: string[] = []
|
|
103
|
+
let contextApp: ReturnType<typeof createVobs> | undefined
|
|
104
|
+
const optional: VobsPlugin = {
|
|
105
|
+
name: 'optional',
|
|
106
|
+
install: () => { calls.push('optional-install') }
|
|
107
|
+
}
|
|
108
|
+
const plugin: VobsPlugin = {
|
|
109
|
+
name: 'lifecycle',
|
|
110
|
+
version: '0.1.0',
|
|
111
|
+
optional: [optional],
|
|
112
|
+
install(context) {
|
|
113
|
+
contextApp = context.app
|
|
114
|
+
context.onDestroy(() => { calls.push('on-destroy') })
|
|
115
|
+
return () => { calls.push('cleanup') }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const app = createVobs({ render: () => createText(''), plugins: [plugin] })
|
|
120
|
+
expect(contextApp).toBe(app)
|
|
121
|
+
expect(calls).toEqual([])
|
|
122
|
+
|
|
123
|
+
app.destroy()
|
|
124
|
+
expect(calls).toEqual(['cleanup', 'on-destroy'])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('组件 provide/inject 按 Owner 层级查找,并可覆盖父级值', () => {
|
|
128
|
+
const key = createInjectionKey<string>('test.component-context')
|
|
129
|
+
let injected: string | undefined
|
|
130
|
+
let nestedInjected: string | undefined
|
|
131
|
+
|
|
132
|
+
function Child(): ReturnType<typeof createText> {
|
|
133
|
+
injected = inject(key)
|
|
134
|
+
provide(key, 'child')
|
|
135
|
+
nestedInjected = inject(key)
|
|
136
|
+
return createText('child')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function Parent(): ReturnType<typeof createText> {
|
|
140
|
+
provide(key, 'parent')
|
|
141
|
+
return createComponent(Child, {}) as ReturnType<typeof createText>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const app = createVobs({ render: () => createComponent(Parent, {}) })
|
|
145
|
+
app.mount(document.createElement('div'))
|
|
146
|
+
|
|
147
|
+
expect(injected).toBe('parent')
|
|
148
|
+
expect(nestedInjected).toBe('child')
|
|
149
|
+
app.destroy()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('组件外调用 provide 会给出作用域错误', () => {
|
|
153
|
+
const key = createInjectionKey<string>('test.outside-context')
|
|
154
|
+
expect(() => provide(key, 'value')).toThrow('Owner 作用域')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('inject 支持默认值和必需注入校验', () => {
|
|
158
|
+
const key = createInjectionKey<string>('test.optional')
|
|
159
|
+
expect(inject(key, 'fallback')).toBe('fallback')
|
|
160
|
+
expect(() => injectRequired(key, 'test.optional')).toThrow('必需注入项')
|
|
161
|
+
const app = createVobs({
|
|
162
|
+
render: () => {
|
|
163
|
+
provide(key, 'provided')
|
|
164
|
+
return createText(`${injectRequired(key)}:${inject(key, 'fallback')}`)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
const container = document.createElement('div')
|
|
168
|
+
app.mount(container)
|
|
169
|
+
expect(container.textContent).toBe('provided:provided')
|
|
170
|
+
app.destroy()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('插件可以观察启动错误,但不能吞掉原始错误', () => {
|
|
174
|
+
const failure = new Error('install failed')
|
|
175
|
+
let reported: unknown
|
|
176
|
+
const observer: VobsPlugin = {
|
|
177
|
+
name: 'observer',
|
|
178
|
+
install(context) {
|
|
179
|
+
context.onError(error => { reported = error })
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const broken: VobsPlugin = {
|
|
183
|
+
name: 'broken',
|
|
184
|
+
install() {
|
|
185
|
+
throw failure
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(() => createVobs({ render: () => createText(''), plugins: [observer, broken] }))
|
|
190
|
+
.toThrow(failure)
|
|
191
|
+
expect(reported).toBe(failure)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('组件错误保留编译器提供的源码位置', () => {
|
|
195
|
+
const failure = new Error('render failed')
|
|
196
|
+
const app = createVobs({
|
|
197
|
+
render: () => createComponent(() => { throw failure }, {}, {
|
|
198
|
+
file: 'src/Page.tsx',
|
|
199
|
+
line: 12,
|
|
200
|
+
column: 7
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
expect(() => app.mount(document.createElement('div'))).toThrow(failure)
|
|
205
|
+
expect((failure as VobsLocatedError).vobsSource).toEqual({
|
|
206
|
+
file: 'src/Page.tsx',
|
|
207
|
+
line: 12,
|
|
208
|
+
column: 7
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('组件 effect 错误也保留源码位置并继续向应用传播', () => {
|
|
213
|
+
const failure = new Error('effect failed')
|
|
214
|
+
const app = createVobs({
|
|
215
|
+
render: () => createComponent(() => {
|
|
216
|
+
effect(() => { throw failure })
|
|
217
|
+
return createText('ready')
|
|
218
|
+
}, {}, { file: 'src/EffectPage.tsx', line: 8, column: 3 })
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(() => app.mount(document.createElement('div'))).toThrow(failure)
|
|
222
|
+
expect((failure as VobsLocatedError).vobsSource).toEqual({
|
|
223
|
+
file: 'src/EffectPage.tsx',
|
|
224
|
+
line: 8,
|
|
225
|
+
column: 3
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('组件 Owner 销毁时自动移除事件监听器', () => {
|
|
230
|
+
let button!: HTMLButtonElement
|
|
231
|
+
let clicks = 0
|
|
232
|
+
const app = createVobs({
|
|
233
|
+
render: () => createComponent(() => {
|
|
234
|
+
button = createElement('button') as HTMLButtonElement
|
|
235
|
+
addEventListener(button, 'click', () => { clicks++ })
|
|
236
|
+
return button
|
|
237
|
+
}, {})
|
|
238
|
+
})
|
|
239
|
+
app.mount(document.createElement('div'))
|
|
240
|
+
button.dispatchEvent(new Event('click'))
|
|
241
|
+
app.destroy()
|
|
242
|
+
button.dispatchEvent(new Event('click'))
|
|
243
|
+
expect(clicks).toBe(1)
|
|
244
|
+
})
|
|
245
|
+
})
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { createOwner, scheduler, setOwnerDebugName, type Owner } from '@vobs/reactivity'
|
|
2
|
+
import { createDOMRenderer } from '@vobs/dom'
|
|
3
|
+
import { insertBefore as insertRuntimeNode, setRenderer, type VobsFragment, type VobsRenderer } from '@vobs/runtime'
|
|
4
|
+
import { injectFromOwner, provideToOwner } from './context'
|
|
5
|
+
|
|
6
|
+
export type InjectionKey<T> = symbol & {
|
|
7
|
+
readonly __vobsType?: T
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProvideOptions {
|
|
11
|
+
override?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface VobsPlugin {
|
|
15
|
+
name: string
|
|
16
|
+
version?: string
|
|
17
|
+
requires?: readonly VobsPlugin[]
|
|
18
|
+
optional?: readonly VobsPlugin[]
|
|
19
|
+
install?: (ctx: VobsContext) => void | (() => void)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VobsContext {
|
|
23
|
+
readonly app: VobsApp
|
|
24
|
+
provide<T>(key: InjectionKey<T>, value: T, options?: ProvideOptions): void
|
|
25
|
+
inject<T>(key: InjectionKey<T>): T | undefined
|
|
26
|
+
injectRequired<T>(key: InjectionKey<T>, description?: string): T
|
|
27
|
+
onDestroy(cleanup: () => void): void
|
|
28
|
+
onError(handler: (error: unknown) => void): () => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface VobsConfig<
|
|
32
|
+
NodeType = Node,
|
|
33
|
+
TextNode extends NodeType = NodeType,
|
|
34
|
+
ElementNode extends NodeType = NodeType,
|
|
35
|
+
CommentNode extends NodeType = NodeType
|
|
36
|
+
> {
|
|
37
|
+
render: () => NodeType | VobsFragment | Node | null | undefined
|
|
38
|
+
renderer?: VobsRenderer<NodeType, TextNode, ElementNode, CommentNode>
|
|
39
|
+
plugins?: VobsPlugin[]
|
|
40
|
+
/** Optional application-level error observer. Observers are isolated from the app. */
|
|
41
|
+
onError?: (error: unknown) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface VobsApp<NodeType = Node> {
|
|
45
|
+
readonly mounted: boolean
|
|
46
|
+
readonly destroyed: boolean
|
|
47
|
+
use(plugin: VobsPlugin): VobsApp<NodeType>
|
|
48
|
+
mount(target: string | NodeType): void
|
|
49
|
+
hydrate(target: string | NodeType): void
|
|
50
|
+
update(): void
|
|
51
|
+
destroy(): void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createInjectionKey<T>(description: string): InjectionKey<T> {
|
|
55
|
+
return Symbol(description) as InjectionKey<T>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createVobs(
|
|
59
|
+
config: VobsConfig<Node, Text, Element, Comment>
|
|
60
|
+
): VobsApp<Node>
|
|
61
|
+
export function createVobs<
|
|
62
|
+
NodeType = Node,
|
|
63
|
+
TextNode extends NodeType = NodeType,
|
|
64
|
+
ElementNode extends NodeType = NodeType,
|
|
65
|
+
CommentNode extends NodeType = NodeType
|
|
66
|
+
>(
|
|
67
|
+
config: VobsConfig<NodeType, TextNode, ElementNode, CommentNode> & {
|
|
68
|
+
renderer: VobsRenderer<NodeType, TextNode, ElementNode, CommentNode>
|
|
69
|
+
}
|
|
70
|
+
): VobsApp<NodeType>
|
|
71
|
+
export function createVobs(
|
|
72
|
+
config: VobsConfig<any, any, any, any>
|
|
73
|
+
): VobsApp<any> {
|
|
74
|
+
if (!config.render) throw new Error('createVobs: render 不能为空')
|
|
75
|
+
|
|
76
|
+
const renderer = config.renderer ?? createDOMRenderer()
|
|
77
|
+
const rootOwner: Owner = createOwner()
|
|
78
|
+
setOwnerDebugName(rootOwner, 'App')
|
|
79
|
+
const cleanups: Array<() => void> = []
|
|
80
|
+
const errorHandlers = new Set<(error: unknown) => void>()
|
|
81
|
+
const installed = new Set<string>()
|
|
82
|
+
const installing = new Set<string>()
|
|
83
|
+
let mounted = false
|
|
84
|
+
let destroyed = false
|
|
85
|
+
let container: any = null
|
|
86
|
+
let app!: VobsApp<any>
|
|
87
|
+
|
|
88
|
+
if (config.onError) {
|
|
89
|
+
errorHandlers.add(config.onError)
|
|
90
|
+
cleanups.push(() => errorHandlers.delete(config.onError!))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const context: VobsContext = {
|
|
94
|
+
get app(): VobsApp<any> {
|
|
95
|
+
return app
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
provide<T>(key: InjectionKey<T>, value: T, options: ProvideOptions = {}): void {
|
|
99
|
+
provideToOwner(rootOwner, key, value, options)
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
inject<T>(key: InjectionKey<T>): T | undefined {
|
|
103
|
+
return injectFromOwner(rootOwner, key)
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
injectRequired<T>(key: InjectionKey<T>, description?: string): T {
|
|
107
|
+
const value = injectFromOwner(rootOwner, key)
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
throw new Error(`Vobs: 找不到必需注入项${description ? ` ${description}` : ''}`)
|
|
110
|
+
}
|
|
111
|
+
return value
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
onDestroy(cleanup: () => void): void {
|
|
115
|
+
cleanups.push(cleanup)
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
onError(handler: (error: unknown) => void): () => void {
|
|
119
|
+
errorHandlers.add(handler)
|
|
120
|
+
const remove = () => errorHandlers.delete(handler)
|
|
121
|
+
cleanups.push(remove)
|
|
122
|
+
return remove
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function notifyError(error: unknown): void {
|
|
127
|
+
for (const handler of [...errorHandlers]) {
|
|
128
|
+
try {
|
|
129
|
+
handler(error)
|
|
130
|
+
} catch {
|
|
131
|
+
// 错误处理器不能覆盖触发它的原始错误。
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function installPlugin(plugin: VobsPlugin): void {
|
|
137
|
+
if (installed.has(plugin.name)) return
|
|
138
|
+
if (installing.has(plugin.name)) {
|
|
139
|
+
throw new Error(`Vobs: 插件依赖存在循环:${plugin.name}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
installing.add(plugin.name)
|
|
143
|
+
try {
|
|
144
|
+
for (const dependency of plugin.requires ?? []) installPlugin(dependency)
|
|
145
|
+
const cleanup = plugin.install?.(context)
|
|
146
|
+
if (cleanup) cleanups.push(cleanup)
|
|
147
|
+
installed.add(plugin.name)
|
|
148
|
+
} finally {
|
|
149
|
+
installing.delete(plugin.name)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cleanup(clearContainer = true): unknown {
|
|
154
|
+
let firstError: unknown
|
|
155
|
+
for (let index = cleanups.length - 1; index >= 0; index--) {
|
|
156
|
+
try {
|
|
157
|
+
cleanups[index]()
|
|
158
|
+
} catch (error) {
|
|
159
|
+
firstError ??= error
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
cleanups.length = 0
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
rootOwner.dispose()
|
|
166
|
+
} catch (error) {
|
|
167
|
+
firstError ??= error
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (container && clearContainer) {
|
|
171
|
+
try {
|
|
172
|
+
renderer.clear(container)
|
|
173
|
+
} catch (error) {
|
|
174
|
+
firstError ??= error
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return firstError
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function start(target: string | any, hydrating: boolean): void {
|
|
181
|
+
if (destroyed) throw new Error('Vobs: 已销毁的应用不能挂载')
|
|
182
|
+
if (mounted) return
|
|
183
|
+
|
|
184
|
+
container = typeof target === 'string' ? document.querySelector(target) : target
|
|
185
|
+
if (!container) throw new Error(`mount: 目标不存在: ${target}`)
|
|
186
|
+
if (hydrating && !renderer.beginHydration) {
|
|
187
|
+
throw new Error('Vobs: 当前渲染器不支持 Hydration')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setRenderer(renderer)
|
|
191
|
+
try {
|
|
192
|
+
if (hydrating) renderer.beginHydration?.()
|
|
193
|
+
const rootNode = rootOwner.run(config.render)
|
|
194
|
+
if (!hydrating) renderer.clear(container)
|
|
195
|
+
if (rootNode) insertRuntimeNode(container, rootNode as any, null)
|
|
196
|
+
if (hydrating) renderer.completeHydration?.()
|
|
197
|
+
mounted = true
|
|
198
|
+
} catch (error) {
|
|
199
|
+
notifyError(error)
|
|
200
|
+
const cleanupError = cleanup(!hydrating)
|
|
201
|
+
destroyed = true
|
|
202
|
+
throw cleanupError ?? error
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
app = {
|
|
207
|
+
get mounted(): boolean {
|
|
208
|
+
return mounted
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
get destroyed(): boolean {
|
|
212
|
+
return destroyed
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
use(plugin: VobsPlugin): VobsApp<any> {
|
|
216
|
+
if (destroyed) throw new Error('Vobs: 已销毁的应用不能安装插件')
|
|
217
|
+
installPlugin(plugin)
|
|
218
|
+
return app
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
mount(target: string | any): void {
|
|
222
|
+
start(target, false)
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
hydrate(target: string | any): void {
|
|
226
|
+
start(target, true)
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
update(): void {
|
|
230
|
+
if (destroyed) throw new Error('Vobs: 已销毁的应用不能更新')
|
|
231
|
+
try {
|
|
232
|
+
scheduler.flush()
|
|
233
|
+
} catch (error) {
|
|
234
|
+
notifyError(error)
|
|
235
|
+
throw error
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
destroy(): void {
|
|
240
|
+
if (destroyed) return
|
|
241
|
+
try {
|
|
242
|
+
const cleanupError = cleanup()
|
|
243
|
+
if (cleanupError) {
|
|
244
|
+
notifyError(cleanupError)
|
|
245
|
+
throw cleanupError
|
|
246
|
+
}
|
|
247
|
+
} finally {
|
|
248
|
+
mounted = false
|
|
249
|
+
destroyed = true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
for (const plugin of config.plugins ?? []) app.use(plugin)
|
|
256
|
+
} catch (error) {
|
|
257
|
+
notifyError(error)
|
|
258
|
+
cleanup()
|
|
259
|
+
destroyed = true
|
|
260
|
+
throw error
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return app
|
|
264
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getCurrentOwner, type Owner } from '@vobs/reactivity'
|
|
2
|
+
import type { InjectionKey, ProvideOptions } from './app'
|
|
3
|
+
|
|
4
|
+
const ownerProviders = new WeakMap<Owner, Map<InjectionKey<unknown>, unknown>>()
|
|
5
|
+
|
|
6
|
+
export function provide<T>(key: InjectionKey<T>, value: T, options: ProvideOptions = {}): void {
|
|
7
|
+
const owner = getCurrentOwner()
|
|
8
|
+
if (!owner) throw new Error('Vobs: provide 必须在组件或 Owner 作用域内调用')
|
|
9
|
+
provideToOwner(owner, key, value, options)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function inject<T>(key: InjectionKey<T>): T | undefined
|
|
13
|
+
export function inject<T>(key: InjectionKey<T>, fallback: T): T
|
|
14
|
+
export function inject<T>(key: InjectionKey<T>, fallback?: T): T | undefined {
|
|
15
|
+
const owner = getCurrentOwner()
|
|
16
|
+
const value = owner ? injectFromOwner(owner, key) : undefined
|
|
17
|
+
return value === undefined ? fallback : value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function injectRequired<T>(key: InjectionKey<T>, description?: string): T {
|
|
21
|
+
const value = inject(key)
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
throw new Error(`Vobs: 找不到必需注入项${description ? ` ${description}` : ''}`)
|
|
24
|
+
}
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function provideToOwner<T>(
|
|
29
|
+
owner: Owner,
|
|
30
|
+
key: InjectionKey<T>,
|
|
31
|
+
value: T,
|
|
32
|
+
options: ProvideOptions = {}
|
|
33
|
+
): void {
|
|
34
|
+
let providers = ownerProviders.get(owner)
|
|
35
|
+
if (!providers) {
|
|
36
|
+
providers = new Map<InjectionKey<unknown>, unknown>()
|
|
37
|
+
ownerProviders.set(owner, providers)
|
|
38
|
+
owner.onDispose(() => ownerProviders.delete(owner))
|
|
39
|
+
}
|
|
40
|
+
if (providers.has(key) && !options.override) {
|
|
41
|
+
throw new Error(`Vobs: 注入项 ${String(key)} 已存在;如需覆盖请传入 override: true`)
|
|
42
|
+
}
|
|
43
|
+
providers.set(key, value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function injectFromOwner<T>(owner: Owner, key: InjectionKey<T>): T | undefined {
|
|
47
|
+
let current: Owner | null = owner
|
|
48
|
+
while (current) {
|
|
49
|
+
const providers = ownerProviders.get(current)
|
|
50
|
+
if (providers?.has(key)) return providers.get(key) as T
|
|
51
|
+
current = current.parent
|
|
52
|
+
}
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/// <reference path="./jsx.d.ts" />
|
|
2
|
+
|
|
3
|
+
export * from '@vobs/reactivity'
|
|
4
|
+
export { createDOMRenderer } from '@vobs/dom'
|
|
5
|
+
export type { VobsRenderer } from '@vobs/runtime'
|
|
6
|
+
export {
|
|
7
|
+
getRuntimeDebugContext,
|
|
8
|
+
getRuntimeDebugHooks,
|
|
9
|
+
pushRuntimeDebugContext,
|
|
10
|
+
runWithRuntimeDebugContext,
|
|
11
|
+
setRuntimeDebugHooks
|
|
12
|
+
} from '@vobs/runtime'
|
|
13
|
+
export type {
|
|
14
|
+
RuntimeDebugContext,
|
|
15
|
+
RuntimeDebugEnvironment,
|
|
16
|
+
RuntimeDebugHooks,
|
|
17
|
+
RuntimeDomMutation,
|
|
18
|
+
RuntimeDomMutationOperation,
|
|
19
|
+
RuntimeErrorEvent,
|
|
20
|
+
RuntimeHydrationMismatch
|
|
21
|
+
} from '@vobs/runtime'
|
|
22
|
+
export {
|
|
23
|
+
setRenderer,
|
|
24
|
+
getRenderer,
|
|
25
|
+
createText,
|
|
26
|
+
createElement,
|
|
27
|
+
createComment,
|
|
28
|
+
insertBefore,
|
|
29
|
+
removeChild,
|
|
30
|
+
setTextContent,
|
|
31
|
+
setProperty,
|
|
32
|
+
setAttribute,
|
|
33
|
+
setStaticProps,
|
|
34
|
+
spreadProps,
|
|
35
|
+
addEventListener,
|
|
36
|
+
removeEventListener,
|
|
37
|
+
clear,
|
|
38
|
+
createComponent,
|
|
39
|
+
createFragment,
|
|
40
|
+
isVobsFragment,
|
|
41
|
+
disposeNodeOwner
|
|
42
|
+
} from '@vobs/runtime'
|
|
43
|
+
export type { FragmentFactory, VobsFragment, VobsNode } from '@vobs/runtime'
|
|
44
|
+
export { bindText, bindAttribute, bindProperty } from '@vobs/runtime'
|
|
45
|
+
export type { ValueSource } from '@vobs/runtime'
|
|
46
|
+
export { ref, setRef } from '@vobs/runtime'
|
|
47
|
+
export type { Ref, RefTarget } from '@vobs/runtime'
|
|
48
|
+
export { insertDynamic, insertDynamicValue, insertList, normalizeDynamicChild } from '@vobs/runtime'
|
|
49
|
+
export type { DynamicChild, NodeFactory } from '@vobs/runtime'
|
|
50
|
+
export type { VobsLocatedError, VobsSourceLocation } from '@vobs/runtime'
|
|
51
|
+
export {
|
|
52
|
+
VobsError,
|
|
53
|
+
createVobsError,
|
|
54
|
+
formatVobsError,
|
|
55
|
+
isVobsError,
|
|
56
|
+
normalizeVobsError
|
|
57
|
+
} from '@vobs/runtime'
|
|
58
|
+
export type {
|
|
59
|
+
FormatVobsErrorOptions,
|
|
60
|
+
VobsErrorDefaults,
|
|
61
|
+
VobsErrorLayer,
|
|
62
|
+
VobsErrorLocation,
|
|
63
|
+
VobsErrorOptions,
|
|
64
|
+
VobsErrorSeverity
|
|
65
|
+
} from '@vobs/runtime'
|
|
66
|
+
export {
|
|
67
|
+
createHmrStateStore,
|
|
68
|
+
disposeHmrModule,
|
|
69
|
+
resolveComponent,
|
|
70
|
+
updateHmrModule
|
|
71
|
+
} from '@vobs/runtime'
|
|
72
|
+
export type { HmrComponent, HmrInstance, HmrStateStore } from '@vobs/runtime'
|
|
73
|
+
export { insertErrorBoundary } from '@vobs/runtime'
|
|
74
|
+
export { ErrorBoundary, insertBoundary } from '@vobs/runtime'
|
|
75
|
+
export type { BoundaryFallback, BoundaryOptions, BoundaryRetry, ErrorBoundaryFallback, ErrorBoundaryOptions, ErrorBoundaryProps } from '@vobs/runtime'
|
|
76
|
+
export { AsyncBoundary, insertAsyncBoundary, Profiler, insertProfiler } from '@vobs/runtime'
|
|
77
|
+
export type { AsyncBoundaryFallback, AsyncBoundaryOptions, AsyncBoundaryProps, AsyncBoundaryView, ProfilerOptions, ProfilerProps, ProfilerRenderInfo } from '@vobs/runtime'
|
|
78
|
+
export { createVobs, createInjectionKey } from './app'
|
|
79
|
+
export { Fragment, Vobs } from './jsx-dev-runtime'
|
|
80
|
+
export { provide, inject, injectRequired } from './context'
|
|
81
|
+
export type {
|
|
82
|
+
InjectionKey,
|
|
83
|
+
ProvideOptions,
|
|
84
|
+
VobsApp,
|
|
85
|
+
VobsConfig,
|
|
86
|
+
VobsPlugin,
|
|
87
|
+
VobsContext
|
|
88
|
+
} from './app'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createComponent,
|
|
3
|
+
createElement,
|
|
4
|
+
createFragment,
|
|
5
|
+
insertBefore,
|
|
6
|
+
normalizeDynamicChild,
|
|
7
|
+
spreadProps,
|
|
8
|
+
type VobsNode
|
|
9
|
+
} from '@vobs/runtime'
|
|
10
|
+
|
|
11
|
+
export function jsxDEV(
|
|
12
|
+
tag: string | Function,
|
|
13
|
+
props: Record<string, unknown> | null,
|
|
14
|
+
_key?: unknown,
|
|
15
|
+
_isStaticChildren?: boolean,
|
|
16
|
+
_source?: unknown,
|
|
17
|
+
_self?: unknown
|
|
18
|
+
): VobsNode {
|
|
19
|
+
if (typeof tag === 'function') {
|
|
20
|
+
return createComponent(tag as (props: Record<string, unknown>) => VobsNode, props ?? {})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { children, ...attrs } = props ?? {}
|
|
24
|
+
|
|
25
|
+
const el = createElement(tag as string)
|
|
26
|
+
|
|
27
|
+
spreadProps(el, attrs)
|
|
28
|
+
|
|
29
|
+
appendChildren(el, children)
|
|
30
|
+
|
|
31
|
+
return el
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function appendChildren(parent: Node, children: unknown, anchor: Node | null = null): void {
|
|
35
|
+
if (children === undefined || children === null) return
|
|
36
|
+
|
|
37
|
+
const childArray = Array.isArray(children) ? children : [children]
|
|
38
|
+
|
|
39
|
+
for (const child of childArray) {
|
|
40
|
+
const node = normalizeDynamicChild(child as Parameters<typeof normalizeDynamicChild>[0])
|
|
41
|
+
if (node) insertBefore(parent, node, anchor)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function jsx(tag: string | Function, props: Record<string, unknown> | null): VobsNode {
|
|
46
|
+
return jsxDEV(tag, props)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function jsxs(tag: string | Function, props: Record<string, unknown> | null): VobsNode {
|
|
50
|
+
return jsxDEV(tag, props)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function Fragment(props: { children?: unknown }): VobsNode {
|
|
54
|
+
return createFragment((parent, anchor) => appendChildren(parent, props?.children, anchor))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Namespace for explicit built-in JSX primitives such as <Vobs.Fragment>. */
|
|
58
|
+
export const Vobs = { Fragment }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Fragment, jsx, jsxs } from './jsx-dev-runtime'
|
package/src/jsx.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { VobsNode } from './fragment'
|
|
2
|
+
import type { Ref, RefTarget } from '@vobs/runtime'
|
|
3
|
+
|
|
4
|
+
// JSX 类型声明
|
|
5
|
+
|
|
6
|
+
type VobsEventHandler<T extends Event = Event> = (event: T) => void
|
|
7
|
+
|
|
8
|
+
interface VobsHTMLAttributes {
|
|
9
|
+
ref?: RefTarget<any>
|
|
10
|
+
accessKey?: string
|
|
11
|
+
autofocus?: boolean
|
|
12
|
+
class?: string
|
|
13
|
+
className?: string
|
|
14
|
+
cols?: number
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
height?: number | string
|
|
17
|
+
hidden?: boolean
|
|
18
|
+
id?: string
|
|
19
|
+
max?: number | string
|
|
20
|
+
maxLength?: number
|
|
21
|
+
min?: number | string
|
|
22
|
+
minLength?: number
|
|
23
|
+
multiple?: boolean
|
|
24
|
+
name?: string
|
|
25
|
+
placeholder?: string
|
|
26
|
+
readOnly?: boolean
|
|
27
|
+
required?: boolean
|
|
28
|
+
rows?: number
|
|
29
|
+
selected?: boolean
|
|
30
|
+
size?: number
|
|
31
|
+
src?: string
|
|
32
|
+
step?: number | string
|
|
33
|
+
style?: string | Readonly<Record<string, string | number | boolean | null | undefined>>
|
|
34
|
+
tabIndex?: number
|
|
35
|
+
title?: string
|
|
36
|
+
type?: string
|
|
37
|
+
value?: string | number | readonly string[]
|
|
38
|
+
key?: string | number
|
|
39
|
+
width?: number | string
|
|
40
|
+
children?: unknown
|
|
41
|
+
onClick?: VobsEventHandler<MouseEvent>
|
|
42
|
+
onDblClick?: VobsEventHandler<MouseEvent>
|
|
43
|
+
onFocus?: VobsEventHandler<FocusEvent>
|
|
44
|
+
onBlur?: VobsEventHandler<FocusEvent>
|
|
45
|
+
onInput?: VobsEventHandler<InputEvent>
|
|
46
|
+
onChange?: VobsEventHandler<Event>
|
|
47
|
+
onKeyDown?: VobsEventHandler<KeyboardEvent>
|
|
48
|
+
onKeyUp?: VobsEventHandler<KeyboardEvent>
|
|
49
|
+
onSubmit?: VobsEventHandler<SubmitEvent>
|
|
50
|
+
[name: `data-${string}`]: string | number | boolean | undefined
|
|
51
|
+
[name: `aria-${string}`]: string | number | boolean | undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare global {
|
|
55
|
+
namespace JSX {
|
|
56
|
+
type Element = VobsNode
|
|
57
|
+
interface ElementChildrenAttribute { children: {} }
|
|
58
|
+
interface IntrinsicElements {
|
|
59
|
+
a: VobsHTMLAttributes
|
|
60
|
+
article: VobsHTMLAttributes
|
|
61
|
+
aside: VobsHTMLAttributes
|
|
62
|
+
button: VobsHTMLAttributes
|
|
63
|
+
div: VobsHTMLAttributes
|
|
64
|
+
footer: VobsHTMLAttributes
|
|
65
|
+
form: VobsHTMLAttributes
|
|
66
|
+
h1: VobsHTMLAttributes
|
|
67
|
+
h2: VobsHTMLAttributes
|
|
68
|
+
h3: VobsHTMLAttributes
|
|
69
|
+
header: VobsHTMLAttributes
|
|
70
|
+
img: VobsHTMLAttributes
|
|
71
|
+
input: VobsHTMLAttributes
|
|
72
|
+
label: VobsHTMLAttributes
|
|
73
|
+
li: VobsHTMLAttributes
|
|
74
|
+
main: VobsHTMLAttributes
|
|
75
|
+
nav: VobsHTMLAttributes
|
|
76
|
+
ol: VobsHTMLAttributes
|
|
77
|
+
option: VobsHTMLAttributes
|
|
78
|
+
pre: VobsHTMLAttributes
|
|
79
|
+
p: VobsHTMLAttributes
|
|
80
|
+
section: VobsHTMLAttributes
|
|
81
|
+
select: VobsHTMLAttributes
|
|
82
|
+
span: VobsHTMLAttributes
|
|
83
|
+
table: VobsHTMLAttributes
|
|
84
|
+
tbody: VobsHTMLAttributes
|
|
85
|
+
td: VobsHTMLAttributes
|
|
86
|
+
textarea: VobsHTMLAttributes
|
|
87
|
+
tfoot: VobsHTMLAttributes
|
|
88
|
+
th: VobsHTMLAttributes
|
|
89
|
+
thead: VobsHTMLAttributes
|
|
90
|
+
tr: VobsHTMLAttributes
|
|
91
|
+
ul: VobsHTMLAttributes
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import { createInjectionKey, inject, provide } from './index'
|
|
3
|
+
import { state, type Signal } from '@vobs/reactivity'
|
|
4
|
+
|
|
5
|
+
interface AuthContext {
|
|
6
|
+
readonly loggedIn: Signal<boolean>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Vobs public types', () => {
|
|
10
|
+
it('state 保留泛型并由初始值推导', () => {
|
|
11
|
+
const count = state(0)
|
|
12
|
+
expectTypeOf(count).toEqualTypeOf<Signal<number>>()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('InjectionKey 将 inject 和 provide 约束到同一类型', () => {
|
|
16
|
+
const key = createInjectionKey<AuthContext>('test.auth')
|
|
17
|
+
const check = (): void => {
|
|
18
|
+
const value: AuthContext | undefined = inject(key)
|
|
19
|
+
if (value) value.loggedIn.value = true
|
|
20
|
+
provide(key, { loggedIn: state(false) })
|
|
21
|
+
// @ts-expect-error The value must match the InjectionKey payload.
|
|
22
|
+
provide(key, state(false))
|
|
23
|
+
}
|
|
24
|
+
expectTypeOf(check).toBeFunction()
|
|
25
|
+
})
|
|
26
|
+
})
|
package/dist/index.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** @license MIT
|
|
2
|
-
* Copyright (c) 2026 vobsjs
|
|
3
|
-
* @vobs/vobs
|
|
4
|
-
*/
|
|
5
|
-
/** @license MIT
|
|
6
|
-
* Copyright (c) 2026 vobsjs
|
|
7
|
-
* vobs
|
|
8
|
-
*/
|
|
9
|
-
export * from '@vobs/reactivity';
|
|
10
|
-
export * from '@vobs/runtime-dom';
|
|
11
|
-
export { RouterCapability, collectRouteNavigation, createRouteLoaderCache, createRouteNavigationScope, createRouteResolver, createRouter, loadRouteComponent, resolveActiveRouteNavigationId, resolveRoute, resolveRouteGuard, resolveRouteInitialState, } from '@vobs/router';
|
package/dist/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** @license MIT
|
|
2
|
-
* Copyright (c) 2026 vobsjs
|
|
3
|
-
* @vobs/vobs
|
|
4
|
-
*/
|
|
5
|
-
/** @license MIT
|
|
6
|
-
* Copyright (c) 2026 vobsjs
|
|
7
|
-
* vobs
|
|
8
|
-
*/
|
|
9
|
-
export * from '@vobs/reactivity';
|
|
10
|
-
export * from '@vobs/runtime-dom';
|
|
11
|
-
export { RouterCapability, collectRouteNavigation, createRouteLoaderCache, createRouteNavigationScope, createRouteResolver, createRouter, loadRouteComponent, resolveActiveRouteNavigationId, resolveRoute, resolveRouteGuard, resolveRouteInitialState, } from '@vobs/router';
|
package/dist/reactivity.d.ts
DELETED
package/dist/reactivity.js
DELETED
package/dist/router.d.ts
DELETED
package/dist/router.js
DELETED
package/dist/runtime-dom.d.ts
DELETED