CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue-facing-decorator

Vue typescript class and decorator based component system with support for ES class inheritance and Vue 3 composition API integration.

Pending
Overview
Eval results
Files

method-lifecycle-decorators.mddocs/

Method and Lifecycle Decorators

Decorators for watchers, event emitters, dependency injection, and lifecycle hooks in Vue components.

Capabilities

Watch Decorator

Decorator for reactive data watchers with comprehensive configuration options.

/**
 * Decorator for reactive data watchers
 * @param key - The reactive property or path to watch
 * @param options - Optional watcher configuration
 * @returns Method decorator
 */
function Watch(key: string, options?: WatchOptions): MethodDecorator;

interface WatchOptions {
  flush?: 'post';
  deep?: boolean;
  immediate?: boolean;
}

interface WatchConfig {
  key: string;
  handler: WatchCallback;
  flush?: 'post';
  deep?: boolean;
  immediate?: boolean;
}

Usage Examples:

import { Component, Setup, Watch } from "vue-facing-decorator";
import { ref, reactive } from "vue";

@Component
class WatchComponent {
  @Setup(() => ref("initial"))
  message!: string;

  @Setup(() => ref(0))
  count!: number;

  @Setup(() => reactive({ nested: { value: "deep" } }))
  deepObject!: { nested: { value: string } };

  // Basic watcher
  @Watch("message")
  onMessageChange(newVal: string, oldVal: string) {
    console.log(`Message changed from "${oldVal}" to "${newVal}"`);
  }

  // Immediate watcher
  @Watch("count", { immediate: true })
  onCountChange(newVal: number, oldVal: number) {
    console.log(`Count: ${newVal} (was: ${oldVal})`);
  }

  // Deep watcher for objects
  @Watch("deepObject", { deep: true })
  onDeepObjectChange(newVal: any, oldVal: any) {
    console.log("Deep object changed:", newVal, oldVal);
  }

  // Post-flush watcher (after DOM updates)
  @Watch("message", { flush: "post" })
  onMessageChangePost(newVal: string) {
    // This runs after DOM updates
    console.log("DOM updated with new message:", newVal);
  }

  // Multiple watchers on same property
  @Watch("count")
  firstCountWatcher(newVal: number) {
    console.log("First watcher:", newVal);
  }

  @Watch("count", { immediate: true })
  secondCountWatcher(newVal: number) {
    console.log("Second watcher:", newVal);
  }
}

Emit Decorator

Decorator for event emission methods with custom event names.

/**
 * Decorator for event emission methods
 * @param eventName - Optional custom event name, defaults to method name
 * @returns Method decorator
 */
function Emit(eventName?: string): MethodDecorator;

type EmitConfig = null | string;

Usage Examples:

import { Component, Emit, Setup } from "vue-facing-decorator";
import { ref } from "vue";

@Component
class EmitComponent {
  @Setup(() => ref("Hello"))
  message!: string;

  // Basic emit - event name matches method name
  @Emit()
  clicked() {
    return { timestamp: Date.now() };
  }

  // Custom event name
  @Emit("customEvent")
  handleAction() {
    return this.message;
  }

  // Emit without return value
  @Emit("simpleEvent")
  simpleAction() {
    console.log("Action performed");
  }

  // Async emit
  @Emit("asyncEvent")
  async asyncAction() {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return { result: "async complete" };
  }

  // Emit with parameters
  @Emit("dataChange")
  changeData(newData: any) {
    this.message = newData.message;
    return newData;
  }

  // Multiple emits from same method
  @Emit("start")
  @Emit("process")
  complexAction() {
    return { action: "complex", data: this.message };
  }
}

// Usage in parent:
// <EmitComponent 
//   @clicked="onClicked"
//   @customEvent="onCustom"
//   @simpleEvent="onSimple"
//   @asyncEvent="onAsync"
//   @dataChange="onDataChange"
// />

Provide Decorator

Decorator for providing reactive data to descendant components.

/**
 * Decorator for providing reactive data to descendant components
 * @param key - Optional provide key, defaults to property name
 * @returns Property decorator
 */
function Provide(key?: string): PropertyDecorator;

type ProvideConfig = null | string;

Usage Examples:

import { Component, Provide, Setup } from "vue-facing-decorator";
import { ref, reactive } from "vue";

@Component
class ProvideComponent {
  // Provide with default key (property name)
  @Provide()
  @Setup(() => ref("provided value"))
  sharedData!: string;

  // Provide with custom key
  @Provide("customKey")
  @Setup(() => reactive({ theme: "dark", lang: "en" }))
  appConfig!: { theme: string; lang: string };

  // Provide computed value
  @Provide("computedValue")
  @Setup(() => computed(() => `Config: ${this.appConfig.theme}/${this.appConfig.lang}`))
  configSummary!: string;

  // Provide method
  @Provide()
  updateTheme(newTheme: string) {
    this.appConfig.theme = newTheme;
  }

  // Provide static value
  @Provide("staticKey")
  staticValue = "never changes";
}

