han-feng/modular

View on GitHub
src/Modular.ts

Summary

Maintainability
D
2 days
Test Coverage
import Logger from 'js-logger'
import LogInfo from './LogInfo'
import ModulesLoader from './ModulesLoader'
import { ExtensionPoint, DefaultExtensionPoint, Type, Preprocessor } from './ExtensionPoint'

const logger = Logger.get('modular.core.Modular')

export interface Activator {
  start(modular: Modular, module: ModuleConfig): void
}

export interface ModuleConfig {
  name: string
  dependencies?: string[]
  extensionPoints?: { [index: string]: ExtensionPoint }
  extensions?: { [index: string]: any }
  preprocessors?: { [index: string]: Preprocessor }
  activator?: Activator
}

export interface ApplicationConfig extends ModuleConfig {
  version?: string
}

export interface ModularOptions {
  modules: ModuleConfig[]
  application?: ApplicationConfig
  strict?: boolean
}

/**
 * 模块化应用实现类
 */
export default class Modular {
  public readonly strict: boolean
  private inited: boolean = false
  private readonly logs: LogInfo[] = [] // 记录处理过程中产生的日志信息
  private application: ApplicationConfig
  private modules: ModuleConfig[]
  private extensionPoints: { [index: string]: DefaultExtensionPoint } = {}
  private attributes: { [index: string]: any } = {}

  /**
   * 构造函数
   * @param config 初始配置
   */
  constructor(options?: ModularOptions) {
    options = options || { modules: [] }
    this.strict = !!options.strict // 严格模式,暂未使用,保留
    this.modules = options.modules || []
    this.application = options.application || { name: 'Application' }
    this.init()
  }

  /**
   * 获取应用配置
   */
  getApplication() {
    return this.application
  }

  /**
   * 获取指定名称的模块配置
   * @param name 模块名称
   */
  getModule(name: string) {
    return this.modules.find(
      (module: ModuleConfig): boolean => module.name === name
    )
  }

  /**
   * 获取全部模块配置
   */
  getModules() {
    return this.modules
  }

  /**
   * 获取指定名称的扩展配置对象
   * @param name 扩展点名称
   * @returns 当扩展点类型为 Multiple 时,返回扩展配置对象数组;
   *          当扩展点类型为 Single 时,返回最后加入的扩展配置对象;
   *          当扩展点类型为 Mixin 时,返回所有扩展配置对象混合后的结果
   */
  getExtension(name: string) {
    const point = this.getExtensionPoint(name)
    if (point !== null) {
      return point.getExtension()
    }
    return null
  }

  /**
   * 获取原始配置对象数组,这些配置对象未经任何加工处理
   * @param name 扩展点名称
   */
  getExtensions(name: string) {
    const point = this.getExtensionPoint(name)
    if (point !== null) {
      return point.getExtensions()
    }
    return null
  }

  /**
   * 获取指定名称的扩展点定义
   * @param name 扩展点名称
   */
  getExtensionPoint(name: string) {
    const point = this.extensionPoints[name]
    if (point === undefined) {
      return null
    }
    return point
  }

  /**
   * 获取全部有效的扩展点定义
   */
  getExtensionPoints() {
    return this.extensionPoints
  }

  /**
   * 获取属性名集合
   */
  getAttributeNames() {
    return Object.keys(this.attributes)
  }

  /**
   * 设置属性
   * @param name 属性名
   * @param value 属性值
   */
  setAttribute(name: string, value: any) {
    this.attributes[name] = value
  }

  /**
   * 获取属性值
   * @param name 属性名
   */
  getAttribute(name: string) {
    return this.attributes[name]
  }

  /**
   * 启动模块化应用
   */
  start() {
    logger.debug('Modular starting ...')
    this.modules.forEach(module => {
      if (module.activator && module.activator.start) {
        module.activator.start(this, module)
      }
    })
    logger.debug('Modular started')
  }

  /**
   * 获取日志记录
   */
  getLogs() {
    return Object.freeze(this.logs) as LogInfo[]
  }

  /**
   * 记录日志
   * @param info 日志信息对象
   */
  private log(info: LogInfo) {
    this.logs.push(info)
    logger.error(info)
  }

