]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: support more directive hook
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Mon, 4 Dec 2023 08:08:15 +0000 (16:08 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Mon, 4 Dec 2023 08:08:15 +0000 (16:08 +0800)
packages/compiler-vapor/src/generate.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/directives.ts
packages/runtime-vapor/src/render.ts
playground/src/directive.vue

index 0b84f863878a2b23255751608fdc3e7a9babc26b..4cd284e84a969867892a2aef3d93d55d74631d77 100644 (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
+}
index c6195f36ef239f84dd3af1f9d9f349ed5ca66f19..ead75c932890e971c5d78bd63dbdcce73156eb91 100644 (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,
index 03d8c62df06ec9d9fe95f90d1427583333ab1381..f5024abfbed6172e21d69b30a19248e9fb228c3f 100644 (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)
+    }
+  }
+}
index 82a3bfde7e3d9251f6d14cf0c2cd1320e5fc0464..64d8f0a3d77cb635aab93d24de3b5bb715c917af 100644 (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)
index 74bb387ef6f3ab81bb20c284f3e5b5c26d35d0e8..56315ecad3f017a682106ac6196b5b759bed5127 100644 (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>