From 9e9703d7b61d81e9b01580b664a9b1f0422c77ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 6 Dec 2023 14:59:11 +0800 Subject: [PATCH] feat(runtime-vapor): render component --- packages/runtime-vapor/src/component.ts | 15 ++- packages/runtime-vapor/src/dom.ts | 113 ++++++++++++++++++ packages/runtime-vapor/src/index.ts | 1 + packages/runtime-vapor/src/render.ts | 146 ++++-------------------- playground/src/main.ts | 7 +- 5 files changed, 152 insertions(+), 130 deletions(-) create mode 100644 packages/runtime-vapor/src/dom.ts diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index ead75c932..0ea657d24 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1,14 +1,23 @@ import { EffectScope } from '@vue/reactivity' -import { Block, BlockFn } from './render' +import { Block } from './render' import { DirectiveBinding } from './directives' +export type SetupFn = (props: any, ctx: any) => Block | Data +export type FunctionalComponent = SetupFn & { + render(ctx: any): Block +} +export interface ObjectComponent { + setup: SetupFn + render(ctx: any): Block +} + export interface ComponentInternalInstance { uid: number container: ParentNode block: Block | null scope: EffectScope - component: BlockFn + component: FunctionalComponent | ObjectComponent isMounted: boolean /** directives */ @@ -32,7 +41,7 @@ export const unsetCurrentInstance = () => { let uid = 0 export const createComponentInstance = ( - component: BlockFn, + component: ObjectComponent | FunctionalComponent, ): ComponentInternalInstance => { const instance: ComponentInternalInstance = { uid: uid++, diff --git a/packages/runtime-vapor/src/dom.ts b/packages/runtime-vapor/src/dom.ts new file mode 100644 index 000000000..92bc1e252 --- /dev/null +++ b/packages/runtime-vapor/src/dom.ts @@ -0,0 +1,113 @@ +import { + isArray, + normalizeClass, + normalizeStyle, + toDisplayString, +} from '@vue/shared' +import type { Block, ParentBlock } from './render' + +export function insert( + block: Block, + parent: ParentNode, + anchor: Node | null = null, +) { + // if (!isHydrating) { + if (block instanceof Node) { + parent.insertBefore(block, anchor) + } else if (isArray(block)) { + for (const child of block) insert(child, parent, anchor) + } else { + insert(block.nodes, parent, anchor) + parent.insertBefore(block.anchor, anchor) + } + // } +} + +export function prepend(parent: ParentBlock, ...nodes: Node[]) { + if (parent instanceof Node) { + // TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1 + parent.prepend(...nodes) + } else if (isArray(parent)) { + parent.unshift(...nodes) + } +} + +export function append(parent: ParentBlock, ...nodes: Node[]) { + if (parent instanceof Node) { + // TODO use insertBefore for better performance + parent.append(...nodes) + } else if (isArray(parent)) { + parent.push(...nodes) + } +} + +export function remove(block: Block, parent: ParentNode) { + if (block instanceof Node) { + parent.removeChild(block) + } else if (isArray(block)) { + for (const child of block) remove(child, parent) + } else { + remove(block.nodes, parent) + block.anchor && parent.removeChild(block.anchor) + } +} + +export function setText(el: Element, oldVal: any, newVal: any) { + if ((newVal = toDisplayString(newVal)) !== oldVal) { + el.textContent = newVal + } +} + +export function setHtml(el: Element, oldVal: any, newVal: any) { + if (newVal !== oldVal) { + el.innerHTML = newVal + } +} + +export function setClass(el: Element, oldVal: any, newVal: any) { + if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) { + el.className = newVal + } +} + +export function setStyle(el: HTMLElement, oldVal: any, newVal: any) { + if ((newVal = normalizeStyle(newVal)) !== oldVal && (newVal || oldVal)) { + if (typeof newVal === 'string') { + el.style.cssText = newVal + } else { + // TODO + } + } +} + +export function setAttr(el: Element, key: string, oldVal: any, newVal: any) { + if (newVal !== oldVal) { + if (newVal != null) { + el.setAttribute(key, newVal) + } else { + el.removeAttribute(key) + } + } +} + +export function setDynamicProp(el: Element, key: string, val: any) { + if (key === 'class') { + setClass(el, void 0, val) + } else if (key === 'style') { + setStyle(el as HTMLElement, void 0, val) + } else if (key in el) { + ;(el as any)[key] = val + } else { + // TODO special checks + setAttr(el, key, void 0, val) + } +} + +type Children = Record +export function children(n: ChildNode): Children { + return { ...Array.from(n.childNodes).map((n) => [n, children(n)]) } +} + +export function createTextNode(val: unknown): Text { + return document.createTextNode(toDisplayString(val)) +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 5533dfc82..63985f7ec 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -42,3 +42,4 @@ export * from './render' export * from './template' export * from './scheduler' export * from './directives' +export * from './dom' diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 64d8f0a3d..74e88eace 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -1,24 +1,22 @@ +import { reactive } from '@vue/reactivity' import { - isArray, - normalizeClass, - normalizeStyle, - toDisplayString, -} from '@vue/shared' -import { - ComponentInternalInstance, + type ComponentInternalInstance, + type FunctionalComponent, + type ObjectComponent, createComponentInstance, setCurrentInstance, unsetCurrentInstance, } from './component' import { invokeDirectiveHook } from './directives' +import { insert, remove } from './dom' export type Block = Node | Fragment | Block[] export type ParentBlock = ParentNode | Node[] export type Fragment = { nodes: Block; anchor: Node } -export type BlockFn = (props?: any) => Block +export type BlockFn = (props: any, ctx: any) => Block export function render( - comp: BlockFn, + comp: ObjectComponent | FunctionalComponent, container: string | ParentNode, ): ComponentInternalInstance { const instance = createComponentInstance(comp) @@ -33,16 +31,28 @@ export function normalizeContainer(container: string | ParentNode): ParentNode { : container } -export const mountComponent = ( +export function mountComponent( instance: ComponentInternalInstance, container: ParentNode, -) => { +) { instance.container = container setCurrentInstance(instance) - const block = instance.scope.run( - () => (instance.block = instance.component()), - )! + const block = instance.scope.run(() => { + const { component } = instance + const props = {} + const ctx = { expose: () => {} } + + const setupFn = + typeof component === 'function' ? component : component.setup + + const state = setupFn(props, ctx) + if (state && '__isScriptSetup' in state) { + return (instance.block = component.render(reactive(state))) + } else { + return (instance.block = state as Block) + } + })! invokeDirectiveHook(instance, 'beforeMount') insert(block, instance.container) @@ -54,7 +64,7 @@ export const mountComponent = ( // m && invoke(m) } -export const unmountComponent = (instance: ComponentInternalInstance) => { +export function unmountComponent(instance: ComponentInternalInstance) { const { container, block, scope } = instance invokeDirectiveHook(instance, 'beforeUnmount') @@ -68,109 +78,3 @@ export const unmountComponent = (instance: ComponentInternalInstance) => { // const { um } = instance // um && invoke(um) } - -export function insert( - block: Block, - parent: ParentNode, - anchor: Node | null = null, -) { - // if (!isHydrating) { - if (block instanceof Node) { - parent.insertBefore(block, anchor) - } else if (isArray(block)) { - for (const child of block) insert(child, parent, anchor) - } else { - insert(block.nodes, parent, anchor) - parent.insertBefore(block.anchor, anchor) - } - // } -} - -export function prepend(parent: ParentBlock, ...nodes: Node[]) { - if (parent instanceof Node) { - // TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1 - parent.prepend(...nodes) - } else if (isArray(parent)) { - parent.unshift(...nodes) - } -} - -export function append(parent: ParentBlock, ...nodes: Node[]) { - if (parent instanceof Node) { - // TODO use insertBefore for better performance - parent.append(...nodes) - } else if (isArray(parent)) { - parent.push(...nodes) - } -} - -export function remove(block: Block, parent: ParentNode) { - if (block instanceof Node) { - parent.removeChild(block) - } else if (isArray(block)) { - for (const child of block) remove(child, parent) - } else { - remove(block.nodes, parent) - block.anchor && parent.removeChild(block.anchor) - } -} - -export function setText(el: Element, oldVal: any, newVal: any) { - if ((newVal = toDisplayString(newVal)) !== oldVal) { - el.textContent = newVal - } -} - -export function setHtml(el: Element, oldVal: any, newVal: any) { - if (newVal !== oldVal) { - el.innerHTML = newVal - } -} - -export function setClass(el: Element, oldVal: any, newVal: any) { - if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) { - el.className = newVal - } -} - -export function setStyle(el: HTMLElement, oldVal: any, newVal: any) { - if ((newVal = normalizeStyle(newVal)) !== oldVal && (newVal || oldVal)) { - if (typeof newVal === 'string') { - el.style.cssText = newVal - } else { - // TODO - } - } -} - -export function setAttr(el: Element, key: string, oldVal: any, newVal: any) { - if (newVal !== oldVal) { - if (newVal != null) { - el.setAttribute(key, newVal) - } else { - el.removeAttribute(key) - } - } -} - -export function setDynamicProp(el: Element, key: string, val: any) { - if (key === 'class') { - setClass(el, void 0, val) - } else if (key === 'style') { - setStyle(el as HTMLElement, void 0, val) - } else if (key in el) { - ;(el as any)[key] = val - } else { - // TODO special checks - setAttr(el, key, void 0, val) - } -} - -type Children = Record -export function children(n: ChildNode): Children { - return { ...Array.from(n.childNodes).map((n) => [n, children(n)]) } -} - -export function createTextNode(val: unknown): Text { - return document.createTextNode(toDisplayString(val)) -} diff --git a/playground/src/main.ts b/playground/src/main.ts index 06b2c60ad..43565bc94 100644 --- a/playground/src/main.ts +++ b/playground/src/main.ts @@ -3,9 +3,4 @@ import { render } from 'vue/vapor' const modules = import.meta.glob('./*.vue') const mod = (modules['.' + location.pathname] || modules['./App.vue'])() -mod.then(({ default: m }) => { - render(() => { - const returned = m.setup?.({}, { expose() {} }) - return m.render(returned) - }, '#app') -}) +mod.then(({ default: mod }) => render(mod, '#app'))