Skip to content

Component Library Architecture Design

Overview

This document explores the architectural principles and design patterns that make Element Plus a robust, scalable, and maintainable component library. We'll examine the core architecture, design decisions, and implementation strategies that enable Element Plus to serve diverse application needs.

Core Architecture Principles

1. Modular Design

Element Plus follows a modular architecture where each component is self-contained and independently deployable.

typescript
// Component module structure
// packages/components/button/
// ├── src/
// │   ├── button.vue
// │   ├── button-group.vue
// │   └── constants.ts
// ├── style/
// │   ├── css.ts
// │   └── index.ts
// ├── __tests__/
// │   └── button.test.ts
// └── index.ts

// Component entry point
export { default as ElButton } from './src/button.vue'
export { default as ElButtonGroup } from './src/button-group.vue'
export * from './src/constants'

// Style entry point
import './style/index.scss'

// Type definitions
export interface ButtonProps {
  size?: 'large' | 'default' | 'small'
  type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text'
  plain?: boolean
  round?: boolean
  circle?: boolean
  loading?: boolean
  disabled?: boolean
  icon?: string | Component
  autofocus?: boolean
  nativeType?: 'button' | 'submit' | 'reset'
  autoInsertSpace?: boolean
  color?: string
  dark?: boolean
  link?: boolean
  text?: boolean
  bg?: boolean
  tag?: string | Component
}

2. Composition API Integration

Element Plus leverages Vue 3's Composition API for better code organization and reusability.

typescript
// Core composables architecture
// packages/hooks/
// ├── use-namespace/
// ├── use-locale/
// ├── use-z-index/
// ├── use-id/
// ├── use-size/
// └── use-global-config/

// Namespace composable for consistent CSS class generation
import { computed, unref } from 'vue'
import type { MaybeRef } from '@vueuse/core'

export const defaultNamespace = 'el'
const statePrefix = 'is-'

const _bem = (
  namespace: string,
  block: string,
  blockSuffix?: string,
  element?: string,
  modifier?: string
) => {
  let cls = `${namespace}-${block}`
  if (blockSuffix) {
    cls += `-${blockSuffix}`
  }
  if (element) {
    cls += `__${element}`
  }
  if (modifier) {
    cls += `--${modifier}`
  }
  return cls
}

export const useNamespace = (block: string) => {
  const namespace = computed(() => defaultNamespace)
  const b = (blockSuffix = '') =>
    _bem(unref(namespace), block, blockSuffix, '', '')
  const e = (element?: string) =>
    element ? _bem(unref(namespace), block, '', element, '') : ''
  const m = (modifier?: string) =>
    modifier ? _bem(unref(namespace), block, '', '', modifier) : ''
  const be = (blockSuffix?: string, element?: string) =>
    blockSuffix && element
      ? _bem(unref(namespace), block, blockSuffix, element, '')
      : ''
  const em = (element?: string, modifier?: string) =>
    element && modifier
      ? _bem(unref(namespace), block, '', element, modifier)
      : ''
  const bm = (blockSuffix?: string, modifier?: string) =>
    blockSuffix && modifier
      ? _bem(unref(namespace), block, blockSuffix, '', modifier)
      : ''
  const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
    blockSuffix && element && modifier
      ? _bem(unref(namespace), block, blockSuffix, element, modifier)
      : ''
  const is: {
    (name: string, state: boolean | undefined): string
    (name: string): string
  } = (name: string, ...args: [boolean | undefined] | []) => {
    const state = args.length >= 1 ? args[0]! : true
    return name && state ? `${statePrefix}${name}` : ''
  }

  // for css var
  const cssVar = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${key}`] = object[key]
      }
    }
    return styles
  }
  const cssVarBlock = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${block}-${key}`] = object[key]
      }
    }
    return styles
  }
  const cssVarName = (name: string) => `--${namespace.value}-${name}`
  const cssVarBlockName = (name: string) => `--${namespace.value}-${block}-${name}`

  return {
    namespace,
    b,
    e,
    m,
    be,
    em,
    bm,
    bem,
    is,
    cssVar,
    cssVarBlock,
    cssVarName,
    cssVarBlockName
  }
}

