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,
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
@ -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) {
genOperation(operation, ctx)
}
for (const { operations } of ir.effect) {
pushWithNewline(`${vaporHelper('effect')}(() => {`)
indent()
@ -261,6 +270,7 @@ export function generate(
deindent()
pushWithNewline('})')
}
// TODO multiple-template
// TODO return statement in IR
pushWithNewline(`return n${ir.dynamic.id}`)
@ -363,20 +373,7 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
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:
@ -483,3 +480,23 @@ function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
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) => {
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,

View File

@ -1,8 +1,8 @@
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
@ -21,15 +21,16 @@ export type DirectiveHook<T = any | null, V = any> = (
// `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> =
@ -54,8 +55,6 @@ export function withDirectives<T extends Node>(
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
@ -68,7 +67,7 @@ export function withDirectives<T extends Node>(
const binding: DirectiveBinding = {
dir,
instance,
instance: currentInstance,
value,
oldValue: void 0,
arg,
@ -79,3 +78,21 @@ export function withDirectives<T extends 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,
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[]
@ -37,11 +38,17 @@ export const mountComponent = (
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)
@ -49,9 +56,14 @@ export const mountComponent = (
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)

View File

@ -1,12 +1,25 @@
<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>