VaporHelper,
IRExpression,
SetEventIRNode,
+ WithDirectiveIRNode,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
-import { camelize, capitalize, isString } from '@vue/shared'
+import { camelize, isString } from '@vue/shared'
// remove when stable
// @ts-expect-error
)
}
+ for (const oper of ir.operation.filter(
+ (oper): oper is WithDirectiveIRNode =>
+ oper.type === IRNodeTypes.WITH_DIRECTIVE,
+ )) {
+ genWithDirective(oper, ctx)
+ }
+
for (const operation of ir.operation) {
genOperation(operation, ctx)
}
+
for (const { operations } of ir.effect) {
pushWithNewline(`${vaporHelper('effect')}(() => {`)
indent()
deindent()
pushWithNewline('})')
}
+
// TODO multiple-template
// TODO return statement in IR
pushWithNewline(`return n${ir.dynamic.id}`)
return
}
case IRNodeTypes.WITH_DIRECTIVE: {
- // TODO merge directive for the same node
- pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
-
- // TODO resolve directive
- const directiveReference = camelize(`v-${oper.name}`)
- if (context.bindingMetadata[directiveReference]) {
- genExpression(createSimpleExpression(directiveReference), context)
- }
-
- if (oper.binding) {
- push(', ')
- genExpression(oper.binding, context)
- }
- push(']])')
+ // generated, skip
return
}
default:
push(')')
}
+
+function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
+ const { push, pushWithNewline, vaporHelper, bindingMetadata } = context
+
+ // TODO merge directive for the same node
+ pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
+
+ // TODO resolve directive
+ const directiveReference = camelize(`v-${oper.name}`)
+ if (bindingMetadata[directiveReference]) {
+ genExpression(createSimpleExpression(directiveReference), context)
+ }
+
+ if (oper.binding) {
+ push(', ')
+ genExpression(oper.binding, context)
+ }
+ push(']])')
+ return
+}
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
currentInstance = instance
- instance.scope.on()
}
export const unsetCurrentInstance = () => {
- currentInstance && currentInstance.scope.off()
currentInstance = null
}
-export interface ComponentPublicInstance {}
-
let uid = 0
export const createComponentInstance = (
component: BlockFn,
-import { isFunction } from '@vue/shared'
-import { currentInstance, type ComponentPublicInstance } from './component'
+import { type Prettify, isFunction } from '@vue/shared'
+import { currentInstance, ComponentInternalInstance } from './component'
export interface DirectiveBinding<V = any> {
- instance: ComponentPublicInstance | null
+ instance: ComponentInternalInstance | null
value: V
oldValue: V | null
arg?: string
// `beforeUnmount`-> node unmount -> `unmounted`
export interface ObjectDirective<T = any, V = any> {
created?: DirectiveHook<T, V>
- // beforeMount?: DirectiveHook<T, V>
- // mounted?: DirectiveHook<T, V>
+ beforeMount?: DirectiveHook<T, V>
+ mounted?: DirectiveHook<T, V>
// beforeUpdate?: DirectiveHook<T, V>
// updated?: DirectiveHook<T, V>
- // beforeUnmount?: DirectiveHook<T, V>
- // unmounted?: DirectiveHook<T, V>
+ beforeUnmount?: DirectiveHook<T, V>
+ unmounted?: DirectiveHook<T, V>
// getSSRProps?: SSRDirectiveHook
- deep?: boolean
+ // deep?: boolean
}
+export type DirectiveHookName = Exclude<keyof ObjectDirective, 'deep'>
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, V>
export type Directive<T = any, V = any> =
if (!currentInstance.dirs.has(node)) currentInstance.dirs.set(node, [])
const bindings = currentInstance.dirs.get(node)!
- // TODO public instance
- const instance = currentInstance as any
for (const directive of directives) {
let [dir, value, arg] = directive
if (!dir) continue
const binding: DirectiveBinding = {
dir,
- instance,
+ instance: currentInstance,
value,
oldValue: void 0,
arg,
return node
}
+
+export function invokeDirectiveHook(
+ instance: ComponentInternalInstance | null,
+ name: DirectiveHookName,
+ nodes?: IterableIterator<Node>,
+) {
+ if (!instance) return
+ if (!nodes) {
+ nodes = instance.dirs.keys()
+ }
+ for (const node of nodes) {
+ const directives = instance.dirs.get(node) || []
+ for (const binding of directives) {
+ const hook = binding.dir[name]
+ hook && hook(node, binding)
+ }
+ }
+}
normalizeStyle,
toDisplayString,
} from '@vue/shared'
-
import {
ComponentInternalInstance,
createComponentInstance,
setCurrentInstance,
+ unsetCurrentInstance,
} from './component'
+import { invokeDirectiveHook } from './directives'
export type Block = Node | Fragment | Block[]
export type ParentBlock = ParentNode | Node[]
container: ParentNode,
) => {
instance.container = container
+
+ setCurrentInstance(instance)
const block = instance.scope.run(
() => (instance.block = instance.component()),
)!
+
+ invokeDirectiveHook(instance, 'beforeMount')
insert(block, instance.container)
instance.isMounted = true
+ invokeDirectiveHook(instance, 'mounted')
+
// TODO: lifecycle hooks (mounted, ...)
// const { m } = instance
// m && invoke(m)
export const unmountComponent = (instance: ComponentInternalInstance) => {
const { container, block, scope } = instance
+
+ invokeDirectiveHook(instance, 'beforeUnmount')
scope.stop()
block && remove(block, container)
instance.isMounted = false
+ invokeDirectiveHook(instance, 'unmounted')
+ unsetCurrentInstance()
+
// TODO: lifecycle hooks (unmounted, ...)
// const { um } = instance
// um && invoke(um)
<script setup lang="ts">
-import { FunctionDirective } from '@vue/vapor'
+import { ObjectDirective } from '@vue/vapor'
-const vDirective: FunctionDirective<HTMLDivElement, undefined> = node => {
- node.textContent = 'hello world'
- node.style.color = 'red'
+const text = 'created (overwrite by v-text), '
+const vDirective: ObjectDirective<HTMLDivElement, undefined> = {
+ created(node) {
+ if (!node.parentElement) {
+ node.textContent += 'created, '
+ node.style.color = 'red'
+ } else {
+ alert('!')
+ }
+ },
+ beforeMount(node) {
+ if (!node.parentElement) node.textContent += 'beforeMount, '
+ },
+ mounted(node) {
+ if (node.parentElement) node.textContent += 'mounted, '
+ }
}
</script>
<template>
- <div v-directive />
+ <div v-directive v-text="text" />
</template>