mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): render component
This commit is contained in:
parent
a9f2bfcdba
commit
9e9703d7b6
|
@ -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++,
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -42,3 +42,4 @@ export * from './render'
|
|||
export * from './template'
|
||||
export * from './scheduler'
|
||||
export * from './directives'
|
||||
export * from './dom'
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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'))
|
||||
|
|
Loading…
Reference in New Issue