Skip to content

Instantly share code, notes, and snippets.

@backbone87
Created December 2, 2020 19:48
Show Gist options
  • Save backbone87/9d41a114f4529ca38fb814ee46d20bc0 to your computer and use it in GitHub Desktop.
Save backbone87/9d41a114f4529ca38fb814ee46d20bc0 to your computer and use it in GitHub Desktop.
import {
defineComponent as originalDefineComponent,
EmitsOptions,
SetupContext,
watch,
onMounted,
DefineComponent,
RenderFunction,
ComputedOptions,
MethodOptions,
ComponentOptionsMixin,
ComponentOptionsWithoutProps as OriginalComponentOptionsWithoutProps,
ComponentPropsOptions,
ComponentOptionsWithObjectProps as OriginalComponentOptionsWithObjectProps,
ComponentObjectPropsOptions,
ExtractPropTypes,
ExtractDefaultPropTypes,
ComponentOptionsWithArrayProps as OriginalComponentOptionsWithArrayProps,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered,
onUnmounted,
onUpdated,
watchEffect,
getCurrentInstance,
ComponentInternalInstance,
} from 'vue';
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
import { setCurrentInstance } from 'vue';
export interface VueBound {
readonly watch: typeof watch;
readonly watchEffect: typeof watchEffect;
readonly onActivated: typeof onActivated;
readonly onBeforeMount: typeof onBeforeMount;
readonly onBeforeUnmount: typeof onBeforeUnmount;
readonly onBeforeUpdate: typeof onBeforeUpdate;
readonly onDeactivated: typeof onDeactivated;
readonly onErrorCaptured: typeof onErrorCaptured;
readonly onMounted: typeof onMounted;
readonly onRenderTracked: typeof onRenderTracked;
readonly onRenderTriggered: typeof onRenderTriggered;
readonly onUnmounted: typeof onUnmounted;
readonly onUpdated: typeof onUpdated;
}
export function bindContext<Props, RawBindings, E extends EmitsOptions>(
setup: BoundSetup<Props, RawBindings, E>,
): Setup<Props, RawBindings, E> {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return (props, ctx) => setup(props, ctx, new VueBoundImpl(getCurrentInstance()));
}
type Composable<A extends any[], R> = (ctx: VueBound, ...args: A) => R;
export function defineComposable<A extends any[], R>(fn: Composable<A, R>): Composable<A, R> {
return fn;
}
type Setup<Props, RawBindings, E extends EmitsOptions> = (
this: void,
props: Readonly<Props>,
ctx: SetupContext<E>,
) => Promise<RawBindings> | RawBindings | RenderFunction | void;
type BoundSetup<Props, RawBindings, E extends EmitsOptions> = (
this: void,
props: Readonly<Props>,
ctx: SetupContext<E>,
bound: VueBound,
) => Promise<RawBindings> | RawBindings | RenderFunction | void;
class VueBoundImpl implements VueBound {
public constructor(private readonly instance: ComponentInternalInstance | null) {}
public get watch(): VueBound['watch'] {
return bindWatch(this.instance);
}
public get watchEffect(): VueBound['watchEffect'] {
return bindWatchEffect(this.instance);
}
public get onActivated(): VueBound['onActivated'] {
return bindLifecycleHook(onActivated, this.instance);
}
public get onBeforeMount(): VueBound['onBeforeMount'] {
return bindLifecycleHook(onBeforeMount, this.instance);
}
public get onBeforeUnmount(): VueBound['onBeforeUnmount'] {
return bindLifecycleHook(onBeforeUnmount, this.instance);
}
public get onBeforeUpdate(): VueBound['onBeforeUpdate'] {
return bindLifecycleHook(onBeforeUpdate, this.instance);
}
public get onDeactivated(): VueBound['onDeactivated'] {
return bindLifecycleHook(onDeactivated, this.instance);
}
public get onErrorCaptured(): VueBound['onErrorCaptured'] {
return bindLifecycleHook(onErrorCaptured, this.instance);
}
public get onMounted(): VueBound['onMounted'] {
return bindLifecycleHook(onMounted, this.instance);
}
public get onRenderTracked(): VueBound['onRenderTracked'] {
return bindLifecycleHook(onRenderTracked, this.instance);
}
public get onRenderTriggered(): VueBound['onRenderTriggered'] {
return bindLifecycleHook(onRenderTriggered, this.instance);
}
public get onUnmounted(): VueBound['onUnmounted'] {
return bindLifecycleHook(onUnmounted, this.instance);
}
public get onUpdated(): VueBound['onUpdated'] {
return bindLifecycleHook(onUpdated, this.instance);
}
public get use(): <A extends any[], R>(fn: Composable<A, R>, ...args: A) => R {
return (fn, ...args) => fn(this, ...args);
}
}
function bindWatch(boundInstance: ComponentInternalInstance | null): typeof watch {
return withInstance(
(sources: any, cb: any, options: any) => watch(sources, withInstance(cb, boundInstance), options),
boundInstance,
);
}
function bindWatchEffect(boundInstance: ComponentInternalInstance | null): typeof watchEffect {
return withInstance((effect, options) => watchEffect(withInstance(effect, boundInstance), options), boundInstance);
}
function withInstance<A extends any[], R>(
fn: (...args: A) => R,
boundInstance: ComponentInternalInstance | null,
): (...args: A) => R {
return (...args) => {
const parentInstance = getCurrentInstance();
try {
setCurrentInstance(boundInstance);
return fn(...args);
} finally {
setCurrentInstance(parentInstance);
}
};
}
function bindLifecycleHook<T, R>(
hook: (arg: T, target?: ComponentInternalInstance | null) => R,
boundInstance: ComponentInternalInstance | null,
): (arg: T, target?: ComponentInternalInstance | null) => R {
return (arg, instance) => hook(arg, instance ?? boundInstance);
}
export function defineComponent<Props, RawBindings = object>(
setup: (props: Readonly<Props>, ctx: SetupContext, bound: VueBound) => RawBindings | RenderFunction,
): DefineComponent<Props, RawBindings>;
export function defineComponent<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>,
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>;
export function defineComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M, Mixin, Extends, E, EE>,
): DefineComponent<
Readonly<
{
[key in PropNames]?: any;
}
>,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
>;
export function defineComponent<
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>,
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>;
export function defineComponent(optionsOrSetup: any): any {
if (typeof optionsOrSetup === 'function') {
return originalDefineComponent(bindContext(optionsOrSetup) as any);
}
if (typeof optionsOrSetup !== 'object' || optionsOrSetup === null || typeof optionsOrSetup.setup !== 'function') {
return originalDefineComponent(optionsOrSetup);
}
optionsOrSetup.setup = bindContext(optionsOrSetup.setup);
return originalDefineComponent(optionsOrSetup);
}
export interface ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string
> extends Omit<OriginalComponentOptionsWithoutProps<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>, 'setup'> {
setup?: (
this: void,
props: Props,
ctx: SetupContext<E>,
bound: VueBound,
) => Promise<RawBindings> | RawBindings | RenderFunction | void;
}
export interface ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Props = Readonly<
{
[key in PropNames]?: any;
}
>
>
extends Omit<
OriginalComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M, Mixin, Extends, E, EE, Props>,
'setup'
> {
setup?: (
this: void,
props: Props,
ctx: SetupContext<E>,
bound: VueBound,
) => Promise<RawBindings> | RawBindings | RenderFunction | void;
}
export interface ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Props = Readonly<ExtractPropTypes<PropsOptions>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>
>
extends Omit<
OriginalComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, Props, Defaults>,
'setup'
> {
setup?: (
this: void,
props: Props,
ctx: SetupContext<E>,
bound: VueBound,
) => Promise<RawBindings> | RawBindings | RenderFunction | void;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment