Skip to content
15 changes: 9 additions & 6 deletions packages/compiler-vapor/src/generators/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@
if (delegate) {
// key is static
context.delegates.add(key.content)
// if this is the only delegated event of this name on this element,
// we can generate optimized handler attachment code
// e.g. n1.$evtclick = () => {}
if (!context.block.operation.some(isSameDelegateEvent)) {
return [NEWLINE, `n${element}.$evt${key.content} = `, ...handler]
}

// TODO: If it is SSR hydration, it can't be generated here, but I don't know how to judge whether it's SSR hydration, so I'll annotate it first.

// // if this is the only delegated event of this name on this element,
// // we can generate optimized handler attachment code
// // e.g. n1.$evtclick = () => {}
// if (!context.block.operation.some(isSameDelegateEvent)) {
// return [NEWLINE, `n${element}.$evt${key.content} = `, ...handler]
// }
}

return [
Expand Down Expand Up @@ -77,7 +80,7 @@
)
}

function isSameDelegateEvent(op: OperationNode) {

Check failure on line 83 in packages/compiler-vapor/src/generators/event.ts

View workflow job for this annotation

GitHub Actions / test / lint-and-test-dts

'isSameDelegateEvent' is declared but its value is never read.
if (
op.type === IRNodeTypes.SET_EVENT &&
op !== oper &&
Expand Down
4 changes: 0 additions & 4 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import type {
ComponentInternalInstance,
ComponentOptions,
ConcreteComponent,

Check failure on line 17 in packages/runtime-core/src/hydration.ts

View workflow job for this annotation

GitHub Actions / test / lint-and-test-dts

'ConcreteComponent' is declared but never used.
} from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
Expand Down Expand Up @@ -279,10 +279,6 @@
)
}
} else if (shapeFlag & ShapeFlags.COMPONENT) {
if ((vnode.type as ConcreteComponent).__vapor) {
throw new Error('Vapor component hydration is not supported yet.')
}

// when setting up the render effect, if the initial vnode already
// has .el set, the component will perform hydration instead of mount
// on its sub-tree.
Expand Down
122 changes: 80 additions & 42 deletions packages/runtime-vapor/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import {
} from './component'
import { createComment, createTextNode } from './dom/node'
import { EffectScope, setActiveSub } from '@vue/reactivity'
import { isHydrating } from './dom/hydration'
import {
CommentDraft,
NodeDraft,
NodeRef,
type VaporNode,
type VaporParentNode,
toNode,
} from './dom/nodeDraft'

export type Block =
| Node
| VaporNode
| VaporFragment
| DynamicFragment
| VaporComponentInstance
Expand All @@ -20,17 +27,17 @@ export type BlockFn = (...args: any[]) => Block

export class VaporFragment {
nodes: Block
anchor?: Node
insert?: (parent: ParentNode, anchor: Node | null) => void
remove?: (parent?: ParentNode) => void
anchor?: VaporNode
insert?: (parent: VaporParentNode, anchor: VaporNode | null) => void
remove?: (parent?: VaporParentNode) => void

constructor(nodes: Block) {
this.nodes = nodes
}
}

export class DynamicFragment extends VaporFragment {
anchor: Node
anchor: VaporNode
scope: EffectScope | undefined
current?: BlockFn
fallback?: BlockFn
Expand All @@ -48,7 +55,7 @@ export class DynamicFragment extends VaporFragment {
this.current = key

const prevSub = setActiveSub()
const parent = this.anchor.parentNode
const parent = toNode(this.anchor).parentNode

// teardown previous branch
if (this.scope) {
Expand Down Expand Up @@ -93,6 +100,8 @@ export function isBlock(val: NonNullable<unknown>): val is Block {
export function isValidBlock(block: Block): boolean {
if (block instanceof Node) {
return !(block instanceof Comment)
} else if (block instanceof NodeRef) {
return !(block instanceof CommentDraft)
} else if (isVaporComponent(block)) {
return isValidBlock(block.block)
} else if (isArray(block)) {
Expand All @@ -105,77 +114,106 @@ export function isValidBlock(block: Block): boolean {

export function insert(
block: Block,
parent: ParentNode & { $anchor?: Node | null },
anchor: Node | null | 0 = null, // 0 means prepend
parent: VaporParentNode,
anchor: VaporNode | null | 0 = null, // 0 means prepend
): void {
anchor = anchor === 0 ? parent.$anchor || parent.firstChild : anchor
if (block instanceof Node) {
if (!isHydrating) {
parent.insertBefore(block, anchor)
const _parent = toNode(parent)
const blockOrDraft = toNode(block)

anchor = anchor === 0 ? _parent.$anchor || _parent.firstChild : anchor
if (blockOrDraft instanceof Node) {
;(_parent as Node).insertBefore(blockOrDraft, anchor as Node)
} else if (blockOrDraft instanceof NodeDraft) {
// Hydration
if (!(_parent instanceof Node)) {
const index = anchor ? _parent.childNodes.indexOf(anchor as NodeRef) : -1
if (index === -1) {
_parent.appendChild(block as NodeRef<false>)
} else {
_parent.childNodes.splice(index, 0, block as NodeRef<false>)
}
} else if (__DEV__) {
throw new Error(
'Cannot insert a NodeDraft to a real ParentNode. Did you forget to resolve it?',
)
}
} else if (isVaporComponent(block)) {
if (block.isMounted) {
insert(block.block!, parent, anchor)
} else if (isVaporComponent(blockOrDraft)) {
if (blockOrDraft.isMounted) {
insert(blockOrDraft.block!, parent, anchor)
} else {
mountComponent(block, parent, anchor)
mountComponent(blockOrDraft, parent, anchor)
}
} else if (isArray(block)) {
for (const b of block) {
} else if (isArray(blockOrDraft)) {
for (const b of blockOrDraft) {
insert(b, parent, anchor)
}
} else {
// fragment
if (block.insert) {
// TODO handle hydration for vdom interop
block.insert(parent, anchor)
if (blockOrDraft.insert) {
blockOrDraft.insert(parent, anchor)
} else {
insert(block.nodes, parent, anchor)
insert(blockOrDraft.nodes, parent, anchor)
}
if (block.anchor) insert(block.anchor, parent, anchor)
if (blockOrDraft.anchor) insert(blockOrDraft.anchor, parent, anchor)
}
}

export type InsertFn = typeof insert

export function prepend(parent: ParentNode, ...blocks: Block[]): void {
export function prepend(parent: VaporParentNode, ...blocks: Block[]): void {
let i = blocks.length
while (i--) insert(blocks[i], parent, 0)
}

export function remove(block: Block, parent?: ParentNode): void {
if (block instanceof Node) {
parent && parent.removeChild(block)
} else if (isVaporComponent(block)) {
unmountComponent(block, parent)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
remove(block[i], parent)
export function remove(block: Block, parent?: VaporParentNode): void {
const _parent = toNode(parent)
const blockOrDraft = toNode(block)

if (blockOrDraft instanceof Node) {
_parent && (_parent as Node).removeChild(blockOrDraft)
} else if (blockOrDraft instanceof NodeDraft) {
// Hydration
if (_parent && !(_parent instanceof Node)) {
const index = _parent.childNodes.indexOf(block as NodeRef)
if (index > -1) {
_parent.childNodes.splice(index, 1)
}
} else if (__DEV__) {
throw new Error(
'Cannot remove a NodeDraft from a real ParentNode. Did you forget to resolve it?',
)
}
} else if (isVaporComponent(blockOrDraft)) {
unmountComponent(blockOrDraft, parent)
} else if (isArray(blockOrDraft)) {
for (let i = 0; i < blockOrDraft.length; i++) {
remove(blockOrDraft[i], parent)
}
} else {
// fragment
if (block.remove) {
block.remove(parent)
if (blockOrDraft.remove) {
blockOrDraft.remove(parent)
} else {
remove(block.nodes, parent)
remove(blockOrDraft.nodes, parent)
}
if (block.anchor) remove(block.anchor, parent)
if ((block as DynamicFragment).scope) {
;(block as DynamicFragment).scope!.stop()
if (blockOrDraft.anchor) remove(blockOrDraft.anchor, parent)
if ((blockOrDraft as DynamicFragment).scope) {
;(blockOrDraft as DynamicFragment).scope!.stop()
}
}
}

/**
* dev / test only
*/
export function normalizeBlock(block: Block): Node[] {
export function normalizeBlock(block: Block): VaporNode[] {
if (!__DEV__ && !__TEST__) {
throw new Error(
'normalizeBlock should not be used in production code paths',
)
}
const nodes: Node[] = []
if (block instanceof Node) {
const nodes: VaporNode[] = []
if (block instanceof Node || block instanceof NodeRef) {
nodes.push(block)
} else if (isArray(block)) {
block.forEach(child => nodes.push(...normalizeBlock(child)))
Expand Down
31 changes: 27 additions & 4 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ import {
insertionParent,
resetInsertionState,
} from './insertionState'
import {
NodeRef,
type VaporNode,
type VaporParentNode,
nodeDraftPatch,
} from './dom/nodeDraft'

export { currentInstance } from '@vue/runtime-dom'

Expand Down Expand Up @@ -502,6 +508,7 @@ export function createComponentWithFallback(
const el = document.createElement(comp)
// mark single root
;(el as any).$root = isSingleRoot
;(el as any).$isSingleRoot = isSingleRoot

if (rawProps) {
renderEffect(() => {
Expand All @@ -526,14 +533,30 @@ export function createComponentWithFallback(

export function mountComponent(
instance: VaporComponentInstance,
parent: ParentNode,
anchor?: Node | null | 0,
parent: VaporParentNode,
anchor?: VaporNode | null | 0,
): void {
if (__DEV__) {
startMeasure(instance, `mount`)
}
if (instance.bm) invokeArrayFns(instance.bm)
insert(instance.block, parent, anchor)
const block = instance.block
if (
isHydrating &&
(Array.isArray(block)
? block[0] instanceof NodeRef
: block instanceof NodeRef) &&
parent instanceof Node
) {
if (anchor && !(anchor instanceof Node)) {
throw new Error(
'When mount a Vapor Instance. Cannot use NodeDraft as an anchor, a Node is needed',
)
}
nodeDraftPatch(block as NodeRef[], parent, anchor)
} else {
insert(block, parent, anchor)
}
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
instance.isMounted = true
if (__DEV__) {
Expand All @@ -543,7 +566,7 @@ export function mountComponent(

export function unmountComponent(
instance: VaporComponentInstance,
parentNode?: ParentNode,
parentNode?: VaporParentNode,
): void {
if (instance.isMounted && !instance.isUnmounted) {
if (__DEV__ && instance.type.__hmrId) {
Expand Down
4 changes: 3 additions & 1 deletion packages/runtime-vapor/src/dom/event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { onEffectCleanup } from '@vue/reactivity'
import { isArray } from '@vue/shared'
import { toNode } from './nodeDraft'

export function addEventListener(
el: Element,
Expand All @@ -26,10 +27,11 @@ export function on(
}

export function delegate(
el: any,
_el: any,
event: string,
handler: (e: Event) => any,
): void {
const el = toNode(_el)
const key = `$evt${event}`
const existing = el[key]
if (existing) {
Expand Down
58 changes: 46 additions & 12 deletions packages/runtime-vapor/src/dom/node.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,63 @@
/*! #__NO_SIDE_EFFECTS__ */
export function createTextNode(value = ''): Text {

import { isHydrating } from './hydration'
import {
CommentDraft,
type NodeDraft,
NodeRef,
TextNodeDraft,
type VaporNode,
type VaporParentNode,
isUnresolvedVaporNode,
toNode,
} from './nodeDraft'

export function createTextNode(value = ''): VaporNode<Text, TextNodeDraft> {
if (isHydrating) {
const node = new NodeRef<boolean, Text, TextNodeDraft>(TextNodeDraft)
node.ref.textContent
return node
}
return document.createTextNode(value)
}

/*! #__NO_SIDE_EFFECTS__ */
export function createComment(data: string): Comment {
export function createComment(data: string): VaporNode<Comment, CommentDraft> {
if (isHydrating) {
const node = new NodeRef<boolean, Comment, CommentDraft>(CommentDraft)
node.ref.data
return node
}
return document.createComment(data)
}

/*! #__NO_SIDE_EFFECTS__ */
export function querySelector(selectors: string): Element | null {
return document.querySelector(selectors)
}
export function child(node: VaporParentNode): VaporNode {
if (isUnresolvedVaporNode(node) && !node.ref.childNodes[0]) {
return (node.ref.setChild(0, new NodeRef()), node.ref.childNodes[0])
}

/*! #__NO_SIDE_EFFECTS__ */
export function child(node: ParentNode): Node {
return node.firstChild!
return toNode(node).firstChild!
}

/*! #__NO_SIDE_EFFECTS__ */
export function nthChild(node: Node, i: number): Node {
return node.childNodes[i]
export function nthChild(node: ParentNode, i: number): VaporNode {
if (isUnresolvedVaporNode(node) && !node.ref.childNodes[i]) {
return node.ref.setChild(i, new NodeRef())
}

return toNode(node).childNodes[i]
}

/*! #__NO_SIDE_EFFECTS__ */
export function next(node: Node): Node {
return node.nextSibling!
export function next(node: VaporParentNode): VaporNode {
if (isUnresolvedVaporNode(node) && !node.ref.nextSibling) {
const parentDraft = node.ref.parentNode!.ref as NodeDraft
return parentDraft.setChild(
parentDraft.childNodes.indexOf(node) + 1,
new NodeRef(),
)
}

return toNode(node).nextSibling!
}
Loading
Loading