feat: support more directive hook

This commit is contained in:
三咲智子 Kevin Deng 2023-12-04 16:08:15 +08:00
parent db151e1b43
commit f3e80d7706
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
5 changed files with 91 additions and 36 deletions

View File

@ -19,9 +19,10 @@ import {
VaporHelper, VaporHelper,
IRExpression, IRExpression,
SetEventIRNode, SetEventIRNode,
WithDirectiveIRNode,
} from './ir' } from './ir'
import { SourceMapGenerator } from 'source-map-js' import { SourceMapGenerator } from 'source-map-js'
import { camelize, capitalize, isString } from '@vue/shared' import { camelize, isString } from '@vue/shared'
// remove when stable // remove when stable
// @ts-expect-error // @ts-expect-error
@ -249,9 +250,17 @@ export function generate(
) )
} }
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) { for (const operation of ir.operation) {
genOperation(operation, ctx) genOperation(operation, ctx)
} }
for (const { operations } of ir.effect) { for (const { operations } of ir.effect) {
pushWithNewline(`${vaporHelper('effect')}(() => {`) pushWithNewline(`${vaporHelper('effect')}(() => {`)
indent() indent()
@ -261,6 +270,7 @@ export function generate(
deindent() deindent()
pushWithNewline('})') pushWithNewline('})')
} }
// TODO multiple-template // TODO multiple-template
// TODO return statement in IR // TODO return statement in IR
pushWithNewline(`return n${ir.dynamic.id}`) pushWithNewline(`return n${ir.dynamic.id}`)
@ -363,20 +373,7 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
return return
} }
case IRNodeTypes.WITH_DIRECTIVE: { case IRNodeTypes.WITH_DIRECTIVE: {
// TODO merge directive for the same node // generated, skip
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(']])')
return return
} }
default: default:
@ -483,3 +480,23 @@ function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
push(')') 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
}

View File

@ -24,16 +24,12 @@ export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
export const setCurrentInstance = (instance: ComponentInternalInstance) => { export const setCurrentInstance = (instance: ComponentInternalInstance) => {
currentInstance = instance currentInstance = instance
instance.scope.on()
} }
export const unsetCurrentInstance = () => { export const unsetCurrentInstance = () => {
currentInstance && currentInstance.scope.off()
currentInstance = null currentInstance = null
} }
export interface ComponentPublicInstance {}
let uid = 0 let uid = 0
export const createComponentInstance = ( export const createComponentInstance = (
component: BlockFn, component: BlockFn,

View File

@ -1,8 +1,8 @@
import { isFunction } from '@vue/shared' import { type Prettify, isFunction } from '@vue/shared'
import { currentInstance, type ComponentPublicInstance } from './component' import { currentInstance, ComponentInternalInstance } from './component'
export interface DirectiveBinding<V = any> { export interface DirectiveBinding<V = any> {
instance: ComponentPublicInstance | null instance: ComponentInternalInstance | null
value: V value: V
oldValue: V | null oldValue: V | null
arg?: string arg?: string
@ -21,15 +21,16 @@ export type DirectiveHook<T = any | null, V = any> = (
// `beforeUnmount`-> node unmount -> `unmounted` // `beforeUnmount`-> node unmount -> `unmounted`
export interface ObjectDirective<T = any, V = any> { export interface ObjectDirective<T = any, V = any> {
created?: DirectiveHook<T, V> created?: DirectiveHook<T, V>
// beforeMount?: DirectiveHook<T, V> beforeMount?: DirectiveHook<T, V>
// mounted?: DirectiveHook<T, V> mounted?: DirectiveHook<T, V>
// beforeUpdate?: DirectiveHook<T, V> // beforeUpdate?: DirectiveHook<T, V>
// updated?: DirectiveHook<T, V> // updated?: DirectiveHook<T, V>
// beforeUnmount?: DirectiveHook<T, V> beforeUnmount?: DirectiveHook<T, V>
// unmounted?: DirectiveHook<T, V> unmounted?: DirectiveHook<T, V>
// getSSRProps?: SSRDirectiveHook // 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 FunctionDirective<T = any, V = any> = DirectiveHook<T, V>
export type Directive<T = any, V = any> = export type Directive<T = any, V = any> =
@ -54,8 +55,6 @@ export function withDirectives<T extends Node>(
if (!currentInstance.dirs.has(node)) currentInstance.dirs.set(node, []) if (!currentInstance.dirs.has(node)) currentInstance.dirs.set(node, [])
const bindings = currentInstance.dirs.get(node)! const bindings = currentInstance.dirs.get(node)!
// TODO public instance
const instance = currentInstance as any
for (const directive of directives) { for (const directive of directives) {
let [dir, value, arg] = directive let [dir, value, arg] = directive
if (!dir) continue if (!dir) continue
@ -68,7 +67,7 @@ export function withDirectives<T extends Node>(
const binding: DirectiveBinding = { const binding: DirectiveBinding = {
dir, dir,
instance, instance: currentInstance,
value, value,
oldValue: void 0, oldValue: void 0,
arg, arg,
@ -79,3 +78,21 @@ export function withDirectives<T extends Node>(
return node 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)
}
}
}

View File

@ -4,12 +4,13 @@ import {
normalizeStyle, normalizeStyle,
toDisplayString, toDisplayString,
} from '@vue/shared' } from '@vue/shared'
import { import {
ComponentInternalInstance, ComponentInternalInstance,
createComponentInstance, createComponentInstance,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance,
} from './component' } from './component'
import { invokeDirectiveHook } from './directives'
export type Block = Node | Fragment | Block[] export type Block = Node | Fragment | Block[]
export type ParentBlock = ParentNode | Node[] export type ParentBlock = ParentNode | Node[]
@ -37,11 +38,17 @@ export const mountComponent = (
container: ParentNode, container: ParentNode,
) => { ) => {
instance.container = container instance.container = container
setCurrentInstance(instance)
const block = instance.scope.run( const block = instance.scope.run(
() => (instance.block = instance.component()), () => (instance.block = instance.component()),
)! )!
invokeDirectiveHook(instance, 'beforeMount')
insert(block, instance.container) insert(block, instance.container)
instance.isMounted = true instance.isMounted = true
invokeDirectiveHook(instance, 'mounted')
// TODO: lifecycle hooks (mounted, ...) // TODO: lifecycle hooks (mounted, ...)
// const { m } = instance // const { m } = instance
// m && invoke(m) // m && invoke(m)
@ -49,9 +56,14 @@ export const mountComponent = (
export const unmountComponent = (instance: ComponentInternalInstance) => { export const unmountComponent = (instance: ComponentInternalInstance) => {
const { container, block, scope } = instance const { container, block, scope } = instance
invokeDirectiveHook(instance, 'beforeUnmount')
scope.stop() scope.stop()
block && remove(block, container) block && remove(block, container)
instance.isMounted = false instance.isMounted = false
invokeDirectiveHook(instance, 'unmounted')
unsetCurrentInstance()
// TODO: lifecycle hooks (unmounted, ...) // TODO: lifecycle hooks (unmounted, ...)
// const { um } = instance // const { um } = instance
// um && invoke(um) // um && invoke(um)

View File

@ -1,12 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { FunctionDirective } from '@vue/vapor' import { ObjectDirective } from '@vue/vapor'
const vDirective: FunctionDirective<HTMLDivElement, undefined> = node => { const text = 'created (overwrite by v-text), '
node.textContent = 'hello world' const vDirective: ObjectDirective<HTMLDivElement, undefined> = {
node.style.color = 'red' 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> </script>
<template> <template>
<div v-directive /> <div v-directive v-text="text" />
</template> </template>