Inject Decorator

Decorator for injecting provided data from ancestor components.

/**
 * Decorator for injecting provided data from ancestor components
 * @param config - Optional injection configuration
 * @returns Property decorator
 */
function Inject(config?: InjectConfig): PropertyDecorator;

interface InjectConfig {
  from?: string | symbol | Symbol | InjectionKey<any>;
  default?: any;
}

Usage Examples:

import { Component, Inject } from "vue-facing-decorator";
import { InjectionKey } from "vue";

// Define injection keys for type safety
const ThemeKey: InjectionKey<string> = Symbol('theme');
const ConfigKey: InjectionKey<{theme: string, lang: string}> = Symbol('config');

@Component
class InjectComponent {
  // Basic inject - uses property name as key
  @Inject()
  sharedData!: string;

  // Inject with custom key
  @Inject({ from: "customKey" })
  appConfig!: { theme: string; lang: string };

  // Inject with default value
  @Inject({ from: "maybeUndefined", default: "default value" })
  optionalValue!: string;

  // Inject with symbol key for type safety
  @Inject({ from: ThemeKey })
  theme!: string;

  @Inject({ from: ConfigKey, default: () => ({ theme: "light", lang: "en" }) })
  config!: { theme: string; lang: string };

  // Inject method
  @Inject({ from: "updateTheme" })
  updateTheme!: (theme: string) => void;

  mounted() {
    console.log("Injected data:", this.sharedData);
    console.log("App config:", this.appConfig);
    
    // Call injected method
    if (this.updateTheme) {
      this.updateTheme("light");
    }
  }
}

Hook Decorator

Decorator for explicitly marking methods as lifecycle hooks.

/**
 * Decorator for explicitly marking methods as lifecycle hooks
 * @returns Method decorator
 */
function Hook(): MethodDecorator;

type HookConfig = null;

Usage Examples:

import { Component, Hook, Setup } from "vue-facing-decorator";
import { ref } from "vue";

@Component
class HookComponent {
  @Setup(() => ref("component data"))
  data!: string;

  // Explicit hook decoration (optional - hooks are auto-detected by name)
  @Hook()
  mounted() {
    console.log("Component mounted");
  }

  @Hook()
  beforeUnmount() {
    console.log("About to unmount");
  }

  // Regular lifecycle hooks (auto-detected, no decorator needed)
  created() {
    console.log("Component created");
  }

  updated() {
    console.log("Component updated");
  }

  // Custom method that happens to have hook name but isn't a hook
  @Hook()
  customMounted() {
    // This will be treated as a regular method, not a lifecycle hook
    console.log("Custom method with hook-like name");
  }
}

Lifecycle Hook Support

All Vue 3 lifecycle hooks are automatically supported by name:

// Vue 3 Lifecycle Hooks (all supported automatically)
interface LifecycleHooks {
  beforeCreate?(): void;
  created?(): void;
  beforeMount?(): void;
  mounted?(): void;
  beforeUpdate?(): void;
  updated?(): void;
  activated?(): void;
  deactivated?(): void;
  beforeUnmount?(): void;
  unmounted?(): void;
  renderTracked?(): void;
  renderTriggered?(): void;
  errorCaptured?(): void;
  serverPrefetch?(): void;
  render?(): VNode;
}

// Legacy Vue 2 hooks (still supported)
interface LegacyHooks {
  beforeDestroy?(): void;
  destroyed?(): void;
}

Complete Lifecycle Example:

import { Component, Setup, Watch, Emit, Provide, Inject } from "vue-facing-decorator";
import { ref } from "vue";

@Component
class CompleteLifecycleComponent {
  @Setup(() => ref(0))
  counter!: number;

  @Provide()
  @Setup(() => ref("provided from parent"))
  sharedValue!: string;

  @Inject({ from: "parentData", default: "no parent" })
  parentData!: string;

  // All lifecycle hooks
  beforeCreate() {
    console.log("Before create - component instance being created");
  }

  created() {
    console.log("Created - component instance created");
  }

  beforeMount() {
    console.log("Before mount - about to mount");
  }

  mounted() {
    console.log("Mounted - component mounted to DOM");
  }

  beforeUpdate() {
    console.log("Before update - reactive data changed, about to update");
  }

  updated() {
    console.log("Updated - DOM updated");
  }

  beforeUnmount() {
    console.log("Before unmount - about to unmount");
  }

  unmounted() {
    console.log("Unmounted - component unmounted");
  }

  // Watcher
  @Watch("counter")
  onCounterChange(newVal: number, oldVal: number) {
    this.counterChanged(newVal, oldVal);
  }

  // Emitter
  @Emit("counterChanged")
  counterChanged(newVal: number, oldVal: number) {
    return { newVal, oldVal, timestamp: Date.now() };
  }

  increment() {
    this.counter++;
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-vue-facing-decorator

docs

advanced-features.md

core-components.md

index.md

method-lifecycle-decorators.md

property-decorators.md

tile.json