Skip to content

Commit b7d1e0f

Browse files
authored
feat: add hook for transforming h's arguments (#851)
Note: this is for internal use (e.g. `@vue/test-utils`) only
1 parent d4aaa21 commit b7d1e0f

File tree

2 files changed

+99
-17
lines changed

2 files changed

+99
-17
lines changed

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

+62-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { h } from '../src/h'
1+
import { h, transformHArgs, resetTransformHArgs } from '../src/h'
22
import { createVNode } from '../src/vnode'
3+
import { ComponentInternalInstance } from '@vue/runtime-core'
4+
import { createApp } from '@vue/runtime-dom'
35

46
// Since h is a thin layer on top of createVNode, we are only testing its
57
// own logic here. Details of vnode creation is tested in vnode.spec.ts.
6-
describe('renderer: h', () => {
8+
const testH = () => {
79
test('type only', () => {
810
expect(h('div')).toMatchObject(createVNode('div'))
911
})
@@ -57,4 +59,62 @@ describe('renderer: h', () => {
5759
})
5860
)
5961
})
62+
}
63+
64+
describe('renderer: h', testH)
65+
66+
describe('renderer: transformHArgs', () => {
67+
describe('no-op pass-through', () => {
68+
beforeAll(() => {
69+
transformHArgs((hArgs: unknown[]) => hArgs)
70+
})
71+
72+
afterAll(resetTransformHArgs)
73+
74+
testH()
75+
})
76+
77+
describe('args is used directly, without merging', () => {
78+
beforeAll(() => {
79+
transformHArgs(() => ['h1', 'Hello World'])
80+
})
81+
82+
afterAll(resetTransformHArgs)
83+
84+
test('nodes become an h1 with text inside', () => {
85+
expect(h('div')).toMatchObject(createVNode('h1', null, 'Hello World'))
86+
})
87+
88+
test('resetting transformHArgs turns things back to normal', () => {
89+
expect(h('div')).toMatchObject(createVNode('h1', null, 'Hello World'))
90+
resetTransformHArgs()
91+
expect(h('div')).toMatchObject(createVNode('div'))
92+
})
93+
})
94+
95+
test('receives component instance as the 2nd arg', () => {
96+
transformHArgs((_: unknown[], instance: ComponentInternalInstance) => {
97+
return ['h1', instance.type.name] // <h1>{{ name }}</h1>
98+
})
99+
100+
const vm = createApp({
101+
// this will be the name of the component in the h1
102+
name: 'Root Component',
103+
render() {
104+
return h({
105+
// this code will never execute,
106+
// because it is overridden by the transformHArgs method
107+
render() {
108+
return h('h2', 'Stub Text')
109+
}
110+
})
111+
}
112+
})
113+
114+
// we need to mount everything so that the instance passed to
115+
// transformHArgs isn't null
116+
vm.mount('body')
117+
118+
expect(document.body.outerHTML).toContain('<h1>Root Component</h1>')
119+
})
60120
})

packages/runtime-core/src/h.ts

+37-15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ComponentOptions
1919
} from './apiOptions'
2020
import { ExtractPropTypes } from './componentProps'
21+
import { currentRenderingInstance } from './componentRenderUtils'
2122

2223
// `h` is a more user-friendly version of `createVNode` that allows omitting the
2324
// props when possible. It is intended for manually written render functions.
@@ -77,52 +78,52 @@ interface Constructor<P = any> {
7778
// manually written render functions.
7879

7980
// element
80-
export function h(type: string, children?: RawChildren): VNode
81-
export function h(
81+
function _h(type: string, children?: RawChildren): VNode
82+
function _h(
8283
type: string,
8384
props?: RawProps | null,
8485
children?: RawChildren
8586
): VNode
8687

8788
// fragment
88-
export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
89-
export function h(
89+
function _h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
90+
function _h(
9091
type: typeof Fragment,
9192
props?: RawProps | null,
9293
children?: VNodeArrayChildren
9394
): VNode
9495

9596
// portal (target prop is required)
96-
export function h(
97+
function _h(
9798
type: typeof Portal,
9899
props: RawProps & PortalProps,
99100
children: RawChildren
100101
): VNode
101102

102103
// suspense
103-
export function h(type: typeof Suspense, children?: RawChildren): VNode
104-
export function h(
104+
function _h(type: typeof Suspense, children?: RawChildren): VNode
105+
function _h(
105106
type: typeof Suspense,
106107
props?: (RawProps & SuspenseProps) | null,
107108
children?: RawChildren | RawSlots
108109
): VNode
109110

110111
// functional component
111-
export function h(type: FunctionalComponent, children?: RawChildren): VNode
112-
export function h<P>(
112+
function _h(type: FunctionalComponent, children?: RawChildren): VNode
113+
function _h<P>(
113114
type: FunctionalComponent<P>,
114115
props?: (RawProps & P) | ({} extends P ? null : never),
115116
children?: RawChildren | RawSlots
116117
): VNode
117118

118119
// stateful component
119-
export function h(type: ComponentOptions, children?: RawChildren): VNode
120-
export function h(
120+
function _h(type: ComponentOptions, children?: RawChildren): VNode
121+
function _h(
121122
type: ComponentOptionsWithoutProps | ComponentOptionsWithArrayProps,
122123
props?: RawProps | null,
123124
children?: RawChildren | RawSlots
124125
): VNode
125-
export function h<O>(
126+
function _h<O>(
126127
type: ComponentOptionsWithObjectProps<O>,
127128
props?:
128129
| (RawProps & ExtractPropTypes<O>)
@@ -131,15 +132,15 @@ export function h<O>(
131132
): VNode
132133

133134
// fake constructor type returned by `defineComponent` or class component
134-
export function h(type: Constructor, children?: RawChildren): VNode
135-
export function h<P>(
135+
function _h(type: Constructor, children?: RawChildren): VNode
136+
function _h<P>(
136137
type: Constructor<P>,
137138
props?: (RawProps & P) | ({} extends P ? null : never),
138139
children?: RawChildren | RawSlots
139140
): VNode
140141

141142
// Actual implementation
142-
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
143+
function _h(type: any, propsOrChildren?: any, children?: any): VNode {
143144
if (arguments.length === 2) {
144145
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
145146
// single vnode without props
@@ -159,3 +160,24 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode {
159160
return createVNode(type, propsOrChildren, children)
160161
}
161162
}
163+
164+
export const h: typeof _h = __DEV__ ? (applyTransformedH as typeof _h) : _h
165+
166+
let argsTransformer: Function | undefined
167+
168+
// This is used to hook into the h function and transform its arguments
169+
// Useful for re-implementing behavior that was previously done with createElement in Vue 2
170+
function applyTransformedH(...args: unknown[]): VNode {
171+
if (argsTransformer) {
172+
args = argsTransformer(args, currentRenderingInstance)
173+
}
174+
return _h(...(args as Parameters<typeof _h>))
175+
}
176+
177+
export function transformHArgs(transformer: Function): void {
178+
argsTransformer = transformer
179+
}
180+
181+
export function resetTransformHArgs(): void {
182+
argsTransformer = undefined
183+
}

0 commit comments

Comments
 (0)