// Global configuration composable
import { computed, getCurrentInstance, inject, provide, ref, unref } from 'vue'
import { configProviderContextKey } from '@element-plus/tokens'
import type { MaybeRef } from '@vueuse/core'
import type { ConfigProviderContext } from '@element-plus/tokens'

const globalConfig = ref<ConfigProviderContext>()

export function useGlobalConfig<
  K extends keyof ConfigProviderContext,
  D extends ConfigProviderContext[K]
>(
  key: K,
  defaultValue?: D
): Ref<Exclude<ConfigProviderContext[K], undefined> | D>
export function useGlobalConfig(): Ref<ConfigProviderContext>
export function useGlobalConfig(key?: keyof ConfigProviderContext, defaultValue = undefined) {
  const config = getCurrentInstance()
    ? inject(configProviderContextKey, globalConfig)
    : globalConfig
  if (key) {
    return computed(() => config.value?.[key] ?? defaultValue)
  } else {
    return config
  }
}

export const provideGlobalConfig = (
  config: MaybeRef<ConfigProviderContext>,
  app?: App,
  global = false
) => {
  const inSetup = !!getCurrentInstance()
  const oldConfig = inSetup ? useGlobalConfig() : undefined

  const provideFn = app?.provide ?? (inSetup ? provide : undefined)
  if (!provideFn) {
    console.warn('provideGlobalConfig', 'provideGlobalConfig() can only be used inside setup().')
    return
  }

  const context = computed(() => {
    const cfg = unref(config)
    if (!oldConfig?.value) return cfg
    return mergeConfig(oldConfig.value, cfg)
  })
  provideFn(configProviderContextKey, context)
  if (global || !globalConfig.value) {
    globalConfig.value = context.value
  }
  return context
}

const mergeConfig = (
  a: ConfigProviderContext,
  b: ConfigProviderContext
): ConfigProviderContext => {
  const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])]
  const obj: Record<string, any> = {}
  for (const key of keys) {
    obj[key] = b[key] ?? a[key]
  }
  return obj
}

3. Theme System Architecture

Element Plus implements a sophisticated theme system based on CSS custom properties and SCSS variables.

scss
// Theme architecture
// packages/theme-chalk/src/
// ├── common/
// │   ├── var.scss          // CSS custom properties
// │   ├── transition.scss   // Animation definitions
// │   └── popup.scss        // Popup utilities
// ├── mixins/
// │   ├── _button.scss      // Component-specific mixins
// │   ├── config.scss       // Configuration mixins
// │   ├── function.scss     // Utility functions
// │   └── mixins.scss       // Core mixins
// └── components/
//     ├── button.scss
//     ├── input.scss
//     └── ...

// CSS Custom Properties System
:root {
  // Color System
  --el-color-primary: #409eff;
  --el-color-primary-light-3: #79bbff;
  --el-color-primary-light-5: #a0cfff;
  --el-color-primary-light-7: #c6e2ff;
  --el-color-primary-light-8: #d9ecff;
  --el-color-primary-light-9: #ecf5ff;
  --el-color-primary-dark-2: #337ecc;
  
  // Typography
  --el-font-size-extra-large: 20px;
  --el-font-size-large: 18px;
  --el-font-size-medium: 16px;
  --el-font-size-base: 14px;
  --el-font-size-small: 13px;
  --el-font-size-extra-small: 12px;
  
  // Spacing
  --el-border-radius-base: 4px;
  --el-border-radius-small: 2px;
  --el-border-radius-round: 20px;
  --el-border-radius-circle: 100%;
  
  // Component-specific variables
  --el-button-font-weight: var(--el-font-weight-primary);
  --el-button-border-color: var(--el-border-color);
  --el-button-bg-color: var(--el-fill-color-blank);
  --el-button-text-color: var(--el-text-color-regular);
  --el-button-disabled-text-color: var(--el-disabled-text-color);
  --el-button-disabled-bg-color: var(--el-fill-color-blank);
  --el-button-disabled-border-color: var(--el-border-color-light);
  --el-button-hover-text-color: var(--el-color-primary);
  --el-button-hover-bg-color: var(--el-color-primary-light-9);
  --el-button-hover-border-color: var(--el-color-primary-light-7);
  --el-button-active-text-color: var(--el-button-hover-text-color);
  --el-button-active-border-color: var(--el-color-primary);
  --el-button-active-bg-color: var(--el-button-hover-bg-color);
}

