diff --git a/packages/runtime-vapor/__tests__/dom/element.spec.ts b/packages/runtime-vapor/__tests__/block.spec.ts similarity index 75% rename from packages/runtime-vapor/__tests__/dom/element.spec.ts rename to packages/runtime-vapor/__tests__/block.spec.ts index b273131a5..ddd4035f6 100644 --- a/packages/runtime-vapor/__tests__/dom/element.spec.ts +++ b/packages/runtime-vapor/__tests__/block.spec.ts @@ -1,12 +1,11 @@ -import { fragmentKey, normalizeBlock } from '../../src/block' -import { insert, prepend, remove } from '../../src/dom/node' +import { Fragment, insert, normalizeBlock, prepend, remove } from '../src/block' const node1 = document.createTextNode('node1') const node2 = document.createTextNode('node2') const node3 = document.createTextNode('node3') const anchor = document.createTextNode('anchor') -describe('element', () => { +describe('node ops', () => { test('normalizeBlock', () => { expect(normalizeBlock([node1, node2, node3])).toEqual([node1, node2, node3]) expect(normalizeBlock([node1, [node2, [node3]]])).toEqual([ @@ -14,13 +13,14 @@ describe('element', () => { node2, node3, ]) - expect( - normalizeBlock([ - node1, - { nodes: node2, anchor, [fragmentKey]: true }, - [node3], - ]), - ).toEqual([node1, node2, anchor, node3]) + const frag = new Fragment(node2) + frag.anchor = anchor + expect(normalizeBlock([node1, frag, [node3]])).toEqual([ + node1, + node2, + anchor, + node3, + ]) }) test('insert', () => { @@ -39,15 +39,16 @@ describe('element', () => { test('prepend', () => { const container = document.createElement('div') prepend(container, [node1], node2) - prepend(container, { nodes: node3, [fragmentKey]: true }) + prepend(container, new Fragment(node3)) expect(Array.from(container.childNodes)).toEqual([node3, node1, node2]) }) test('remove', () => { const container = document.createElement('div') container.append(node1, node2, node3) + const frag = new Fragment(node3) remove([node1], container) - remove({ nodes: node3, [fragmentKey]: true }, container) + remove(frag, container) expect(Array.from(container.childNodes)).toEqual([node2]) expect(() => remove(anchor, container)).toThrowError( diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index a6f237558..143f46929 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,4 +1,4 @@ -import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef' +import type { NodeRef } from '../../src/dom/templateRef' import { createFor, createIf, diff --git a/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts deleted file mode 100644 index 10b8c6718..000000000 --- a/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - type Component, - type Directive, - createVaporApp, - resolveComponent, - resolveDirective, -} from 'packages/runtime-vapor/src/_old' -import { makeRender } from '../_utils' - -const define = makeRender() - -describe('resolveAssets', () => { - test('should work', () => { - const FooBar = () => [] - const BarBaz = () => undefined - let component1: Component | string - let component2: Component | string - let component3: Component | string - let component4: Component | string - let directive1: Directive - let directive2: Directive - let directive3: Directive - let directive4: Directive - const Root = define({ - render() { - component1 = resolveComponent('FooBar')! - directive1 = resolveDirective('BarBaz')! - // camelize - component2 = resolveComponent('Foo-bar')! - directive2 = resolveDirective('Bar-baz')! - // capitalize - component3 = resolveComponent('fooBar')! - directive3 = resolveDirective('barBaz')! - // camelize and capitalize - component4 = resolveComponent('foo-bar')! - directive4 = resolveDirective('bar-baz')! - return [] - }, - }) - const app = createVaporApp(Root.component) - app.component('FooBar', FooBar) - app.directive('BarBaz', BarBaz) - const root = document.createElement('div') - app.mount(root) - expect(component1!).toBe(FooBar) - expect(component2!).toBe(FooBar) - expect(component3!).toBe(FooBar) - expect(component4!).toBe(FooBar) - expect(directive1!).toBe(BarBaz) - expect(directive2!).toBe(BarBaz) - expect(directive3!).toBe(BarBaz) - expect(directive4!).toBe(BarBaz) - }) - - describe('warning', () => { - test('used outside render() or setup()', () => { - resolveComponent('foo') - expect( - '[Vue warn]: resolveComponent can only be used in render() or setup().', - ).toHaveBeenWarned() - resolveDirective('foo') - expect( - '[Vue warn]: resolveDirective can only be used in render() or setup().', - ).toHaveBeenWarned() - }) - test('not exist', () => { - const Root = define({ - setup() { - resolveComponent('foo') - resolveDirective('bar') - }, - }) - const app = createVaporApp(Root.component) - const root = document.createElement('div') - app.mount(root) - expect('Failed to resolve component: foo').toHaveBeenWarned() - expect('Failed to resolve directive: bar').toHaveBeenWarned() - }) - }) -}) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index a4212714c..01cc30ceb 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -1,6 +1,11 @@ import { isArray } from '@vue/shared' -import { type VaporComponentInstance, isVaporComponent } from './component' -import { createComment, insert, remove } from './dom/node' +import { + type VaporComponentInstance, + isVaporComponent, + mountComponent, + unmountComponent, +} from './component' +import { createComment } from './dom/node' import { EffectScope } from '@vue/reactivity' export type Block = Node | Fragment | VaporComponentInstance | Block[] @@ -84,28 +89,50 @@ export function normalizeBlock(block: Block): Node[] { return nodes } -export function findFirstRootElement( - instance: VaporComponentInstance, -): Element | undefined { - const element = getFirstNode(instance.block) - return element instanceof Element ? element : undefined -} - -export function getFirstNode(block: Block | null): Node | undefined { - if (!block || isVaporComponent(block)) return - if (block instanceof Node) return block - if (isArray(block)) { - if (block.length === 1) { - return getFirstNode(block[0]) - } - } else { - return getFirstNode(block.nodes) - } -} - // TODO optimize export function isValidBlock(block: Block): boolean { return ( normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0 ) } + +export function insert( + block: Block, + parent: ParentNode, + anchor: Node | null | 0 = null, +): void { + if (block instanceof Node) { + parent.insertBefore(block, anchor === 0 ? parent.firstChild : anchor) + } else if (isVaporComponent(block)) { + mountComponent(block, parent, anchor) + } else if (isArray(block)) { + for (let i = 0; i < block.length; i++) { + insert(block[i], parent, anchor) + } + } else { + // fragment + insert(block.nodes, parent, anchor) + if (block.anchor) insert(block.anchor, parent, anchor) + } +} + +export function prepend(parent: ParentNode, ...blocks: Block[]): void { + let i = blocks.length + while (i--) insert(blocks[i], parent, 0) +} + +export function remove(block: Block, parent: ParentNode): void { + if (block instanceof Node) { + parent.removeChild(block) + } else if (isVaporComponent(block)) { + unmountComponent(block, parent) + } else if (isArray(block)) { + for (let i = 0; i < block.length; i++) { + remove(block[i], parent) + } + } else { + // fragment + remove(block.nodes, parent) + if (block.anchor) remove(block.anchor, parent) + } +} diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index a70bbdc9a..688fe29f5 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -19,7 +19,7 @@ import { unregisterHMR, warn, } from '@vue/runtime-dom' -import { type Block, isBlock } from './block' +import { type Block, insert, isBlock, remove } from './block' import { pauseTracking, proxyRefs, resetTracking } from '@vue/reactivity' import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared' import { @@ -41,7 +41,6 @@ import { dynamicSlotsProxyHandlers, getSlot, } from './componentSlots' -import { insert, remove } from './dom/node' import { hmrReload, hmrRerender } from './hmr' export { currentInstance } from '@vue/runtime-dom' diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index e54966d83..5a50cd0aa 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,53 +1,6 @@ import { isArray } from '@vue/shared' import { renderEffect } from '../renderEffect' import { setText } from './prop' -import type { Block } from '../block' -import { - isVaporComponent, - mountComponent, - unmountComponent, -} from '../component' - -export function insert( - block: Block, - parent: ParentNode, - anchor: Node | null | 0 = null, -): void { - if (block instanceof Node) { - parent.insertBefore(block, anchor === 0 ? parent.firstChild : anchor) - } else if (isVaporComponent(block)) { - mountComponent(block, parent, anchor) - } else if (isArray(block)) { - for (let i = 0; i < block.length; i++) { - insert(block[i], parent, anchor) - } - } else { - // fragment - insert(block.nodes, parent, anchor) - if (block.anchor) insert(block.anchor, parent, anchor) - } -} - -export function prepend(parent: ParentNode, ...blocks: Block[]): void { - for (const b of blocks) insert(b, parent, 0) -} - -// TODO invoke unmount recursive -export function remove(block: Block, parent: ParentNode): void { - if (block instanceof Node) { - parent.removeChild(block) - } else if (isVaporComponent(block)) { - unmountComponent(block, parent) - } else if (isArray(block)) { - for (let i = 0; i < block.length; i++) { - remove(block[i], parent) - } - } else { - // fragment - remove(block.nodes, parent) - if (block.anchor) remove(block.anchor, parent) - } -} export function createTextNode(values?: any[] | (() => any[])): Text { // eslint-disable-next-line no-restricted-globals diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index f01b337bd..eb2d93773 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -4,7 +4,7 @@ import { pushWarningContext, simpleSetCurrentInstance, } from '@vue/runtime-core' -import { normalizeBlock } from './block' +import { insert, normalizeBlock, remove } from './block' import { type VaporComponent, type VaporComponentInstance, @@ -13,7 +13,6 @@ import { mountComponent, unmountComponent, } from './component' -import { insert, remove } from './dom/node' export function hmrRerender(instance: VaporComponentInstance): void { const normalized = normalizeBlock(instance.block) diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index ba47805b4..6c22332af 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -3,11 +3,12 @@ export { createVaporApp } from './apiCreateApp' export { defineVaporComponent } from './apiDefineComponent' // compiler-use only +export { insert, prepend, remove } from './block' export { createComponent, createComponentWithFallback } from './component' export { renderEffect } from './renderEffect' export { createSlot } from './componentSlots' export { template, children, next } from './dom/template' -export { insert, prepend, remove, createTextNode } from './dom/node' +export { createTextNode } from './dom/node' export { setStyle } from './dom/style' export { setText,