@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.10
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/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/dist/actions.d.ts +32 -30
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +251 -266
- package/dist/actions.js.map +1 -1
- package/dist/async.d.ts +20 -17
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +18 -17
- package/dist/async.js.map +1 -1
- package/dist/compiled/async-resource.d.ts +9 -6
- package/dist/compiled/async-resource.d.ts.map +1 -1
- package/dist/compiled/async-resource.js +69 -77
- package/dist/compiled/async-resource.js.map +1 -1
- package/dist/compiled/core.d.ts +148 -147
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +3064 -2987
- package/dist/compiled/core.js.map +1 -1
- package/dist/compiled/types.d.ts +44 -56
- package/dist/compiled/types.d.ts.map +1 -1
- package/dist/compiled/types.js +10 -6
- package/dist/compiled/types.js.map +1 -1
- package/dist/compiled/validation.d.ts +4 -1
- package/dist/compiled/validation.d.ts.map +1 -1
- package/dist/compiled/validation.js +17 -20
- package/dist/compiled/validation.js.map +1 -1
- package/dist/component-spread.d.ts +5 -3
- package/dist/component-spread.d.ts.map +1 -1
- package/dist/component-spread.js +49 -67
- package/dist/component-spread.js.map +1 -1
- package/dist/concurrent.d.ts +20 -18
- package/dist/concurrent.d.ts.map +1 -1
- package/dist/concurrent.js +191 -210
- package/dist/concurrent.js.map +1 -1
- package/dist/direct-dom.d.ts +17 -12
- package/dist/direct-dom.d.ts.map +1 -1
- package/dist/direct-dom.js +279 -222
- package/dist/direct-dom.js.map +1 -1
- package/dist/dom/attributes.d.ts +9 -6
- package/dist/dom/attributes.d.ts.map +1 -1
- package/dist/dom/attributes.js +65 -47
- package/dist/dom/attributes.js.map +1 -1
- package/dist/dom/events.d.ts +8 -2
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +118 -65
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/forms.d.ts +9 -6
- package/dist/dom/forms.d.ts.map +1 -1
- package/dist/dom/forms.js +152 -198
- package/dist/dom/forms.js.map +1 -1
- package/dist/dom/intrinsic.d.ts +17 -15
- package/dist/dom/intrinsic.d.ts.map +1 -1
- package/dist/dom/intrinsic.js +48 -50
- package/dist/dom/intrinsic.js.map +1 -1
- package/dist/dom/namespace.d.ts +4 -1
- package/dist/dom/namespace.d.ts.map +1 -1
- package/dist/dom/namespace.js +25 -28
- package/dist/dom/namespace.js.map +1 -1
- package/dist/dom/namespaces.d.ts +11 -8
- package/dist/dom/namespaces.d.ts.map +1 -1
- package/dist/dom/namespaces.js +14 -12
- package/dist/dom/namespaces.js.map +1 -1
- package/dist/dom/properties.d.ts +18 -16
- package/dist/dom/properties.d.ts.map +1 -1
- package/dist/dom/properties.js +273 -296
- package/dist/dom/properties.js.map +1 -1
- package/dist/dom/style-properties.d.ts +4 -1
- package/dist/dom/style-properties.d.ts.map +1 -1
- package/dist/dom/style-properties.js +49 -45
- package/dist/dom/style-properties.js.map +1 -1
- package/dist/dom/styles.d.ts +5 -2
- package/dist/dom/styles.d.ts.map +1 -1
- package/dist/dom/styles.js +47 -67
- package/dist/dom/styles.js.map +1 -1
- package/dist/dom-forms.d.ts +4 -1
- package/dist/dom-forms.d.ts.map +1 -1
- package/dist/dom-forms.js +16 -13
- package/dist/dom-forms.js.map +1 -1
- package/dist/dom-styles.d.ts +4 -1
- package/dist/dom-styles.d.ts.map +1 -1
- package/dist/dom-styles.js +10 -7
- package/dist/dom-styles.js.map +1 -1
- package/dist/framework-hydrate.d.ts +20 -17
- package/dist/framework-hydrate.d.ts.map +1 -1
- package/dist/framework-hydrate.js +128 -146
- package/dist/framework-hydrate.js.map +1 -1
- package/dist/framework-protocol.d.ts +27 -25
- package/dist/framework-protocol.d.ts.map +1 -1
- package/dist/framework-protocol.js +173 -211
- package/dist/framework-protocol.js.map +1 -1
- package/dist/framework.d.ts +34 -32
- package/dist/framework.d.ts.map +1 -1
- package/dist/framework.js +74 -93
- package/dist/framework.js.map +1 -1
- package/dist/hydrate.d.ts +11 -8
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +13 -9
- package/dist/hydrate.js.map +1 -1
- package/dist/hydration-bridge.d.ts +70 -58
- package/dist/hydration-bridge.d.ts.map +1 -1
- package/dist/hydration-bridge.js +77 -66
- package/dist/hydration-bridge.js.map +1 -1
- package/dist/hydration.d.ts +54 -28
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +450 -463
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +11 -13
- package/dist/indexed-list.d.ts +8 -5
- package/dist/indexed-list.d.ts.map +1 -1
- package/dist/indexed-list.js +14 -7
- package/dist/indexed-list.js.map +1 -1
- package/dist/jsx-runtime.d.ts +10 -8
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +28 -23
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/keyed-list.d.ts +17 -14
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +244 -161
- package/dist/keyed-list.js.map +1 -1
- package/dist/metadata.d.ts +6 -3
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +131 -164
- package/dist/metadata.js.map +1 -1
- package/dist/nested-prop.d.ts +4 -2
- package/dist/nested-prop.d.ts.map +1 -1
- package/dist/nested-prop.js +17 -16
- package/dist/nested-prop.js.map +1 -1
- package/dist/profiling.d.ts +11 -8
- package/dist/profiling.d.ts.map +1 -1
- package/dist/profiling.js +17 -17
- package/dist/profiling.js.map +1 -1
- package/dist/protocol.d.ts +5 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +8 -5
- package/dist/protocol.js.map +1 -1
- package/dist/raw-html.d.ts +5 -2
- package/dist/raw-html.d.ts.map +1 -1
- package/dist/raw-html.js +86 -146
- package/dist/raw-html.js.map +1 -1
- package/dist/ref.d.ts +8 -3
- package/dist/ref.d.ts.map +1 -1
- package/dist/ref.js +10 -2
- package/dist/ref.js.map +1 -1
- package/dist/renderable-protocol.d.ts +26 -8
- package/dist/renderable-protocol.d.ts.map +1 -1
- package/dist/renderable-protocol.js +30 -9
- package/dist/renderable-protocol.js.map +1 -1
- package/dist/renderable.d.ts +28 -22
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +152 -133
- package/dist/renderable.js.map +1 -1
- package/dist/retained-ui.d.ts +9 -6
- package/dist/retained-ui.d.ts.map +1 -1
- package/dist/retained-ui.js +12 -10
- package/dist/retained-ui.js.map +1 -1
- package/dist/root.d.ts +18 -15
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +73 -85
- package/dist/root.js.map +1 -1
- package/dist/scheduler.d.ts +10 -8
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +97 -105
- package/dist/scheduler.js.map +1 -1
- package/dist/server-framework.d.ts +45 -42
- package/dist/server-framework.d.ts.map +1 -1
- package/dist/server-framework.js +209 -217
- package/dist/server-framework.js.map +1 -1
- package/dist/server.d.ts +130 -128
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +603 -710
- package/dist/server.js.map +1 -1
- package/dist/shared/promise.d.ts +6 -3
- package/dist/shared/promise.d.ts.map +1 -1
- package/dist/shared/promise.js +21 -19
- package/dist/shared/promise.js.map +1 -1
- package/dist/source-mask.d.ts +10 -6
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +60 -63
- package/dist/source-mask.js.map +1 -1
- package/dist/spread.d.ts +5 -2
- package/dist/spread.d.ts.map +1 -1
- package/dist/spread.js +79 -99
- package/dist/spread.js.map +1 -1
- package/dist/state-slot.d.ts +29 -15
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +73 -42
- package/dist/state-slot.js.map +1 -1
- package/dist/testing.d.ts +9 -6
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +16 -12
- package/dist/testing.js.map +1 -1
- package/package.json +8 -7
- package/src/actions.ts +415 -0
- package/src/async.ts +78 -0
- package/src/compiled/async-resource.ts +113 -0
- package/src/compiled/core.ts +4647 -0
- package/src/compiled/types.ts +96 -0
- package/src/compiled/validation.ts +23 -0
- package/src/component-spread.ts +86 -0
- package/src/concurrent.ts +310 -0
- package/src/direct-dom.ts +491 -0
- package/src/dom/attributes.ts +55 -0
- package/src/dom/events.ts +179 -0
- package/src/dom/forms.ts +220 -0
- package/src/dom/intrinsic.ts +94 -0
- package/src/dom/namespace.ts +30 -0
- package/src/dom/namespaces.ts +14 -0
- package/src/dom/properties.ts +355 -0
- package/src/dom/style-properties.ts +46 -0
- package/src/dom/styles.ts +86 -0
- package/src/dom-forms.ts +23 -0
- package/src/dom-styles.ts +11 -0
- package/src/env.d.ts +6 -0
- package/src/framework-hydrate.ts +224 -0
- package/src/framework-protocol.ts +335 -0
- package/src/framework.ts +161 -0
- package/src/hydrate.ts +38 -0
- package/src/hydration-bridge.ts +202 -0
- package/src/hydration.ts +644 -0
- package/src/index.ts +94 -0
- package/src/indexed-list.ts +20 -0
- package/src/jsx-runtime.ts +55 -0
- package/src/keyed-list.ts +379 -0
- package/src/metadata.ts +212 -0
- package/src/nested-prop.ts +27 -0
- package/src/profiling.ts +43 -0
- package/src/protocol.ts +8 -0
- package/src/raw-html.ts +206 -0
- package/src/ref.ts +13 -0
- package/src/renderable-protocol.ts +63 -0
- package/src/renderable.ts +313 -0
- package/src/retained-ui.ts +19 -0
- package/src/root.ts +122 -0
- package/src/scheduler.ts +136 -0
- package/src/server-framework.ts +397 -0
- package/src/server.ts +1160 -0
- package/src/shared/promise.ts +28 -0
- package/src/source-mask.ts +103 -0
- package/src/spread.ts +131 -0
- package/src/state-slot.ts +132 -0
- package/src/testing.ts +27 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { registerCompiledCleanup } from './compiled/core.ts'
|
|
2
|
+
import {
|
|
3
|
+
LINK_IDENTITY_PROPS,
|
|
4
|
+
META_IDENTITY_ATTRIBUTES,
|
|
5
|
+
META_IDENTITY_PROPS,
|
|
6
|
+
} from './dom/attributes.ts'
|
|
7
|
+
import { applyBaseDomProp, installDomPropHandler } from './dom/properties.ts'
|
|
8
|
+
|
|
9
|
+
type MetadataEntry = {
|
|
10
|
+
readonly elements: Element[]
|
|
11
|
+
readonly baseline: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type MetadataState = {
|
|
15
|
+
readonly entries: Map<string, MetadataEntry>
|
|
16
|
+
readonly stylePrecedence: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type MetadataOwner = {
|
|
20
|
+
readonly element: Element
|
|
21
|
+
readonly state: MetadataState
|
|
22
|
+
entry: MetadataEntry
|
|
23
|
+
key: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const metadataByDocument = new WeakMap<Document, MetadataState>()
|
|
27
|
+
const metadataOwners = new WeakMap<Element, MetadataOwner>()
|
|
28
|
+
let propRekeyingInstalled = false
|
|
29
|
+
|
|
30
|
+
export function installFrameworkMetadataPropRekeying(): void {
|
|
31
|
+
if (propRekeyingInstalled) return
|
|
32
|
+
propRekeyingInstalled = true
|
|
33
|
+
installDomPropHandler((element, name, value) => {
|
|
34
|
+
const owner = metadataOwners.get(element)
|
|
35
|
+
if (owner === undefined || !isMetadataIdentityProp(element.localName, name)) return false
|
|
36
|
+
detachMetadataOwner(owner)
|
|
37
|
+
applyBaseDomProp(element, name, value)
|
|
38
|
+
if (name === 'precedence') {
|
|
39
|
+
element.setAttribute('data-precedence', String(value ?? 'default'))
|
|
40
|
+
element.removeAttribute('precedence')
|
|
41
|
+
}
|
|
42
|
+
attachMetadataOwner(owner)
|
|
43
|
+
return true
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isFrameworkMetadataElement(
|
|
48
|
+
element: Element,
|
|
49
|
+
props: Readonly<Record<string, unknown>> | null = null,
|
|
50
|
+
): boolean {
|
|
51
|
+
const name = element.localName
|
|
52
|
+
if (
|
|
53
|
+
(name === 'title' || name === 'meta' || name === 'link') &&
|
|
54
|
+
element.hasAttribute('itemprop')
|
|
55
|
+
) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
if (name === 'title' || name === 'meta') return true
|
|
59
|
+
if (name === 'link') {
|
|
60
|
+
if (
|
|
61
|
+
props !== null &&
|
|
62
|
+
['onLoad', 'onError', 'disabled'].some((key) => Object.hasOwn(props, key))
|
|
63
|
+
) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
return element.getAttribute('rel') !== 'stylesheet' || element.hasAttribute('precedence')
|
|
67
|
+
}
|
|
68
|
+
if (name === 'style') return element.hasAttribute('href') && element.hasAttribute('precedence')
|
|
69
|
+
return name === 'script' && element.hasAttribute('async') && element.hasAttribute('src')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function hoistFrameworkMetadata(element: Element): DocumentFragment {
|
|
73
|
+
const document = element.ownerDocument
|
|
74
|
+
const state = metadataState(document)
|
|
75
|
+
const key = metadataKey(element)
|
|
76
|
+
const owner: MetadataOwner = {
|
|
77
|
+
element,
|
|
78
|
+
state,
|
|
79
|
+
key,
|
|
80
|
+
entry: metadataEntry(document, state, key),
|
|
81
|
+
}
|
|
82
|
+
attachMetadataOwner(owner)
|
|
83
|
+
metadataOwners.set(element, owner)
|
|
84
|
+
element.removeAttribute('precedence')
|
|
85
|
+
registerCompiledCleanup(() => {
|
|
86
|
+
detachMetadataOwner(owner)
|
|
87
|
+
metadataOwners.delete(element)
|
|
88
|
+
})
|
|
89
|
+
return document.createDocumentFragment()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function metadataState(document: Document): MetadataState {
|
|
93
|
+
let state = metadataByDocument.get(document)
|
|
94
|
+
if (state !== undefined) return state
|
|
95
|
+
state = {
|
|
96
|
+
entries: new Map(),
|
|
97
|
+
stylePrecedence: [...document.head.children]
|
|
98
|
+
.filter((candidate) => metadataTier(candidate) === 2)
|
|
99
|
+
.map(stylePrecedence)
|
|
100
|
+
.filter((value, index, values) => values.indexOf(value) === index),
|
|
101
|
+
}
|
|
102
|
+
metadataByDocument.set(document, state)
|
|
103
|
+
return state
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function metadataEntry(document: Document, state: MetadataState, key: string): MetadataEntry {
|
|
107
|
+
let entry = state.entries.get(key)
|
|
108
|
+
if (entry !== undefined) return entry
|
|
109
|
+
const existing = [...document.head.children].find(
|
|
110
|
+
(candidate) => isFrameworkMetadataElement(candidate) && metadataKey(candidate) === key,
|
|
111
|
+
)
|
|
112
|
+
entry = {
|
|
113
|
+
elements: existing === undefined ? [] : [existing],
|
|
114
|
+
baseline: existing === undefined ? 0 : 1,
|
|
115
|
+
}
|
|
116
|
+
state.entries.set(key, entry)
|
|
117
|
+
return entry
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function attachMetadataOwner(owner: MetadataOwner): void {
|
|
121
|
+
const document = owner.element.ownerDocument
|
|
122
|
+
owner.key = metadataKey(owner.element)
|
|
123
|
+
owner.entry = metadataEntry(document, owner.state, owner.key)
|
|
124
|
+
owner.entry.elements.at(-1)?.remove()
|
|
125
|
+
owner.entry.elements.push(owner.element)
|
|
126
|
+
insertByPrecedence(document.head, owner.element, owner.state)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function detachMetadataOwner(owner: MetadataOwner): void {
|
|
130
|
+
const { element, entry, key, state } = owner
|
|
131
|
+
const index = entry.elements.indexOf(element)
|
|
132
|
+
if (index === -1) return
|
|
133
|
+
const visible = index === entry.elements.length - 1
|
|
134
|
+
entry.elements.splice(index, 1)
|
|
135
|
+
element.remove()
|
|
136
|
+
if (entry.elements.length === entry.baseline) {
|
|
137
|
+
if (visible && entry.elements.length !== 0) {
|
|
138
|
+
element.ownerDocument.head.append(entry.elements.at(-1)!)
|
|
139
|
+
}
|
|
140
|
+
if (state.entries.get(key) === entry) state.entries.delete(key)
|
|
141
|
+
} else if (visible) {
|
|
142
|
+
insertByPrecedence(element.ownerDocument.head, entry.elements.at(-1)!, state)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isMetadataIdentityProp(elementName: string, propName: string): boolean {
|
|
147
|
+
if (propName === 'precedence') return elementName === 'link' || elementName === 'style'
|
|
148
|
+
if (elementName === 'meta') {
|
|
149
|
+
return (META_IDENTITY_PROPS as readonly string[]).includes(propName)
|
|
150
|
+
}
|
|
151
|
+
if (elementName === 'link') return (LINK_IDENTITY_PROPS as readonly string[]).includes(propName)
|
|
152
|
+
if (elementName === 'script') return propName === 'src'
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function metadataKey(element: Element): string {
|
|
157
|
+
const name = element.localName
|
|
158
|
+
if (name === 'title') return 'title'
|
|
159
|
+
if (name === 'meta') {
|
|
160
|
+
for (const attribute of META_IDENTITY_ATTRIBUTES) {
|
|
161
|
+
if (element.hasAttribute(attribute))
|
|
162
|
+
return `meta:${attribute}:${element.getAttribute(attribute)}`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (name === 'link') {
|
|
166
|
+
return `link:${element.getAttribute('rel') ?? ''}:${element.getAttribute('href') ?? ''}:${element.getAttribute('as') ?? ''}`
|
|
167
|
+
}
|
|
168
|
+
if (name === 'style') return `style:${element.getAttribute('href') ?? ''}`
|
|
169
|
+
return `script:${element.getAttribute('src') ?? ''}`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function insertByPrecedence(head: HTMLHeadElement, element: Element, state: MetadataState): void {
|
|
173
|
+
const tier = metadataTier(element) ?? 1
|
|
174
|
+
const precedence = stylePrecedence(element)
|
|
175
|
+
if (tier === 2) {
|
|
176
|
+
if (!state.stylePrecedence.includes(precedence)) state.stylePrecedence.push(precedence)
|
|
177
|
+
element.setAttribute('data-precedence', precedence)
|
|
178
|
+
}
|
|
179
|
+
const before = [...head.children].find((candidate) => {
|
|
180
|
+
const candidateTier = metadataTier(candidate)
|
|
181
|
+
if (candidateTier === undefined) return false
|
|
182
|
+
if (candidateTier !== tier) return candidateTier > tier
|
|
183
|
+
return (
|
|
184
|
+
tier === 2 &&
|
|
185
|
+
state.stylePrecedence.indexOf(stylePrecedence(candidate)) >
|
|
186
|
+
state.stylePrecedence.indexOf(precedence)
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
head.insertBefore(element, before ?? null)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function metadataTier(element: Element): number | undefined {
|
|
193
|
+
const name = element.localName
|
|
194
|
+
if (name === 'style') return element.hasAttribute('data-precedence') ? 2 : undefined
|
|
195
|
+
if (name === 'script') return element.hasAttribute('src') ? 3 : undefined
|
|
196
|
+
if (name === 'link') {
|
|
197
|
+
const relation = element.getAttribute('rel')
|
|
198
|
+
if (relation === 'stylesheet') {
|
|
199
|
+
return element.hasAttribute('precedence') || element.hasAttribute('data-precedence')
|
|
200
|
+
? 2
|
|
201
|
+
: undefined
|
|
202
|
+
}
|
|
203
|
+
if (['preconnect', 'dns-prefetch', 'preload', 'modulepreload'].includes(relation ?? ''))
|
|
204
|
+
return 0
|
|
205
|
+
return 1
|
|
206
|
+
}
|
|
207
|
+
return name === 'title' || name === 'meta' ? 1 : undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function stylePrecedence(element: Element): string {
|
|
211
|
+
return element.getAttribute('precedence') ?? element.getAttribute('data-precedence') ?? 'default'
|
|
212
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { binding, isCompiledBinding } from './compiled/core.ts'
|
|
2
|
+
|
|
3
|
+
const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
|
|
4
|
+
|
|
5
|
+
type ContainerFallback = (() => unknown) | null
|
|
6
|
+
|
|
7
|
+
export function nestedProp(
|
|
8
|
+
input: unknown,
|
|
9
|
+
path: readonly string[],
|
|
10
|
+
containerFallbacks: readonly ContainerFallback[],
|
|
11
|
+
): unknown {
|
|
12
|
+
const read = (): unknown => {
|
|
13
|
+
let value = isCompiledBinding(input) ? input[1]() : input
|
|
14
|
+
for (const [index, name] of path.entries()) {
|
|
15
|
+
if (value === undefined) value = containerFallbacks[index]?.()
|
|
16
|
+
if (value === null || value === undefined) {
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
DEV ? `cannot destructure nested prop ${name} from a nullish value` : 'V015',
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
value = (value as Record<string, unknown>)[name]
|
|
22
|
+
}
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return isCompiledBinding(input) ? binding(input[2], input[3], read, input[4], input[5]) : read()
|
|
27
|
+
}
|
package/src/profiling.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
captureCompiledOwnerStack,
|
|
3
|
+
enableProfiling,
|
|
4
|
+
profiled,
|
|
5
|
+
recordCompiledDebugValue,
|
|
6
|
+
} from './compiled/core.ts'
|
|
7
|
+
import type {
|
|
8
|
+
CompiledBinding,
|
|
9
|
+
CompiledRenderValue,
|
|
10
|
+
ProfilerOnRender,
|
|
11
|
+
StructuralBinding,
|
|
12
|
+
} from './compiled/types.ts'
|
|
13
|
+
|
|
14
|
+
export interface ProfilerProps {
|
|
15
|
+
readonly children?: (() => CompiledRenderValue) | readonly [() => CompiledRenderValue]
|
|
16
|
+
readonly id: string | CompiledBinding<string>
|
|
17
|
+
readonly onRender: ProfilerOnRender | CompiledBinding<ProfilerOnRender>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Profiler(props: ProfilerProps): StructuralBinding {
|
|
21
|
+
const render = Array.isArray(props.children) ? props.children[0] : props.children
|
|
22
|
+
if (typeof render !== 'function') {
|
|
23
|
+
throw new TypeError('Profiler children must be a compiler-generated render function')
|
|
24
|
+
}
|
|
25
|
+
enableProfiling()
|
|
26
|
+
return profiled(props.id, props.onRender, render)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useDebugValue<Value>(
|
|
30
|
+
value: Value | CompiledBinding<Value>,
|
|
31
|
+
format?: (value: Value) => unknown,
|
|
32
|
+
): void {
|
|
33
|
+
if (format !== undefined && typeof format !== 'function') {
|
|
34
|
+
throw new TypeError('useDebugValue formatter must be a function')
|
|
35
|
+
}
|
|
36
|
+
enableProfiling()
|
|
37
|
+
recordCompiledDebugValue(value, format)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function captureOwnerStack(): string | null {
|
|
41
|
+
enableProfiling()
|
|
42
|
+
return captureCompiledOwnerStack()
|
|
43
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const VIDACT_RUNTIME_PROTOCOL = 'vidact-runtime-v2'
|
|
2
|
+
|
|
3
|
+
export function assertRuntimeProtocol(expected: string): void {
|
|
4
|
+
if (expected === VIDACT_RUNTIME_PROTOCOL) return
|
|
5
|
+
throw new Error(
|
|
6
|
+
`Vidact compiler/runtime protocol mismatch: compiled for ${expected}, runtime provides ${VIDACT_RUNTIME_PROTOCOL}`,
|
|
7
|
+
)
|
|
8
|
+
}
|
package/src/raw-html.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isCompiledBinding,
|
|
3
|
+
mountCompiledPropTransition,
|
|
4
|
+
type CompiledPropTransition,
|
|
5
|
+
} from './compiled/core.ts'
|
|
6
|
+
import { HYDRATION_PREFIX, HydrationMismatch, isHydrating } from './hydration-bridge.ts'
|
|
7
|
+
|
|
8
|
+
const RAW_HTML_PUBLICATION_PRIORITY = 100
|
|
9
|
+
const rawHtmlHosts = new WeakSet<HTMLElement>()
|
|
10
|
+
const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
|
|
11
|
+
|
|
12
|
+
export function mountRawHtmlProp(
|
|
13
|
+
element: HTMLElement,
|
|
14
|
+
value: unknown,
|
|
15
|
+
children: readonly unknown[],
|
|
16
|
+
): void {
|
|
17
|
+
if (value === null || value === undefined) return
|
|
18
|
+
if (isCompiledBinding(value)) {
|
|
19
|
+
mountCompiledPropTransition(
|
|
20
|
+
value,
|
|
21
|
+
(initial) => mountRawHtml(element, initial, children),
|
|
22
|
+
(next, previous) => prepareRawHtml(element, next, previous, children),
|
|
23
|
+
)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
mountRawHtml(element, value, children)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function mountRawHtml(element: HTMLElement, value: unknown, children: readonly unknown[]): void {
|
|
30
|
+
const html = readRawHtml(element, value, children)
|
|
31
|
+
if (html === null || html === undefined) return
|
|
32
|
+
const container = rawHtmlContainer(element)
|
|
33
|
+
const staged = parseRawHtml(element, html)
|
|
34
|
+
if (isHydrating()) {
|
|
35
|
+
if (!hasEqualHydrationRawChildren(container, staged)) {
|
|
36
|
+
throw new HydrationMismatch('server raw HTML does not match the client value')
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
container.replaceChildren(adoptRawHtml(element, staged))
|
|
40
|
+
}
|
|
41
|
+
rawHtmlHosts.add(element)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasEqualHydrationRawChildren(container: Node, expected: DocumentFragment): boolean {
|
|
45
|
+
const start = container.firstChild
|
|
46
|
+
const end = container.lastChild
|
|
47
|
+
if (
|
|
48
|
+
!(start instanceof Comment) ||
|
|
49
|
+
start.data !== `${HYDRATION_PREFIX}:h` ||
|
|
50
|
+
!(end instanceof Comment) ||
|
|
51
|
+
end.data !== `/${HYDRATION_PREFIX}:h`
|
|
52
|
+
) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
const actual: Node[] = []
|
|
56
|
+
for (let node = start.nextSibling; node !== null && node !== end; node = node.nextSibling) {
|
|
57
|
+
actual.push(node)
|
|
58
|
+
}
|
|
59
|
+
if (actual.length !== expected.childNodes.length) return false
|
|
60
|
+
for (let index = 0; index < actual.length; index += 1) {
|
|
61
|
+
if (!actual[index]?.isEqualNode(expected.childNodes[index] ?? null)) return false
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function prepareRawHtml(
|
|
67
|
+
element: HTMLElement,
|
|
68
|
+
nextValue: unknown,
|
|
69
|
+
previousValue: unknown,
|
|
70
|
+
children: readonly unknown[],
|
|
71
|
+
): CompiledPropTransition | undefined {
|
|
72
|
+
const nextHtml = readRawHtml(element, nextValue, children)
|
|
73
|
+
const previousHtml = readRawHtml(element, previousValue, children)
|
|
74
|
+
if (nextHtml === null || nextHtml === undefined || nextHtml === previousHtml) return undefined
|
|
75
|
+
|
|
76
|
+
const staged = parseRawHtml(element, nextHtml)
|
|
77
|
+
const container = rawHtmlContainer(element)
|
|
78
|
+
const wasRawHtmlHost = rawHtmlHosts.has(element)
|
|
79
|
+
let previousNodes: Node[] | undefined
|
|
80
|
+
return [
|
|
81
|
+
() => {
|
|
82
|
+
// A reactive script `type` prop commits before the raw subtree.
|
|
83
|
+
assertExecutableRawHtmlTarget(element)
|
|
84
|
+
const adopted = adoptRawHtml(element, staged)
|
|
85
|
+
previousNodes = [...container.childNodes]
|
|
86
|
+
container.replaceChildren(adopted)
|
|
87
|
+
rawHtmlHosts.add(element)
|
|
88
|
+
},
|
|
89
|
+
() => {
|
|
90
|
+
if (previousNodes === undefined) return
|
|
91
|
+
container.replaceChildren(...previousNodes)
|
|
92
|
+
if (!wasRawHtmlHost) rawHtmlHosts.delete(element)
|
|
93
|
+
previousNodes = undefined
|
|
94
|
+
},
|
|
95
|
+
undefined,
|
|
96
|
+
undefined,
|
|
97
|
+
RAW_HTML_PUBLICATION_PRIORITY,
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function validateRawHtmlRelatedProp(element: HTMLElement, name: string): void {
|
|
102
|
+
if (name === 'type' && rawHtmlHosts.has(element)) assertExecutableRawHtmlTarget(element)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readRawHtml(element: HTMLElement, value: unknown, children: readonly unknown[]): unknown {
|
|
106
|
+
if (value === null || value === undefined) return undefined
|
|
107
|
+
assertRawHtmlPropTarget(element)
|
|
108
|
+
if (typeof value !== 'object' || !('__html' in value)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
DEV
|
|
111
|
+
? '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://react.dev/link/dangerously-set-inner-html for more information.'
|
|
112
|
+
: 'V601',
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
const html = value['__html']
|
|
116
|
+
if (html === null || html === undefined) return html
|
|
117
|
+
if (children.some((child) => child !== null && child !== undefined)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
DEV ? 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' : 'V602',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
assertExecutableRawHtmlTarget(element)
|
|
123
|
+
return html
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function assertRawHtmlPropTarget(element: HTMLElement): void {
|
|
127
|
+
const tag = element.localName
|
|
128
|
+
if (isVoidHtmlElement(tag)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
DEV
|
|
131
|
+
? `${tag} is a void element tag and must neither have \`children\` nor use \`dangerouslySetInnerHTML\`.`
|
|
132
|
+
: 'V603',
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
if (tag === 'textarea') {
|
|
136
|
+
throw new Error(DEV ? '`dangerouslySetInnerHTML` does not make sense on <textarea>.' : 'V604')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function assertExecutableRawHtmlTarget(element: HTMLElement): void {
|
|
141
|
+
const tag = element.localName
|
|
142
|
+
if (tag === 'script' && isExecutableScript(element as HTMLScriptElement)) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
DEV
|
|
145
|
+
? 'dangerouslySetInnerHTML on an executable <script> is unsupported because direct-created scripts execute when connected; use a non-executable data MIME type'
|
|
146
|
+
: 'V605',
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isVoidHtmlElement(tag: string): boolean {
|
|
152
|
+
return (
|
|
153
|
+
tag === 'area' ||
|
|
154
|
+
tag === 'base' ||
|
|
155
|
+
tag === 'br' ||
|
|
156
|
+
tag === 'col' ||
|
|
157
|
+
tag === 'embed' ||
|
|
158
|
+
tag === 'hr' ||
|
|
159
|
+
tag === 'img' ||
|
|
160
|
+
tag === 'input' ||
|
|
161
|
+
tag === 'keygen' ||
|
|
162
|
+
tag === 'link' ||
|
|
163
|
+
tag === 'menuitem' ||
|
|
164
|
+
tag === 'meta' ||
|
|
165
|
+
tag === 'param' ||
|
|
166
|
+
tag === 'source' ||
|
|
167
|
+
tag === 'track' ||
|
|
168
|
+
tag === 'wbr'
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isExecutableScript(element: HTMLScriptElement): boolean {
|
|
173
|
+
const type = element.type.trim().toLowerCase().split(';', 1)[0] ?? ''
|
|
174
|
+
if (type === '' || type === 'module' || type === 'importmap' || type === 'speculationrules') {
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
return (
|
|
178
|
+
/^(?:application|text)\/(?:x-)?(?:java|ecma)script(?:1\.[0-5])?$/.test(type) ||
|
|
179
|
+
type === 'text/jscript' ||
|
|
180
|
+
type === 'text/livescript'
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseRawHtml(element: HTMLElement, html: unknown): DocumentFragment {
|
|
185
|
+
const stagingDocument = element.ownerDocument.implementation.createHTMLDocument()
|
|
186
|
+
const staging =
|
|
187
|
+
element.localName === 'noscript'
|
|
188
|
+
? (element.cloneNode(false) as HTMLElement)
|
|
189
|
+
: stagingDocument.createElement(element.localName.includes('-') ? 'div' : element.localName)
|
|
190
|
+
Reflect.set(staging, 'innerHTML', html)
|
|
191
|
+
const fragment = staging.ownerDocument.createDocumentFragment()
|
|
192
|
+
fragment.append(...rawHtmlContainer(staging).childNodes)
|
|
193
|
+
return fragment
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function rawHtmlContainer(element: HTMLElement): HTMLElement | DocumentFragment {
|
|
197
|
+
return element instanceof HTMLTemplateElement ? element.content : element
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function adoptRawHtml(element: HTMLElement, staged: DocumentFragment): DocumentFragment {
|
|
201
|
+
if (staged.ownerDocument === element.ownerDocument) return staged
|
|
202
|
+
const adopted = element.ownerDocument.createDocumentFragment()
|
|
203
|
+
adopted.append(...staged.childNodes)
|
|
204
|
+
element.ownerDocument.defaultView?.customElements.upgrade(adopted)
|
|
205
|
+
return adopted
|
|
206
|
+
}
|
package/src/ref.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MutableRef<T> {
|
|
2
|
+
current: T
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** Creates the plain ref object used by dependency initialization outside component hooks. */
|
|
6
|
+
export function createRef<T>(): MutableRef<T | null> {
|
|
7
|
+
return { current: null }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Creates the stable ref cell captured by a component's one-time execution. */
|
|
11
|
+
export function useRef<T>(initialValue: T): MutableRef<T> {
|
|
12
|
+
return { current: initialValue }
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const RENDERABLE = Symbol.for('vidact.v1.Renderable')
|
|
2
|
+
|
|
3
|
+
export interface RenderableProtocol {
|
|
4
|
+
readonly [RENDERABLE]: {
|
|
5
|
+
readonly identity: unknown
|
|
6
|
+
readonly input: unknown
|
|
7
|
+
readonly reconcile: boolean
|
|
8
|
+
readonly construct: (input: never) => unknown
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cloning a renderable pulls in the element-construction module, which most
|
|
14
|
+
* compiled apps never touch. The renderable module registers its clone the
|
|
15
|
+
* first time an element capability is created, so `jsx` can reach it without
|
|
16
|
+
* importing it, and bundles that never build elements leave it out.
|
|
17
|
+
*/
|
|
18
|
+
export type RenderableClone = (
|
|
19
|
+
value: RenderableProtocol,
|
|
20
|
+
overrides: Record<string, unknown>,
|
|
21
|
+
) => unknown
|
|
22
|
+
|
|
23
|
+
let renderableClone: RenderableClone | undefined
|
|
24
|
+
|
|
25
|
+
export function installRenderableClone(clone: RenderableClone): void {
|
|
26
|
+
renderableClone = clone
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderableCloneHandler(): RenderableClone | undefined {
|
|
30
|
+
return renderableClone
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isRenderableProtocol(value: unknown): value is RenderableProtocol {
|
|
34
|
+
return (
|
|
35
|
+
typeof value === 'object' &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
Object.hasOwn(value, RENDERABLE) &&
|
|
38
|
+
typeof (value as RenderableProtocol)[RENDERABLE]?.construct === 'function'
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function materializeRenderable(value: RenderableProtocol): unknown {
|
|
43
|
+
const internals = value[RENDERABLE]
|
|
44
|
+
return internals.construct(internals.input as never)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function materializeRenderableWithInput(value: RenderableProtocol, input: unknown): unknown {
|
|
48
|
+
return value[RENDERABLE].construct(input as never)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderableIdentity(value: RenderableProtocol): unknown {
|
|
52
|
+
return value[RENDERABLE].identity
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function canReconcileRenderable(value: RenderableProtocol): boolean {
|
|
56
|
+
return value[RENDERABLE].reconcile
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function renderablePropsSnapshot(value: RenderableProtocol): Record<PropertyKey, unknown> {
|
|
60
|
+
const props = (value as RenderableProtocol & { readonly props: Record<PropertyKey, unknown> })
|
|
61
|
+
.props
|
|
62
|
+
return Object.fromEntries(Reflect.ownKeys(props).map((name) => [name, Reflect.get(props, name)]))
|
|
63
|
+
}
|