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