UNPKG

vue-facing-decorator

Version:

Vue typescript class and decorator based component.

198 lines (181 loc) 6.99 kB
import { defineComponent, type ComponentCustomOptions, type MethodOptions } from 'vue'; import { obtainSlot, getSuperSlot, getProviderFunction } from './utils' import { build as optionSetup } from './option/setup' import { build as optionComputed } from './option/computed' import { build as optionData } from './option/data' import { build as optionMethodsAndHooks } from './option/methodsAndHooks' import { build as optionRef } from './option/ref' import { build as optionWatch } from './option/watch' import { build as optionProps } from './option/props' import { build as optionInject } from './option/inject' import { build as optionProvide } from './option/provide' import { build as optionEmit } from './option/emit' import { build as optionVModel } from './option/vmodel' import { build as optionAccessor } from './option/accessor' import type { SetupContext } from 'vue'; import type { OptionBuilder } from './optionBuilder' import type { VueCons } from './index' import * as DecoratorCompatible from './deco3/utils' export type Cons = VueCons type SetupFunction<T> = (this: void, props: Readonly<any>, ctx: SetupContext<any>) => T | Promise<T> export type OptionSetupFunction = SetupFunction<any> export type ComponentSetupFunction = SetupFunction<Record<string, any>> function ComponentOption(cons: Cons, extend?: any) { const optionBuilder: OptionBuilder = {} optionSetup(cons, optionBuilder) optionVModel(cons, optionBuilder) optionComputed(cons, optionBuilder)//after VModel optionWatch(cons, optionBuilder) optionProps(cons, optionBuilder) optionInject(cons, optionBuilder) optionEmit(cons, optionBuilder) optionRef(cons, optionBuilder)//after Computed optionAccessor(cons, optionBuilder) optionMethodsAndHooks(cons, optionBuilder)//the last one const raw = { name: cons.name, setup: optionBuilder.setup, data() { delete optionBuilder.data optionData(cons, optionBuilder, this) return optionBuilder.data ?? {} }, methods: optionBuilder.methods, computed: optionBuilder.computed, watch: optionBuilder.watch, props: optionBuilder.props, inject: optionBuilder.inject, provide() { optionProvide(cons, optionBuilder, this) return optionBuilder.provide ?? {} }, ...optionBuilder.hooks, extends: extend } return raw as any } type ComponentOption = { name?: string emits?: string[] provide?: Record<string, any> | Function components?: Record<string, any> directives?: Record<string, any>; inheritAttrs?: boolean; expose?: string[]; render?: Function; modifier?: (raw: any) => any options?: ComponentCustomOptions & Record<string, any> template?: string mixins?: any[] setup?: ComponentSetupFunction methods?: MethodOptions } type ComponentConsOption = Cons | ComponentOption function buildComponent(cons: Cons, arg: ComponentOption, extend?: any): any { const option = ComponentOption(cons, extend) const slot = obtainSlot(cons.prototype) Object.keys(arg).reduce<Record<string, any>>((option, name: string) => { if (['options', 'modifier', 'methods', 'emits', 'setup', 'provide'].includes(name)) { return option } option[name] = arg[name as keyof ComponentOption] return option }, option) //apply event emits let emits = Array.from(slot.obtainMap('emits').keys()) if (Array.isArray(arg.emits)) { emits = Array.from(new Set([...emits, ...arg.emits])) } option.emits = emits //merge methods if ('object' === typeof arg.methods && !Array.isArray(arg.methods) && arg.methods !== null) { option.methods??={} Object.assign(option.methods, arg.methods); } //merge setup function if (!option.setup) { option.setup = arg.setup } else { const oldSetup: OptionSetupFunction = option.setup const newSetup: ComponentSetupFunction = arg.setup ?? function () { return {} } const setup: ComponentSetupFunction = function (props, ctx) { const newRet = newSetup(props, ctx) const oldRet = oldSetup(props, ctx) if (oldRet instanceof Promise || newRet instanceof Promise) { return Promise.all([newRet, oldRet]).then((arr) => { return Object.assign({}, arr[0], arr[1]) }) } else { return Object.assign({}, newRet, oldRet) } } option.setup = setup } //merge provide function const oldProvider = getProviderFunction(option.provide) const newProvider = getProviderFunction(arg.provide) option.provide = function() { return Object.assign({}, oldProvider.call(this), newProvider.call(this)) } //custom decorator const map = slot.getMap('customDecorator') if (map && map.size > 0) { map.forEach((v) => { v.forEach(ite=>ite.creator.apply({}, [option, ite.key])) }) } //shallow merge options if (arg.options) { Object.assign(option, arg.options) } //apply modifier if (arg.modifier) { arg.modifier(option) } return defineComponent(option) } function build(cons: Cons, option: ComponentOption) { const slot = obtainSlot(cons.prototype) slot.inComponent = true const superSlot = getSuperSlot(cons.prototype) if (superSlot) { if (!superSlot.inComponent) { throw 'Class should be decorated by Component or ComponentBase: ' + slot.master } if (superSlot.cachedVueComponent === null) { throw 'Component decorator 1' } } const component = buildComponent(cons, option, superSlot === null ? undefined : superSlot.cachedVueComponent) component.__vfdConstructor = cons slot.cachedVueComponent = component; (cons as any).__vccOpts = component } function _Component(cb: (cons: Cons, option: ComponentOption) => any, arg: ComponentConsOption, ctx?: ClassDecoratorContext) { if (typeof arg === 'function') { return DecoratorCompatible.compatibleClassDecorator(function (cons: Cons) { return cb(cons, {}) })(arg, ctx) } return DecoratorCompatible.compatibleClassDecorator(function (cons: Cons) { return cb(cons, arg) }) } export function ComponentBase(arg: ComponentConsOption, ctx?: ClassDecoratorContext): any { return _Component(function (cons: Cons, option: ComponentOption) { build(cons, option) return cons }, arg, ctx) } export const Component = ComponentBase export function toNative<T extends Cons>(cons: T): T { const slot = obtainSlot(cons.prototype) if (!slot.inComponent) { throw 'to native 1' } const cached = slot.cachedVueComponent if (!cached) { throw 'to native 2' } return cached }