// Theme generation mixins
@mixin genTheme($colors) {
  @each $type in (primary, success, warning, danger, error, info) {
    $color: map.get($colors, $type);
    
    @include set-css-var-value(('color', $type), $color);
    
    @for $i from 1 through 9 {
      @include set-css-var-value(
        ('color', $type, 'light', $i),
        mix(#fff, $color, math.percentage(math.div($i, 10)))
      );
    }
    
    @include set-css-var-value(
      ('color', $type, 'dark', 2),
      mix(#000, $color, 0.2)
    );
  }
}

// Component mixin system
@mixin button-variant($color, $background-color, $border-color) {
  color: $color;
  background-color: $background-color;
  border-color: $border-color;
  
  &:hover,
  &:focus {
    background: mix(#fff, $background-color, 20%);
    border-color: mix(#fff, $border-color, 20%);
    color: $color;
  }
  
  &:active {
    background: mix(#000, $background-color, 10%);
    border-color: mix(#000, $border-color, 10%);
    color: $color;
    outline: none;
  }
  
  &.is-disabled {
    &,
    &:hover,
    &:focus,
    &:active {
      color: #fff;
      background-color: mix(#fff, $background-color, 50%);
      border-color: mix(#fff, $border-color, 50%);
    }
  }
}

@mixin button-size($padding-vertical, $padding-horizontal, $font-size, $border-radius) {
  padding: $padding-vertical $padding-horizontal;
  font-size: $font-size;
  border-radius: $border-radius;
  
  &.is-round {
    border-radius: math.div($font-size + $padding-vertical * 2, 2);
  }
}

4. Plugin System Architecture

Element Plus provides a flexible plugin system for extending functionality.

typescript
// Plugin system architecture
import type { App } from 'vue'
import { INSTALLED_KEY } from '@element-plus/constants'

export interface InstallOptions {
  size?: ComponentSize
  zIndex?: number
  locale?: Language
  namespace?: string
}

export interface SFCWithInstall<T> {
  new (): T
  install(app: App, options?: InstallOptions): void
}

export interface SFCInstallWithContext<T> {
  new (): T
  install(app: App, options?: InstallOptions): void
  _context: AppContext | null
}

// Plugin installation utility
export const withInstall = <T, E extends Record<string, any>>(
  main: T,
  extra?: E
) => {
  ;(main as SFCWithInstall<T>).install = (app: App, options?: InstallOptions) => {
    for (const comp of [main, ...Object.values(extra ?? {})]) {
      app.component(comp.name, comp)
    }
    
    if (options) {
      app.provide(configProviderContextKey, options)
    }
  }

  if (extra) {
    for (const [key, comp] of Object.entries(extra)) {
      ;(main as any)[key] = comp
    }
  }
  return main as SFCWithInstall<T> & E
}

// Service plugin pattern
export const withInstallFunction = <T>(fn: T, name: string) => {
  ;(fn as SFCWithInstall<T>).install = (app: App) => {
    ;(fn as SFCInstallWithContext<T>)._context = app._context
    app.config.globalProperties[name] = fn
  }

  return fn as SFCInstallWithContext<T>
}

// Directive plugin pattern
export const withInstallDirective = <T>(directive: T, name: string) => {
  ;(directive as SFCWithInstall<T>).install = (app: App) => {
    app.directive(name, directive)
  }

  return directive as SFCWithInstall<T>
}

// Example component plugin
import Button from './button.vue'
import ButtonGroup from './button-group.vue'

export const ElButton = withInstall(Button, {
  ButtonGroup
})
export default ElButton
export const ElButtonGroup = ElButton.ButtonGroup

// Example service plugin
import { ElMessage } from './message'

export default withInstallFunction(ElMessage, '$message')

5. Type System Architecture

Element Plus provides comprehensive TypeScript support with a well-structured type system.

typescript
// Type system architecture
// packages/utils/types.ts

// Utility types
export type Awaitable<T> = Promise<T> | T
export type Arrayable<T> = T | T[]
export type Nullable<T> = T | null
export type Mutable<T> = { -readonly [P in keyof T]: T[P] }
export type HTMLElementCustomized<T = {}> = HTMLElement & T

// Component size system
export type ComponentSize = 'large' | 'default' | 'small'

// Event handler types
export type EventHandler<T = Event> = (event: T) => void
export type MouseEventHandler = EventHandler<MouseEvent>
export type KeyboardEventHandler = EventHandler<KeyboardEvent>
export type FocusEventHandler = EventHandler<FocusEvent>

// Form validation types
export interface FormValidateCallback {
  (isValid: boolean, invalidFields?: ValidateFieldsError): void
}

export interface FormValidateFailure {
  errors: ValidateError[] | null
  fields: ValidateFieldsError
}

export type FormValidationResult = Promise<boolean>
export type FormValidateResult = Promise<FormValidateFailure>

// Component prop types
export interface ComponentProps {
  size?: ComponentSize
  disabled?: boolean
  loading?: boolean
}

// Theme types
export interface ThemeVars {
  colorPrimary?: string
  colorSuccess?: string
  colorWarning?: string
  colorDanger?: string
  colorInfo?: string
  fontSize?: string
  fontFamily?: string
  borderRadius?: string
}

// Global configuration types
export interface ConfigProviderContext {
  a11y?: boolean
  locale?: Language
  size?: ComponentSize
  button?: {
    autoInsertSpace?: boolean
  }
  experimentalFeatures?: {
    draw?: boolean
  }
  keyboardNavigation?: boolean
  message?: {
    max?: number
  }
  zIndex?: number
  namespace?: string
}

// Component instance types
export interface ComponentInternalInstance {
  uid: number
  type: ConcreteComponent
  parent: ComponentInternalInstance | null
  appContext: AppContext
  vnode: VNode
  next: VNode | null
  subTree: VNode
  effect: ReactiveEffect
  update: SchedulerJob
  render: InternalRenderFunction | null
  provides: Data
  scope: EffectScope
  accessCache: Data | null
  renderCache: (Function | VNode)[]
  components: Record<string, ConcreteComponent> | null
  directives: Record<string, Directive> | null
  propsOptions: NormalizedPropsOptions
  emitsOptions: ObjectEmitsOptions | null
  emit: EmitFn
  emitted: Record<string, boolean> | null
  propsDefaults: Data | undefined
  inheritAttrs?: boolean
  ctx: ComponentRenderContext
  data: Data
  props: Data
  attrs: Data
  slots: InternalSlots
  refs: Data
  setupState: Data
  setupContext: SetupContext | null
  suspense: SuspenseBoundary | null
  suspenseId: number
  asyncDep: Promise<any> | null
  asyncResolved: boolean
  isMounted: boolean
  isUnmounted: boolean
  isDeactivated: boolean
  bc: SchedulerJob[] | null
  c: SchedulerJob[] | null
  bm: SchedulerJob[] | null
  m: SchedulerJob[] | null
  bu: SchedulerJob[] | null
  u: SchedulerJob[] | null
  um: SchedulerJob[] | null
  bum: SchedulerJob[] | null
  da: SchedulerJob[] | null
  a: SchedulerJob[] | null
  rtg: SchedulerJob[] | null
  rtc: SchedulerJob[] | null
  ec: SchedulerJob[] | null
  sp: SchedulerJob[] | null
}

Component Design Patterns

1. Provider-Consumer Pattern

Element Plus uses the provider-consumer pattern for sharing configuration and state across component hierarchies.

typescript
// Form provider implementation
import { computed, inject, provide, reactive, ref, toRefs } from 'vue'
import { formContextKey, formItemContextKey } from '@element-plus/tokens'
import type { FormContext, FormItemContext } from '@element-plus/tokens'

// Form provider
export const useFormProvider = (props: FormProps) => {
  const fields = ref<FormItemContext[]>([])
  
  const addField = (field: FormItemContext) => {
    fields.value.push(field)
  }
  
  const removeField = (field: FormItemContext) => {
    if (field.prop) {
      fields.value.splice(fields.value.indexOf(field), 1)
    }
  }
  
  const resetFields = (properties: string[] = []) => {
    if (!properties.length) {
      return fields.value.forEach(field => field.resetField())
    }
    
    const filteredFields = fields.value.filter(
      field => field.prop && properties.includes(field.prop)
    )
    filteredFields.forEach(field => field.resetField())
  }
  
  const clearValidate = (properties: string[] = []) => {
    if (!properties.length) {
      return fields.value.forEach(field => field.clearValidate())
    }
    
    const filteredFields = fields.value.filter(
      field => field.prop && properties.includes(field.prop)
    )
    filteredFields.forEach(field => field.clearValidate())
  }
  
  const validate = async (callback?: FormValidateCallback) => {
    return validateField(undefined, callback)
  }
  
  const validateField = async (
    modelProps: string[] = [],
    callback?: FormValidateCallback
  ) => {
    let validationErrors: ValidateFieldsError = {}
    
    const fieldsToValidate = modelProps.length > 0
      ? fields.value.filter(field => modelProps.includes(field.prop!))
      : fields.value
    
    if (fieldsToValidate.length === 0) {
      callback?.(true, {})
      return true
    }
    
    let valid = true
    let count = 0
    
    for (const field of fieldsToValidate) {
      field.validate('', (message, invalidFields) => {
        if (message) {
          valid = false
          validationErrors = {
            ...validationErrors,
            ...invalidFields
          }
        }
        
        if (++count === fieldsToValidate.length) {
          callback?.(valid, validationErrors)
        }
      })
    }
    
    return valid
  }
  
  const context: FormContext = reactive({
    ...toRefs(props),
    addField,
    removeField,
    resetFields,
    clearValidate,
    validate,
    validateField
  })
  
  provide(formContextKey, context)
  
  return context
}

// Form consumer
export const useFormItem = () => {
  const form = inject(formContextKey, undefined)
  const formItem = inject(formItemContextKey, undefined)
  
  return {
    form,
    formItem
  }
}

// Form item provider
export const useFormItemProvider = (props: FormItemProps) => {
  const { form } = useFormItem()
  const validateState = ref('')
  const validateMessage = ref('')
  const validateDisabled = ref(false)
  
  const validate = (trigger: string, callback?: FormValidateCallback) => {
    // Validation logic implementation
  }
  
  const resetField = () => {
    // Reset field logic
  }
  
  const clearValidate = () => {
    validateState.value = ''
    validateMessage.value = ''
    validateDisabled.value = false
  }
  
  const context: FormItemContext = reactive({
    ...toRefs(props),
    validateState,
    validateMessage,
    validateDisabled,
    validate,
    resetField,
    clearValidate
  })
  
  provide(formItemContextKey, context)
  
  onMounted(() => {
    if (props.prop) {
      form?.addField(context)
    }
  })
  
  onBeforeUnmount(() => {
    form?.removeField(context)
  })
  
  return context
}

2. Compound Component Pattern

Element Plus uses compound components for complex UI patterns like Tables, Menus, and Forms.

typescript
// Table compound component implementation
// ElTable + ElTableColumn pattern

// Table provider
export const useTableProvider = (props: TableProps) => {
  const columns = ref<TableColumnContext[]>([])
  const store = reactive(new TableStore())
  
  const insertColumn = (column: TableColumnContext, index?: number) => {
    if (typeof index !== 'undefined') {
      columns.value.splice(index, 0, column)
    } else {
      columns.value.push(column)
    }
    
    store.updateColumns(columns.value)
  }
  
  const removeColumn = (column: TableColumnContext) => {
    const index = columns.value.indexOf(column)
    if (index > -1) {
      columns.value.splice(index, 1)
      store.updateColumns(columns.value)
    }
  }
  
  const context: TableContext = reactive({
    ...toRefs(props),
    store,
    columns: readonly(columns),
    insertColumn,
    removeColumn
  })
  
  provide(tableContextKey, context)
  
  return context
}

// Table column consumer
export const useTableColumn = (props: TableColumnProps) => {
  const table = inject(tableContextKey)
  const columnId = ref(`el-table-column-${generateId()}`)
  
  const columnConfig = computed(() => ({
    id: columnId.value,
    ...props,
    renderCell: (data: any) => {
      // Cell rendering logic
    },
    renderHeader: (data: any) => {
      // Header rendering logic
    }
  }))
  
  onMounted(() => {
    table?.insertColumn(columnConfig.value)
  })
  
  onBeforeUnmount(() => {
    table?.removeColumn(columnConfig.value)
  })
  
  return {
    columnId,
    columnConfig
  }
}

// Menu compound component
export const useMenuProvider = (props: MenuProps) => {
  const openedMenus = ref<string[]>([])
  const activeIndex = ref<string | null>(null)
  
  const openMenu = (index: string) => {
    if (!openedMenus.value.includes(index)) {
      openedMenus.value.push(index)
    }
  }
  
  const closeMenu = (index: string) => {
    const i = openedMenus.value.indexOf(index)
    if (i !== -1) {
      openedMenus.value.splice(i, 1)
    }
  }
  
  const handleMenuItemClick = (menuItem: MenuItemContext) => {
    if (menuItem.disabled) return
    
    activeIndex.value = menuItem.index
    emit('select', menuItem.index, menuItem.indexPath, menuItem)
  }
  
  const context: MenuContext = reactive({
    ...toRefs(props),
    openedMenus: readonly(openedMenus),
    activeIndex: readonly(activeIndex),
    openMenu,
    closeMenu,
    handleMenuItemClick
  })
  
  provide(menuContextKey, context)
  
  return context
}

3. Render Function Pattern

Element Plus uses render functions for dynamic content and complex rendering logic.

typescript
// Virtual list implementation with render functions
import { defineComponent, computed, ref, onMounted, onUpdated } from 'vue'

export const VirtualList = defineComponent({
  name: 'ElVirtualList',
  props: {
    data: {
      type: Array,
      required: true
    },
    itemSize: {
      type: Number,
      required: true
    },
    height: {
      type: Number,
      required: true
    },
    buffer: {
      type: Number,
      default: 5
    }
  },
  setup(props, { slots }) {
    const scrollTop = ref(0)
    const containerRef = ref<HTMLElement>()
    
    const visibleRange = computed(() => {
      const start = Math.floor(scrollTop.value / props.itemSize)
      const end = Math.min(
        start + Math.ceil(props.height / props.itemSize) + props.buffer,
        props.data.length
      )
      
      return {
        start: Math.max(0, start - props.buffer),
        end
      }
    })
    
    const visibleData = computed(() => {
      return props.data.slice(visibleRange.value.start, visibleRange.value.end)
    })
    
    const totalHeight = computed(() => {
      return props.data.length * props.itemSize
    })
    
    const offsetY = computed(() => {
      return visibleRange.value.start * props.itemSize
    })
    
    const handleScroll = (event: Event) => {
      scrollTop.value = (event.target as HTMLElement).scrollTop
    }
    
    return () => (
      <div
        ref={containerRef}
        class="el-virtual-list"
        style={{ height: `${props.height}px`, overflow: 'auto' }}
        onScroll={handleScroll}
      >
        <div style={{ height: `${totalHeight.value}px`, position: 'relative' }}>
          <div
            style={{
              transform: `translateY(${offsetY.value}px)`,
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0
            }}
          >
            {visibleData.value.map((item, index) => {
              const actualIndex = visibleRange.value.start + index
              return (
                <div
                  key={actualIndex}
                  style={{ height: `${props.itemSize}px` }}
                >
                  {slots.default?.({ item, index: actualIndex })}
                </div>
              )
            })}
          </div>
        </div>
      </div>
    )
  }
})

// Table cell render function
export const TableCell = defineComponent({
  name: 'ElTableCell',
  props: {
    column: {
      type: Object,
      required: true
    },
    row: {
      type: Object,
      required: true
    },
    rowIndex: {
      type: Number,
      required: true
    }
  },
  setup(props) {
    return () => {
      const { column, row, rowIndex } = props
      
      // Custom render function
      if (column.renderCell) {
        return column.renderCell({
          row,
          column,
          rowIndex,
          cellValue: row[column.property]
        })
      }
      
      // Slot render
      if (column.slots?.default) {
        return column.slots.default({
          row,
          column,
          rowIndex,
          cellValue: row[column.property]
        })
      }
      
      // Formatter function
      if (column.formatter) {
        return column.formatter(row, column, row[column.property], rowIndex)
      }
      
      // Default render
      return row[column.property]
    }
  }
})

Performance Optimization Strategies

1. Lazy Loading and Code Splitting

typescript
// Component lazy loading
export const LazyButton = defineAsyncComponent({
  loader: () => import('./button.vue'),
  loadingComponent: () => h('div', 'Loading...'),
  errorComponent: () => h('div', 'Error loading component'),
  delay: 200,
  timeout: 3000
})

// Tree-shakable imports
export { ElButton } from './button'
export { ElInput } from './input'
export { ElForm } from './form'
// ... other components

// Auto-import configuration
export const ElementPlusResolver = {
  type: 'component',
  resolve: (name: string) => {
    if (name.startsWith('El')) {
      return {
        name,
        from: 'element-plus',
        sideEffects: `element-plus/es/components/${name.slice(2).toLowerCase()}/style/css`
      }
    }
  }
}

2. Virtual Scrolling Implementation

typescript
// Virtual scrolling for large datasets
export const useVirtualScroll = ({
  itemSize,
  containerHeight,
  data,
  buffer = 5
}) => {
  const scrollTop = ref(0)
  const isScrolling = ref(false)
  const scrollingTimer = ref<NodeJS.Timeout>()
  
  const visibleRange = computed(() => {
    const start = Math.floor(scrollTop.value / itemSize)
    const visibleCount = Math.ceil(containerHeight / itemSize)
    
    return {
      start: Math.max(0, start - buffer),
      end: Math.min(data.length, start + visibleCount + buffer)
    }
  })
  
  const visibleData = computed(() => {
    return data.slice(visibleRange.value.start, visibleRange.value.end)
  })
  
  const totalHeight = computed(() => data.length * itemSize)
  const offsetY = computed(() => visibleRange.value.start * itemSize)
  
  const handleScroll = (event: Event) => {
    scrollTop.value = (event.target as HTMLElement).scrollTop
    
    if (!isScrolling.value) {
      isScrolling.value = true
    }
    
    clearTimeout(scrollingTimer.value)
    scrollingTimer.value = setTimeout(() => {
      isScrolling.value = false
    }, 150)
  }
  
  return {
    visibleRange,
    visibleData,
    totalHeight,
    offsetY,
    isScrolling,
    handleScroll
  }
}

3. Memoization and Caching

typescript
// Component memoization
export const MemoizedComponent = defineComponent({
  name: 'MemoizedComponent',
  props: {
    data: {
      type: Array,
      required: true
    },
    config: {
      type: Object,
      required: true
    }
  },
  setup(props) {
    // Memoized computed properties
    const processedData = computed(() => {
      return expensiveDataProcessing(props.data, props.config)
    })
    
    // Cached function results
    const memoizedCalculation = useMemoize(
      (input: any[]) => expensiveCalculation(input),
      {
        maxSize: 100,
        ttl: 5 * 60 * 1000 // 5 minutes
      }
    )
    
    return {
      processedData,
      memoizedCalculation
    }
  }
})

// Memoization utility
export function useMemoize<T extends (...args: any[]) => any>(
  fn: T,
  options: {
    maxSize?: number
    ttl?: number
    getKey?: (...args: Parameters<T>) => string
  } = {}
) {
  const {
    maxSize = 50,
    ttl = Infinity,
    getKey = (...args) => JSON.stringify(args)
  } = options
  
  const cache = new Map<string, { value: ReturnType<T>; timestamp: number }>()
  
  return (...args: Parameters<T>): ReturnType<T> => {
    const key = getKey(...args)
    const now = Date.now()
    
    // Check if cached result exists and is still valid
    const cached = cache.get(key)
    if (cached && (now - cached.timestamp) < ttl) {
      return cached.value
    }
    
    // Calculate new result
    const result = fn(...args)
    
    // Store in cache
    cache.set(key, { value: result, timestamp: now })
    
    // Cleanup old entries if cache is too large
    if (cache.size > maxSize) {
      const oldestKey = cache.keys().next().value
      cache.delete(oldestKey)
    }
    
    return result
  }
}

Testing Architecture

1. Component Testing Strategy

typescript
// Component testing utilities
import { mount, VueWrapper } from '@vue/test-utils'
import { ElConfigProvider } from 'element-plus'

export const createTestWrapper = <T>(
  component: T,
  options: {
    props?: Record<string, any>
    slots?: Record<string, any>
    global?: {
      plugins?: any[]
      provide?: Record<string, any>
    }
  } = {}
): VueWrapper<any> => {
  return mount(component, {
    global: {
      plugins: [ElementPlus],
      ...options.global
    },
    ...options
  })
}

// Test utilities for Element Plus components
export const getElementPlusWrapper = (component: any, props = {}) => {
  return mount({
    components: { TestComponent: component },
    template: '<ElConfigProvider><TestComponent v-bind="props" /></ElConfigProvider>',
    data() {
      return { props }
    }
  }, {
    global: {
      plugins: [ElementPlus]
    }
  })
}

// Accessibility testing helpers
export const testA11y = async (wrapper: VueWrapper<any>) => {
  const { violations } = await axe(wrapper.element)
  expect(violations).toHaveLength(0)
}

// Visual regression testing
export const takeSnapshot = (wrapper: VueWrapper<any>, name: string) => {
  expect(wrapper.element).toMatchSnapshot(name)
}

2. Integration Testing

typescript
// Form integration test example
describe('Form Integration', () => {
  it('should validate form with multiple fields', async () => {
    const wrapper = createTestWrapper({
      template: `
        <el-form ref="form" :model="form" :rules="rules">
          <el-form-item prop="name">
            <el-input v-model="form.name" />
          </el-form-item>
          <el-form-item prop="email">
            <el-input v-model="form.email" />
          </el-form-item>
          <el-button @click="submit">Submit</el-button>
        </el-form>
      `,
      data() {
        return {
          form: { name: '', email: '' },
          rules: {
            name: [{ required: true, message: 'Name is required' }],
            email: [{ required: true, message: 'Email is required' }]
          }
        }
      },
      methods: {
        async submit() {
          await this.$refs.form.validate()
        }
      }
    })
    
    const button = wrapper.find('button')
    await button.trigger('click')
    
    await nextTick()
    
    expect(wrapper.findAll('.el-form-item__error')).toHaveLength(2)
  })
})

Best Practices

1. Architecture Guidelines

  • Separation of Concerns: Keep logic, styling, and templates separate
  • Composition over Inheritance: Use composition API and mixins
  • Type Safety: Provide comprehensive TypeScript support
  • Performance: Implement lazy loading and virtual scrolling
  • Accessibility: Follow WCAG guidelines
  • Testing: Maintain high test coverage

2. Component Design Principles

  • Single Responsibility: Each component should have one clear purpose
  • Reusability: Design for reuse across different contexts
  • Configurability: Provide sensible defaults with customization options
  • Consistency: Follow established patterns and conventions
  • Documentation: Provide clear API documentation and examples

3. Performance Considerations

  • Bundle Size: Keep components lightweight and tree-shakable
  • Runtime Performance: Optimize for rendering and interaction speed
  • Memory Usage: Prevent memory leaks and optimize memory consumption
  • Loading Performance: Implement progressive loading strategies

Conclusion

Element Plus's architecture demonstrates:

  • Modular Design: Clean separation and independent components
  • Flexible Plugin System: Extensible architecture for customization
  • Type Safety: Comprehensive TypeScript integration
  • Performance Optimization: Virtual scrolling, lazy loading, and memoization
  • Testing Strategy: Comprehensive testing approach
  • Best Practices: Industry-standard patterns and principles

This architecture enables Element Plus to serve as a robust foundation for building scalable, maintainable, and performant Vue.js applications.

Element Plus Study Guide