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 { EffectScope } from '@vue/reactivity'
|
||||||
import { Block, BlockFn } from './render'
|
import { Block } from './render'
|
||||||
import { DirectiveBinding } from './directives'
|
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 {
|
export interface ComponentInternalInstance {
|
||||||
uid: number
|
uid: number
|
||||||
container: ParentNode
|
container: ParentNode
|
||||||
block: Block | null
|
block: Block | null
|
||||||
scope: EffectScope
|
scope: EffectScope
|
||||||
|
|
||||||
component: BlockFn
|
component: FunctionalComponent | ObjectComponent
|
||||||
isMounted: boolean
|
isMounted: boolean
|
||||||
|
|
||||||
/** directives */
|
/** directives */
|
||||||
|
@ -32,7 +41,7 @@ export const unsetCurrentInstance = () => {
|
||||||
|
|
||||||
let uid = 0
|
let uid = 0
|
||||||
export const createComponentInstance = (
|
export const createComponentInstance = (
|
||||||
component: BlockFn,
|
component: ObjectComponent | FunctionalComponent,
|
||||||
): ComponentInternalInstance => {
|
): ComponentInternalInstance => {
|
||||||
const instance: ComponentInternalInstance = {
|
const instance: ComponentInternalInstance = {
|
||||||
uid: uid++,
|
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 './template'
|
||||||
export * from './scheduler'
|
export * from './scheduler'
|
||||||
export * from './directives'
|
export * from './directives'
|
||||||
|
export * from './dom'
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
|
import { reactive } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
isArray,
|
type ComponentInternalInstance,
|
||||||
normalizeClass,
|
type FunctionalComponent,
|
||||||
normalizeStyle,
|
type ObjectComponent,
|
||||||
toDisplayString,
|
|
||||||
} from '@vue/shared'
|
|
||||||
import {
|
|
||||||
ComponentInternalInstance,
|
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setCurrentInstance,
|
setCurrentInstance,
|
||||||
unsetCurrentInstance,
|
unsetCurrentInstance,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
|
import { insert, remove } from './dom'
|
||||||
|
|
||||||
export type Block = Node | Fragment | Block[]
|
export type Block = Node | Fragment | Block[]
|
||||||
export type ParentBlock = ParentNode | Node[]
|
export type ParentBlock = ParentNode | Node[]
|
||||||
export type Fragment = { nodes: Block; anchor: 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(
|
export function render(
|
||||||
comp: BlockFn,
|
comp: ObjectComponent | FunctionalComponent,
|
||||||
container: string | ParentNode,
|
container: string | ParentNode,
|
||||||
): ComponentInternalInstance {
|
): ComponentInternalInstance {
|
||||||
const instance = createComponentInstance(comp)
|
const instance = createComponentInstance(comp)
|
||||||
|
@ -33,16 +31,28 @@ export function normalizeContainer(container: string | ParentNode): ParentNode {
|
||||||
: container
|
: container
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mountComponent = (
|
export function mountComponent(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
container: ParentNode,
|
container: ParentNode,
|
||||||
) => {
|
) {
|
||||||
instance.container = container
|
instance.container = container
|
||||||
|
|
||||||
setCurrentInstance(instance)
|
setCurrentInstance(instance)
|
||||||
const block = instance.scope.run(
|
const block = instance.scope.run(() => {
|
||||||
() => (instance.block = instance.component()),
|
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')
|
invokeDirectiveHook(instance, 'beforeMount')
|
||||||
insert(block, instance.container)
|
insert(block, instance.container)
|
||||||
|
@ -54,7 +64,7 @@ export const mountComponent = (
|
||||||
// m && invoke(m)
|
// m && invoke(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unmountComponent = (instance: ComponentInternalInstance) => {
|
export function unmountComponent(instance: ComponentInternalInstance) {
|
||||||
const { container, block, scope } = instance
|
const { container, block, scope } = instance
|
||||||
|
|
||||||
invokeDirectiveHook(instance, 'beforeUnmount')
|
invokeDirectiveHook(instance, 'beforeUnmount')
|
||||||
|
@ -68,109 +78,3 @@ export const unmountComponent = (instance: ComponentInternalInstance) => {
|
||||||
// const { um } = instance
|
// const { um } = instance
|
||||||
// um && invoke(um)
|
// 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 modules = import.meta.glob<any>('./*.vue')
|
||||||
const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
|
const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
|
||||||
|
|
||||||
mod.then(({ default: m }) => {
|
mod.then(({ default: mod }) => render(mod, '#app'))
|
||||||
render(() => {
|
|
||||||
const returned = m.setup?.({}, { expose() {} })
|
|
||||||
return m.render(returned)
|
|
||||||
}, '#app')
|
|
||||||
})
|
|
||||||
|
|
Loading…
Reference in New Issue