  /**
   * 创建模块名称映射集合
   * @param modules 模块配置对象集合
   */
  private createNameMapping(modules: ModuleConfig[]) {
    const nameMapping: { [index: string]: ModuleConfig } = {}
    modules.forEach(module => {
      if (module.name === undefined || module.name === '') {
        this.log(new LogInfo('E01', 'error', { m: module }))
        return
      }
      const name = module.name
      if (nameMapping[name]) {
        this.log(
          new LogInfo('E02', 'error', { m1: nameMapping[name], m2: module })
        )
        return
      }
      nameMapping[name] = module
    })
    return nameMapping
  }

  /**
   * 初始化
   */
  private init() {
    if (this.inited) {
      // 防止初始化两次
      this.log(new LogInfo('E00', 'error'))
      return
    }
    logger.debug('Modular init')
    const app = this.application
    let modules = this.modules

    // 建立 name 查询索引
    const nameMapping: {
      [index: string]: ModuleConfig
    } = this.createNameMapping(modules)
    // 解析依赖,模块排序
    const modulesLoader = new ModulesLoader()
    modules.forEach(module => {
      this.loadDepens(module, modulesLoader, nameMapping)
    })
    // 处理应用配置
    this.loadDepens(app, modulesLoader, nameMapping)

    modules = modulesLoader.getModules()

    // 组装扩展配置
    const points: { [index: string]: DefaultExtensionPoint } = {}
    const len = modules.length
    for (let i = 0; i < len; i++) {
      const module = modules[i]
      if (module.extensionPoints) {
        const ps = module.extensionPoints
        for (const name in ps) {
          if (points[name]) {
            this.log(new LogInfo('E05', 'error', { m: module, ep: name }))
          } else {
            const point = new DefaultExtensionPoint(ps[name], this)
            point.module = module.name
            points[name] = point
          }
        }
      }
      if (module.preprocessors) {
        const processors = module.preprocessors
        for (const name in processors) {
          if (points[name]) {
            points[name].addPreprocessors(processors[name])
          } else {
            this.log(new LogInfo('E06', 'error', { m: module, ep: name }))
          }
        }
      }
      if (module.extensions) {
        const ext = module.extensions
        for (const name in ext) {
          if (points[name]) {
            if (points[name].type === Type.Multiple && Array.isArray(ext[name])) {
              points[name].addExtension(module.name, ...ext[name])
            } else {
              points[name].addExtension(module.name, ext[name])
            }
          } else {
            this.log(new LogInfo('E06', 'error', { m: module, ep: name }))
          }
        }
      }
      modules[i] = Object.freeze(module)
    }
    this.application = Object.freeze(app) // 应用配置
    this.modules = Object.freeze(modules) as ModuleConfig[]
    this.extensionPoints = Object.freeze(points)
    this.inited = true
  }

  /**
   * 深度遍历加载 Modules
   * @param module 当前模块
   * @param modulesLoader 模块加载器
   * @param nameMapping 模块名称索引
   * @param cache 当前处理中的模块缓存
   */
  private loadDepens(
    module: ModuleConfig,
    modulesLoader: ModulesLoader,
    nameMapping: { [index: string]: ModuleConfig },
    cache: { [index: string]: ModuleConfig } = {}
  ) {
    if (module.name === undefined || module.name === '') {
      return false
    }
    if (modulesLoader.contains(module)) {
      return true
    }
    if (module.dependencies && module.dependencies.length) {
      module.dependencies = Object.freeze(module.dependencies) as string[]
      const ds = module.dependencies
      const dslen = ds.length
      for (let i = 0; i < dslen; i++) {
        const d = ds[i]
        if (nameMapping[d]) {
          if (cache[d]) {
            // 正在处理中的cache包含依赖项,说明出现循环依赖的情况,跳过
            return true
          }
          cache[d] = nameMapping[d]
          if (
            this.loadDepens(nameMapping[d], modulesLoader, nameMapping, cache)
          ) {
            delete cache[d]
            // 依赖项加载成功
            continue
          } else {
            this.log(new LogInfo('E03', 'error', { m1: module, m2: d }))
            return false
          }
        } else {
          this.log(new LogInfo('E04', 'error', { m1: module, m2: d }))
          return false
        }
      }
      // 依赖项全部加载成功
    }
    modulesLoader.add(module)
    return true
  }
}