Skip to content

Commit f1f676a

Browse files
committed
feat: add hook to transform h's arguments
1 parent a1da9c2 commit f1f676a

File tree

2 files changed

+94
-17
lines changed

2 files changed

+94
-17
lines changed

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

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

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)