@v4fire/client
Version:
V4Fire client core library
222 lines (177 loc) • 5.42 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
/**
* [[include:core/component/flyweight/README.md]]
* @packageDocumentation
*/
import { deprecate } from 'core/functools/deprecation';
import Async from 'core/async';
import { components } from 'core/component/const';
import type { CreateElement, VNode } from 'core/component/engines';
import { forkMeta } from 'core/component/meta';
import { initProps } from 'core/component/prop';
import { initFields } from 'core/component/field';
import { destroyComponent, FlyweightVNode } from 'core/component/functional';
import { attachMethodsFromMeta } from 'core/component/method';
import { implementEventAPI } from 'core/component/event';
import { attachAccessorsFromMeta } from 'core/component/accessor';
import { getNormalParent } from 'core/component/traverse';
import { getComponentDataFromVNode } from 'core/component/vnode';
import { execRenderObject } from 'core/component/render';
import type { ComponentInterface } from 'core/component/interface';
/**
* Takes a vnode and, if it has the composite attribute, returns a new vnode that contains a flyweight component,
* otherwise returns the original vnode
*
* @param vnode
* @param createElement - function to create VNode element
* @param parentComponent - parent component instance
*/
export function parseVNodeAsFlyweight(
vnode: VNode,
createElement: CreateElement,
parentComponent: ComponentInterface
): VNode | FlyweightVNode {
const
renderEngine = parentComponent.$renderEngine,
compositeAttr = vnode.data?.attrs?.['v4-flyweight-component'];
if (!renderEngine.supports.composite || compositeAttr == null) {
return vnode;
}
vnode.tag = 'span';
let
meta = components.get(compositeAttr);
if (!meta) {
return vnode;
}
meta = forkMeta(meta);
delete vnode.data!.attrs!['v4-flyweight-component'];
if (parentComponent.isFlyweight) {
parentComponent = parentComponent.$normalParent!;
}
const
componentData = getComponentDataFromVNode(compositeAttr, vnode),
componentProto = meta.constructor.prototype,
componentTpl = TPLS[compositeAttr] ?? componentProto.render;
// To create a flyweight component we need to create the "fake" context.
// The context is based on the specified parent context by using `Object.create`.
// Also, we need to shim some component hooks.
const
fakeCtx = Object.create(parentComponent);
fakeCtx.isFlyweight = true;
fakeCtx.hook = 'beforeDataCreate';
fakeCtx.meta = meta;
fakeCtx.componentName = meta.componentName;
fakeCtx.instance = meta.instance;
fakeCtx.unsafe = fakeCtx;
fakeCtx.$async = new Async(fakeCtx);
fakeCtx.$renderEngine = renderEngine;
fakeCtx.$createElement = createElement.bind(fakeCtx);
fakeCtx.$destroy = () => destroyComponent(fakeCtx);
fakeCtx._self = fakeCtx;
fakeCtx._renderProxy = fakeCtx;
fakeCtx._c = fakeCtx.$createElement;
fakeCtx._staticTrees = [];
Object.defineProperty(fakeCtx, '$el', {
configurable: true,
enumerable: true,
value: undefined
});
Object.defineProperty(fakeCtx, '$refs', {
configurable: true,
enumerable: true,
value: {}
});
Object.defineProperty(fakeCtx, '$refHandlers', {
configurable: true,
enumerable: true,
value: {}
});
Object.defineProperty(fakeCtx, '$props', {
configurable: true,
enumerable: true,
value: {}
});
Object.defineProperty(fakeCtx, '$attrs', {
configurable: true,
enumerable: true,
value: componentData.attrs
});
Object.defineProperty(fakeCtx, '$systemFields', {
configurable: true,
enumerable: true,
writable: true,
value: fakeCtx
});
Object.defineProperty(fakeCtx, '$data', {
configurable: true,
enumerable: true,
get(): typeof fakeCtx {
deprecate({name: '$$data', type: 'property', renamedTo: '$fields'});
return fakeCtx;
}
});
Object.defineProperty(fakeCtx, '$fields', {
configurable: true,
enumerable: true,
writable: true,
value: fakeCtx
});
Object.defineProperty(fakeCtx, '$slots', {
configurable: true,
enumerable: true,
value: componentData.slots
});
Object.defineProperty(fakeCtx, '$scopedSlots', {
configurable: true,
enumerable: true,
value: componentData.scopedSlots
});
Object.defineProperty(fakeCtx, '$parent', {
configurable: true,
enumerable: true,
value: parentComponent
});
Object.defineProperty(fakeCtx, '$normalParent', {
configurable: true,
enumerable: true,
value: getNormalParent(fakeCtx)
});
Object.defineProperty(fakeCtx, '$children', {
configurable: true,
enumerable: true,
value: vnode.children
});
initProps(fakeCtx, {
from: componentData.props,
store: fakeCtx,
saveToStore: true
});
attachMethodsFromMeta(fakeCtx);
implementEventAPI(fakeCtx);
attachAccessorsFromMeta(fakeCtx);
initFields(meta.systemFields, fakeCtx, fakeCtx);
initFields(meta.fields, fakeCtx, fakeCtx);
fakeCtx.onCreatedHook();
const newVNode = <FlyweightVNode>execRenderObject(
componentTpl.index(),
fakeCtx
);
newVNode.fakeInstance = fakeCtx;
newVNode.data = newVNode.data ?? {};
renderEngine.patchVNode(newVNode, fakeCtx, {
// @ts-ignore (unsafe cast)
data: componentData
});
// Attach component event listeners
for (let o = componentData.on, keys = Object.keys(o), i = 0; i < keys.length; i++) {
const key = keys[i];
fakeCtx.$on(key, o[key]);
}
return newVNode;
}