UNPKG

@v4fire/client

Version:

V4Fire client core library

309 lines (238 loc) • 8.13 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ import symbolGenerator from 'core/symbol'; import * as c from 'core/component/const'; import { attachTemplatesToMeta } from 'core/component/meta'; import { getComponentRenderCtxFromVNode } from 'core/component/vnode'; import { execRenderObject } from 'core/component/render'; import { parseVNodeAsFlyweight } from 'core/component/flyweight'; import { createFakeCtx, initComponentVNode, FlyweightVNode } from 'core/component/functional'; import { applyDynamicAttrs } from 'core/component/render-function/v-attrs'; import { registerComponent } from 'core/component/register'; import type { CreateElement, VNode, VNodeData } from 'core/component/engines'; import type { FunctionalCtx, ComponentInterface, UnsafeComponentInterface } from 'core/component/interface'; export const $$ = symbolGenerator(); /** * Wraps the specified createElement function and returns a pair: * the wrapped function, and a list of registered render tasks. * * This method adds V4Fire specific logic (v-attrs, composites, etc.) to a simple createElement function. * * @param nativeCreateElement - original createElement function * @param baseCtx - base component context */ export function wrapCreateElement( nativeCreateElement: CreateElement, baseCtx: ComponentInterface ): [CreateElement, Function[]] { const tasks = baseCtx[$$.tasks] ?? <Function[]>[]; baseCtx[$$.tasks] = tasks; const engine = baseCtx.$renderEngine, {supports} = engine; const wrappedCreateElement = <CreateElement>function wrappedCreateElement( this: Nullable<ComponentInterface>, tag: CanUndef<string>, tagData?: VNodeData, children?: VNode[] ): CanPromise<VNode> { // eslint-disable-next-line 'use strict'; const ctx = this ?? baseCtx, unsafe = Object.cast<UnsafeComponentInterface>(ctx), attrs = Object.isPlainObject(tagData) ? tagData.attrs : undefined; const createElement = <typeof nativeCreateElement>function createElement(this: unknown, ...args: unknown[]) { if (supports.boundCreateElement) { const dontProvideBoundContext = nativeCreateElement[$$.wrappedCreateElement] === true; return nativeCreateElement.apply(dontProvideBoundContext ? this : unsafe, args); } return nativeCreateElement.apply(this, args); }; let tagName = tag, flyweightComponent; if (attrs == null) { if (tag === 'v-render') { return createElement(); } } else { if (tag === 'v-render') { return attrs.from ?? createElement(); } if (tagName?.[0] === '@') { flyweightComponent = tagName.slice(1); tagName = 'span'; } else { flyweightComponent = attrs['v4-flyweight-component']; } if (tagName != null && flyweightComponent != null) { tagName = tagName === 'span' ? flyweightComponent : tagName.dasherize(); attrs['v4-flyweight-component'] = tagName; } } const component = registerComponent(tagName); if (Object.isPlainObject(tagData)) { applyDynamicAttrs(tagData, component); } let renderKey = ''; if (attrs != null) { if (attrs['render-key'] != null) { renderKey = `${tagName}:${attrs['global-name']}:${attrs['render-key']}`; } if (renderKey !== '' && component == null) { attrs['data-render-key'] = renderKey; delete attrs['render-key']; } } let vnode = <CanUndef<VNode | FlyweightVNode>>unsafe.renderTmp[renderKey], needLinkToEl = Boolean(flyweightComponent); const needCreateFunctionalComponent = !supports.regular || vnode == null && flyweightComponent == null && supports.functional && component?.params.functional === true; if (component && needCreateFunctionalComponent) { needLinkToEl = true; const {componentName} = component; let renderObj = c.componentTemplates[componentName]; if (renderObj == null) { attachTemplatesToMeta(component, TPLS[componentName]); renderObj = c.componentTemplates[componentName]; } if (renderObj == null) { return createElement(); } const node = createElement('span', {...tagData, tag: undefined}, children), renderCtx = getComponentRenderCtxFromVNode(component, node, ctx); let baseCtx = <CanUndef<FunctionalCtx>>c.renderCtxCache[componentName]; if (baseCtx == null) { baseCtx = Object.create(engine.minimalCtx); // @ts-ignore (access) baseCtx.componentName = componentName; // @ts-ignore (access) baseCtx.meta = component; // @ts-ignore (access) component.params.functional = true; // @ts-ignore (access) baseCtx.instance = component.instance; // @ts-ignore (access) baseCtx.$options = {}; } c.renderCtxCache[componentName] = baseCtx; // @ts-ignore (access) baseCtx._l = ctx._l; // @ts-ignore (access) baseCtx._u = ctx._u; const fakeCtx = createFakeCtx<ComponentInterface>(Object.cast(wrappedCreateElement), renderCtx, baseCtx!, { initProps: true }); const createComponentVNode = () => { const vnode = execRenderObject(renderObj!, fakeCtx); if (Object.isPromise(vnode)) { return vnode.then((vnode) => initComponentVNode(Object.cast(vnode), fakeCtx, renderCtx)); } return initComponentVNode(vnode, fakeCtx, renderCtx); }; if (supports.ssr && Object.isPromise(fakeCtx.unsafe.$initializer)) { return fakeCtx.unsafe.$initializer.then(async () => patchVNode(await createComponentVNode())); } vnode = <FlyweightVNode>createComponentVNode(); } if (vnode == null) { // eslint-disable-next-line prefer-rest-params vnode = createElement.apply(unsafe, arguments); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (vnode == null) { return createElement(); } if (flyweightComponent != null) { vnode = parseVNodeAsFlyweight(vnode, <CreateElement>wrappedCreateElement, ctx); } } return patchVNode(vnode); function patchVNode<T extends VNode | FlyweightVNode>(vnode: T): T { const vData = vnode.data, ref = vData != null && (vData[$$.ref] ?? vData.ref); if (renderKey !== '') { unsafe.renderTmp[renderKey] = engine.cloneVNode(vnode); } // Add $refs link if it does not exist if (vData != null && ref != null && ctx !== baseCtx) { vData[$$.ref] = ref; vData.ref = `${ref}:${ctx.componentId}`; Object.defineProperty(unsafe.$refs, ref, { configurable: true, enumerable: true, get: () => { const r = baseCtx.unsafe.$refs, l = r[`${ref}:${unsafe.$componentId}`] ?? r[`${ref}:${ctx.componentId}`]; if (l != null) { return l; } return 'fakeInstance' in vnode ? vnode.fakeInstance : vnode.elm; } }); } // Add $el link if it does not exist if (needLinkToEl && 'fakeInstance' in vnode) { Object.defineProperty(vnode.fakeInstance, '$el', { enumerable: true, configurable: true, set(): void { // Loopback }, get(): CanUndef<Node> { return vnode.elm; } }); } if (tasks.length > 0) { for (let i = 0; i < tasks.length; i++) { tasks[i](vnode); } tasks.splice(0); } vnode.fakeContext = ctx; return vnode; } }; wrappedCreateElement[$$.wrappedCreateElement] = true; if (supports.ssr) { const wrappedAsyncCreateElement = <CreateElement>function wrappedAsyncCreateElement( this: Nullable<ComponentInterface>, tag: CanUndef<string>, opts?: VNodeData, children?: VNode[] ): CanPromise<VNode> { if (children != null && children.length > 0) { children = children.flat(); // eslint-disable-next-line @typescript-eslint/unbound-method if (children.some(Object.isPromise)) { return Promise.all(children).then((children) => wrappedCreateElement.call(this, tag, opts, children)); } } // eslint-disable-next-line prefer-rest-params return wrappedCreateElement.apply(this, arguments); }; return [wrappedAsyncCreateElement, tasks]; } return [wrappedCreateElement, tasks]; }