]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: add hook for transforming h's arguments (#851)
authorJessica Sachs <jess@jessicasachs.io>
Thu, 19 Mar 2020 14:54:03 +0000 (10:54 -0400)
committerGitHub <noreply@github.com>
Thu, 19 Mar 2020 14:54:03 +0000 (10:54 -0400)
Note: this is for internal use (e.g. `@vue/test-utils`) only

packages/runtime-core/__tests__/h.spec.ts
packages/runtime-core/src/h.ts

index b55c9f4820b0e9beb024124ad6b8f5aa0d3085b4..4358043207ff318c56bc9ca7d1a0bfc15b7a9674 100644 (file)
@@ -1,9 +1,11 @@
-import { h } from '../src/h'
+import { h, transformHArgs, resetTransformHArgs } from '../src/h'
 import { createVNode } from '../src/vnode'
+import { ComponentInternalInstance } from '@vue/runtime-core'
+import { createApp } from '@vue/runtime-dom'
 
 // Since h is a thin layer on top of createVNode, we are only testing its
 // own logic here. Details of vnode creation is tested in vnode.spec.ts.
-describe('renderer: h', () => {
+const testH = () => {
   test('type only', () => {
     expect(h('div')).toMatchObject(createVNode('div'))
   })
@@ -57,4 +59,62 @@ describe('renderer: h', () => {
       })
     )
   })
+}
+
+describe('renderer: h', testH)
+
+describe('renderer: transformHArgs', () => {
+  describe('no-op pass-through', () => {
+    beforeAll(() => {
+      transformHArgs((hArgs: unknown[]) => hArgs)
+    })
+    afterAll(resetTransformHArgs)
+    testH()
+  })
+
+  describe('args is used directly, without merging', () => {
+    beforeAll(() => {
+      transformHArgs(() => ['h1', 'Hello World'])
+    })
+    afterAll(resetTransformHArgs)
+    test('nodes become an h1 with text inside', () => {
+      expect(h('div')).toMatchObject(createVNode('h1', null, 'Hello World'))
+    })
+
+    test('resetting transformHArgs turns things back to normal', () => {
+      expect(h('div')).toMatchObject(createVNode('h1', null, 'Hello World'))
+      resetTransformHArgs()
+      expect(h('div')).toMatchObject(createVNode('div'))
+    })
+  })
+
+  test('receives component instance as the 2nd arg', () => {
+    transformHArgs((_: unknown[], instance: ComponentInternalInstance) => {
+      return ['h1', instance.type.name] // <h1>{{ name }}</h1>
+    })
+
+    const vm = createApp({
+      // this will be the name of the component in the h1
+      name: 'Root Component',
+      render() {
+        return h({
+          // this code will never execute,
+          // because it is overridden by the transformHArgs method
+          render() {
+            return h('h2', 'Stub Text')
+          }
+        })
+      }
+    })
+
+    // we need to mount everything so that the instance passed to
+    // transformHArgs isn't null
+    vm.mount('body')
+
+    expect(document.body.outerHTML).toContain('<h1>Root Component</h1>')
+  })
 })
index e6f07fb1f1f861ef979a47c3ab4a9d5497c5348c..0e53d414be665e5e6ef4ce395cb24c60606c96bb 100644 (file)
@@ -18,6 +18,7 @@ import {
   ComponentOptions
 } from './apiOptions'
 import { ExtractPropTypes } from './componentProps'
+import { currentRenderingInstance } from './componentRenderUtils'
 
 // `h` is a more user-friendly version of `createVNode` that allows omitting the
 // props when possible. It is intended for manually written render functions.
@@ -77,52 +78,52 @@ interface Constructor<P = any> {
 // manually written render functions.
 
 // element
-export function h(type: string, children?: RawChildren): VNode
-export function h(
+function _h(type: string, children?: RawChildren): VNode
+function _h(
   type: string,
   props?: RawProps | null,
   children?: RawChildren
 ): VNode
 
 // fragment
-export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
-export function h(
+function _h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
+function _h(
   type: typeof Fragment,
   props?: RawProps | null,
   children?: VNodeArrayChildren
 ): VNode
 
 // portal (target prop is required)
-export function h(
+function _h(
   type: typeof Portal,
   props: RawProps & PortalProps,
   children: RawChildren
 ): VNode
 
 // suspense
-export function h(type: typeof Suspense, children?: RawChildren): VNode
-export function h(
+function _h(type: typeof Suspense, children?: RawChildren): VNode
+function _h(
   type: typeof Suspense,
   props?: (RawProps & SuspenseProps) | null,
   children?: RawChildren | RawSlots
 ): VNode
 
 // functional component
-export function h(type: FunctionalComponent, children?: RawChildren): VNode
-export function h<P>(
+function _h(type: FunctionalComponent, children?: RawChildren): VNode
+function _h<P>(
   type: FunctionalComponent<P>,
   props?: (RawProps & P) | ({} extends P ? null : never),
   children?: RawChildren | RawSlots
 ): VNode
 
 // stateful component
-export function h(type: ComponentOptions, children?: RawChildren): VNode
-export function h(
+function _h(type: ComponentOptions, children?: RawChildren): VNode
+function _h(
   type: ComponentOptionsWithoutProps | ComponentOptionsWithArrayProps,
   props?: RawProps | null,
   children?: RawChildren | RawSlots
 ): VNode
-export function h<O>(
+function _h<O>(
   type: ComponentOptionsWithObjectProps<O>,
   props?:
     | (RawProps & ExtractPropTypes<O>)
@@ -131,15 +132,15 @@ export function h<O>(
 ): VNode
 
 // fake constructor type returned by `defineComponent` or class component
-export function h(type: Constructor, children?: RawChildren): VNode
-export function h<P>(
+function _h(type: Constructor, children?: RawChildren): VNode
+function _h<P>(
   type: Constructor<P>,
   props?: (RawProps & P) | ({} extends P ? null : never),
   children?: RawChildren | RawSlots
 ): VNode
 
 // Actual implementation
-export function h(type: any, propsOrChildren?: any, children?: any): VNode {
+function _h(type: any, propsOrChildren?: any, children?: any): VNode {
   if (arguments.length === 2) {
     if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
       // single vnode without props
@@ -159,3 +160,24 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode {
     return createVNode(type, propsOrChildren, children)
   }
 }
+
+export const h: typeof _h = __DEV__ ? (applyTransformedH as typeof _h) : _h
+
+let argsTransformer: Function | undefined
+
+// This is used to hook into the h function and transform its arguments
+// Useful for re-implementing behavior that was previously done with createElement in Vue 2
+function applyTransformedH(...args: unknown[]): VNode {
+  if (argsTransformer) {
+    args = argsTransformer(args, currentRenderingInstance)
+  }
+  return _h(...(args as Parameters<typeof _h>))
+}
+
+export function transformHArgs(transformer: Function): void {
+  argsTransformer = transformer
+}
+
+export function resetTransformHArgs(): void {
+  argsTransformer = undefined
+}