feat(runtime-vapor): render component

This commit is contained in:
三咲智子 Kevin Deng 2023-12-06 14:59:11 +08:00
parent a9f2bfcdba
commit 9e9703d7b6
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
5 changed files with 152 additions and 130 deletions

View File

@ -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++,

View File

@ -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<number, [ChildNode, Children]>
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))
}

View File

@ -42,3 +42,4 @@ export * from './render'
export * from './template'
export * from './scheduler'
export * from './directives'
export * from './dom'

View File

@ -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<number, [ChildNode, Children]>
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))
}

View File

@ -3,9 +3,4 @@ import { render } from 'vue/vapor'
const modules = import.meta.glob<any>('./*.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'))