diff --git a/packages/runtime-vapor/__tests__/component.spec.ts b/packages/runtime-vapor/__tests__/component.spec.ts index 5fdff8eafe4..e876d696186 100644 --- a/packages/runtime-vapor/__tests__/component.spec.ts +++ b/packages/runtime-vapor/__tests__/component.spec.ts @@ -306,25 +306,29 @@ describe('component', () => { __DEV__ = true }) - it('warn if functional vapor component not return a block', () => { - define(() => { - return () => {} + it('functional vapor component return a object', () => { + const { host } = define(() => { + return {} }).render() - expect( - 'Functional vapor component must return a block directly', - ).toHaveBeenWarned() + expect(host.textContent).toBe(`[object Object]`) }) - it('warn if setup return a function and no render function', () => { - define({ + it('functional vapor component return a function', () => { + const { host } = define(() => { + return () => ({}) + }).render() + + expect(host.textContent).toBe(`() => ({})`) + }) + + it('setup return a function and no render function', () => { + const { host } = define({ setup() { return () => [] }, }).render() - expect( - 'Vapor component setup() returned non-block value, and has no render function', - ).toHaveBeenWarned() + expect(host.textContent).toBe(`() => []`) }) }) diff --git a/packages/runtime-vapor/__tests__/dom/node.spec.ts b/packages/runtime-vapor/__tests__/dom/node.spec.ts new file mode 100644 index 00000000000..89995406d11 --- /dev/null +++ b/packages/runtime-vapor/__tests__/dom/node.spec.ts @@ -0,0 +1,25 @@ +import { createTextNode, normalizeNode } from '../../src/dom/node' +import { VaporFragment } from '../../src' + +describe('dom node', () => { + test('normalizeNode', () => { + // null / undefined -> Comment + expect(normalizeNode(null)).toBeInstanceOf(Comment) + expect(normalizeNode(undefined)).toBeInstanceOf(Comment) + + // boolean -> Comment + expect(normalizeNode(true)).toBeInstanceOf(Comment) + expect(normalizeNode(false)).toBeInstanceOf(Comment) + + // array -> Fragment + expect(normalizeNode(['foo'])).toBeInstanceOf(VaporFragment) + + // VNode -> VNode + const vnode = createTextNode('div') + expect(normalizeNode(vnode)).toBe(vnode) + + // primitive types + expect(normalizeNode('foo')).toMatchObject(createTextNode('foo')) + expect(normalizeNode(1)).toMatchObject(createTextNode('1')) + }) +}) diff --git a/packages/runtime-vapor/__tests__/errorHandling.spec.ts b/packages/runtime-vapor/__tests__/errorHandling.spec.ts index 87a79614d41..a8e55482a54 100644 --- a/packages/runtime-vapor/__tests__/errorHandling.spec.ts +++ b/packages/runtime-vapor/__tests__/errorHandling.spec.ts @@ -182,7 +182,6 @@ describe('error handling', () => { define(Comp).render() expect(fn).toHaveBeenCalledWith(err, 'setup function') - expect(`returned non-block value`).toHaveBeenWarned() }) test('in render function', () => { diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 548babebf8b..97677fbf6ce 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -23,9 +23,8 @@ import { simpleSetCurrentInstance, startMeasure, unregisterHMR, - warn, } from '@vue/runtime-dom' -import { type Block, insert, isBlock, remove } from './block' +import { type Block, insert, remove } from './block' import { type ShallowRef, markRaw, @@ -60,6 +59,7 @@ import { import { hmrReload, hmrRerender } from './hmr' import { isHydrating, locateHydrationNode } from './dom/hydration' import { insertionAnchor, insertionParent } from './insertionState' +import { normalizeNode } from './dom/node' export { currentInstance } from '@vue/runtime-dom' @@ -204,18 +204,12 @@ export function createComponent( ? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [ instance.props, instance, - ]) || EMPTY_OBJ - : EMPTY_OBJ - - if (__DEV__ && !isBlock(setupResult)) { - if (isFunction(component)) { - warn(`Functional vapor component must return a block directly.`) - instance.block = [] - } else if (!component.render) { - warn( - `Vapor component setup() returned non-block value, and has no render function.`, - ) - instance.block = [] + ]) || [] + : [] + + if (__DEV__) { + if (isFunction(component) || !component.render) { + instance.block = normalizeNode(setupResult) } else { instance.devtoolsRawSetupState = setupResult // TODO make the proxy warn non-existent property access during dev @@ -241,7 +235,7 @@ export function createComponent( ) } else { // in prod result can only be block - instance.block = setupResult as Block + instance.block = normalizeNode(setupResult) } } diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index 83bc32c57f0..608aba627df 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,3 +1,6 @@ +import { type Block, VaporFragment, isBlock } from '../block' +import { isArray } from '@vue/shared' + /*! #__NO_SIDE_EFFECTS__ */ export function createTextNode(value = ''): Text { return document.createTextNode(value) @@ -27,3 +30,24 @@ export function nthChild(node: Node, i: number): Node { export function next(node: Node): Node { return node.nextSibling! } + +type NodeChildAtom = Node | string | number | boolean | null | undefined | void + +export type NodeArrayChildren = Array + +export type NodeChild = NodeChildAtom | NodeArrayChildren + +export function normalizeNode(node: NodeChild): Block { + if (node == null || typeof node === 'boolean') { + // empty placeholder + return createComment('') + } else if (isArray(node) && node.length) { + // fragment + return new VaporFragment(node.map(normalizeNode)) + } else if (isBlock(node)) { + return node + } else { + // strings and numbers + return createTextNode(String(node)) + } +}