From 455a1534c9bbae8c5b51d6556ed55cf6c7a95cc7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 23 Mar 2020 16:54:28 -0400 Subject: [PATCH] refactor: transformHArgs -> transformVNodeArgs --- packages/runtime-core/__tests__/h.spec.ts | 64 +------------------ packages/runtime-core/__tests__/vnode.spec.ts | 54 +++++++++++++++- packages/runtime-core/src/h.ts | 52 +++++---------- packages/runtime-core/src/index.ts | 3 + packages/runtime-core/src/vnode.ts | 27 +++++++- 5 files changed, 99 insertions(+), 101 deletions(-) diff --git a/packages/runtime-core/__tests__/h.spec.ts b/packages/runtime-core/__tests__/h.spec.ts index 4358043207f..b55c9f4820b 100644 --- a/packages/runtime-core/__tests__/h.spec.ts +++ b/packages/runtime-core/__tests__/h.spec.ts @@ -1,11 +1,9 @@ -import { h, transformHArgs, resetTransformHArgs } from '../src/h' +import { h } 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. -const testH = () => { +describe('renderer: h', () => { test('type only', () => { expect(h('div')).toMatchObject(createVNode('div')) }) @@ -59,62 +57,4 @@ const testH = () => { }) ) }) -} - -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] //

{{ name }}

- }) - - 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('

Root Component

') - }) }) diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index 7c11c2ab9c7..b89934566e8 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -7,10 +7,13 @@ import { Text, cloneVNode, mergeProps, - normalizeVNode + normalizeVNode, + transformVNodeArgs } from '../src/vnode' import { Data } from '../src/component' import { ShapeFlags, PatchFlags } from '@vue/shared' +import { h } from '../src' +import { createApp, nodeOps, serializeInner } from '@vue/runtime-test' describe('vnode', () => { test('create with just tag', () => { @@ -335,4 +338,53 @@ describe('vnode', () => { expect(vnode.dynamicChildren).toStrictEqual([vnode1]) }) }) + + describe('transformVNodeArgs', () => { + afterEach(() => { + // reset + transformVNodeArgs() + }) + + test('no-op pass through', () => { + transformVNodeArgs(args => args) + const vnode = createVNode('div', { id: 'foo' }, 'hello') + expect(vnode).toMatchObject({ + type: 'div', + props: { id: 'foo' }, + children: 'hello', + shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN + }) + }) + + test('direct override', () => { + transformVNodeArgs(() => ['div', { id: 'foo' }, 'hello']) + const vnode = createVNode('p') + expect(vnode).toMatchObject({ + type: 'div', + props: { id: 'foo' }, + children: 'hello', + shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN + }) + }) + + test('receive component instance as 2nd arg', () => { + transformVNodeArgs((args, instance) => { + if (instance) { + return ['h1', null, instance.type.name] + } else { + return args + } + }) + const App = { + // this will be the name of the component in the h1 + name: 'Root Component', + render() { + return h('p') // this will be overwritten by the transform + } + } + const root = nodeOps.createElement('div') + createApp(App).mount(root) + expect(serializeInner(root)).toBe('

Root Component

') + }) + }) }) diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index 0e53d414be6..e6f07fb1f1f 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -18,7 +18,6 @@ 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. @@ -78,52 +77,52 @@ interface Constructor

{ // manually written render functions. // element -function _h(type: string, children?: RawChildren): VNode -function _h( +export function h(type: string, children?: RawChildren): VNode +export function h( type: string, props?: RawProps | null, children?: RawChildren ): VNode // fragment -function _h(type: typeof Fragment, children?: VNodeArrayChildren): VNode -function _h( +export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode +export function h( type: typeof Fragment, props?: RawProps | null, children?: VNodeArrayChildren ): VNode // portal (target prop is required) -function _h( +export function h( type: typeof Portal, props: RawProps & PortalProps, children: RawChildren ): VNode // suspense -function _h(type: typeof Suspense, children?: RawChildren): VNode -function _h( +export function h(type: typeof Suspense, children?: RawChildren): VNode +export function h( type: typeof Suspense, props?: (RawProps & SuspenseProps) | null, children?: RawChildren | RawSlots ): VNode // functional component -function _h(type: FunctionalComponent, children?: RawChildren): VNode -function _h

( +export function h(type: FunctionalComponent, children?: RawChildren): VNode +export function h

( type: FunctionalComponent

, props?: (RawProps & P) | ({} extends P ? null : never), children?: RawChildren | RawSlots ): VNode // stateful component -function _h(type: ComponentOptions, children?: RawChildren): VNode -function _h( +export function h(type: ComponentOptions, children?: RawChildren): VNode +export function h( type: ComponentOptionsWithoutProps | ComponentOptionsWithArrayProps, props?: RawProps | null, children?: RawChildren | RawSlots ): VNode -function _h( +export function h( type: ComponentOptionsWithObjectProps, props?: | (RawProps & ExtractPropTypes) @@ -132,15 +131,15 @@ function _h( ): VNode // fake constructor type returned by `defineComponent` or class component -function _h(type: Constructor, children?: RawChildren): VNode -function _h

( +export function h(type: Constructor, children?: RawChildren): VNode +export function h

( type: Constructor

, props?: (RawProps & P) | ({} extends P ? null : never), children?: RawChildren | RawSlots ): VNode // Actual implementation -function _h(type: any, propsOrChildren?: any, children?: any): VNode { +export function h(type: any, propsOrChildren?: any, children?: any): VNode { if (arguments.length === 2) { if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { // single vnode without props @@ -160,24 +159,3 @@ 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)) -} - -export function transformHArgs(transformer: Function): void { - argsTransformer = transformer -} - -export function resetTransformHArgs(): void { - argsTransformer = undefined -} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 9ca4ecd1f32..d6be3c56645 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -109,6 +109,9 @@ export { toDisplayString, camelize } from '@vue/shared' // For integration with runtime compiler export { registerRuntimeCompiler } from './component' +// For test-utils +export { transformVNodeArgs } from './vnode' + // SSR ------------------------------------------------------------------------- import { createComponentInstance, setupComponent } from './component' diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 78599e63c44..709781867d9 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -211,7 +211,32 @@ export function isSameVNodeType(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key } -export function createVNode( +let vnodeArgsTransformer: + | (( + args: Parameters, + instance: ComponentInternalInstance | null + ) => Parameters) + | undefined + +// Internal API for registering an arguments transform for createVNode +// used for creating stubs in the test-utils +export function transformVNodeArgs(transformer?: typeof vnodeArgsTransformer) { + vnodeArgsTransformer = transformer +} + +const createVNodeWithArgsTransform = ( + ...args: Parameters +): VNode => { + return _createVNode( + ...(vnodeArgsTransformer + ? vnodeArgsTransformer(args, currentRenderingInstance) + : args) + ) +} + +export const createVNode = __DEV__ ? createVNodeWithArgsTransform : _createVNode + +function _createVNode( type: VNodeTypes | ClassComponent, props: (Data & VNodeProps) | null = null, children: unknown = null,