midway.js 资料初探

192 阅读2分钟

概要

Midway 是阿里巴巴 - 淘宝前端架构团队,基于渐进式理念研发的 Node.js 框架,通过自研的依赖注入容器,搭配各种上层模块,组合出适用于不同场景的解决方案。 Midway 基于 TypeScript 开发,结合了面向对象(OOP + Class + IoC)与函数式(FP + Function + Hooks)两种编程范式。

细节

编程范式

  • Spring IOC模式:midway1.0/2.0范式,基于Typescript的注解,以class+依赖注入方式使用;
  • 函数式:Function + Hook,前端可在源码中直接调用后端函数,打包器会自动转化为HTTP调用,缺点是目前后端接口的默认范式无法自定义,如果后端接口还要提供给其他服务使用,则不适合用此方式。
  • Faas模式:兼容了3个云服务商的函数计算,阿里云、腾讯云、AWS。

前端Hooks

为了支持全栈开发,创建了hooks项目,但已不再维护,examples可直接使用:github.com/midwayjs/ho…

bootstrap

midway的启动需要准备好container管理系统。

// packages/bootstrap/src/bootstrap.ts

export class BootstrapStarter 

// packages/core/src/setup.ts

export async function initializeGlobalApplicationContext
{
  // bind a class/value as a dependency, then use get() to get it
  applicationContext.bindClass(MidwayFrameworkService);

  await applicationContext.getAsync(MidwayFrameworkService, [
  applicationContext,
  globalOptions,
  ]);

  await applicationContext.getAsync(MidwayAspectService, [applicationContext]);

  await applicationContext.getAsync(MidwayDecoratorService, [
    applicationContext,
  ]);

  await applicationContext.getAsync(MidwayFrameworkService, [
    applicationContext,
    globalOptions,
  ]);

  await applicationContext.getAsync(MidwayLifeCycleService, [
    applicationContext,
  ]);

}

// bind
bindClass(exports, options?: Partial<IObjectDefinition>)
protected bindModule(module: any, options: Partial<IObjectDefinition>)

{
    if (definition) {
      this.registry.registerDefinition(definition.id, definition);
    }
}

// get
get(identifier: any, args?: any[], objectContext?: ObjectContext): any
async getAsync(
    identifier: any,
    args?: any[],
    objectContext?: ObjectContext
  ): Promise<any>

// create
create(opt: IManagedResolverFactoryCreateOptions): any
async createAsync(opt: IManagedResolverFactoryCreateOptions): Promise<any>


decorator

  • @Provide 暴露class
  • @Inject 注入class
// packages/decorator/src/decoratorManager.ts

export function Provide(identifier?: ObjectIdentifier) {
  return function (target: any) {
    return saveProviderId(identifier, target);
  };
}

export function Inject(identifier?: ObjectIdentifier) {
  return function (target: any, targetKey: string): void {
    savePropertyInject({ target, targetKey, identifier });
  };
}

// attach data to class or property
attachMetadata(
    decoratorNameKey: ObjectIdentifier,
    data,
    target,
    propertyName?: string,
    groupBy?: string,
    groupMode?: GroupModeType
  )

// write decorator into metadata, relies on Reflect(reflect-metadata)
static attachMetadata(
    metaKey: string,
    target: any,
    dataKey: string,
    data: any,
    groupBy?: string,
    groupMode: GroupModeType = 'one'
  ) 

@midway/hooks

api

export function Api<
  Operators extends Operator<any>[],
  Handler extends AsyncFunction
>(
  ...args: [...operators: Operators, handler: Handler]
): ApiRunner<
  ExtractInputType<Operators> extends void[]
    ? void
    : ArrayToObject<ExtractInputType<Operators>>,
  Handler
> {
    const metadataHelper: MetadataHelper = {
      getMetadata(key: any) {
        return Reflect.getMetadata(key, runner)
      },
      setMetadata(key: any, value: any) {
        return Reflect.defineMetadata(key, value, runner)
      },
    }
    for (const operator of operators) {
      operator.metadata(metadataHelper)
    }

    const executors = operators
    .filter((operator) => typeof operator.execute === 'function')
    .map((operator) => operator.execute)

    async function runner(...args: any[]) {
      await compose(stack)(executeHelper)
    }
    Reflect.defineMetadata(USE_INPUT_METADATA, useInputMetadata, runner)
    return runner as any
}
export function Query<T extends Record<string, string>>(): Operator<{
  query: T
}> {
  return {
    name: HttpMetadata.QUERY,
    input: true,
    metadata({ setMetadata }) {
      setMetadata(HttpMetadata.QUERY, true)
    },
  }
}

rpc(client)

function createHttpMethodOperator(method: HttpMethod) {
  return (path?: string) => {
    return {
      name: method,
      metadata({ setMetadata }) {
        setMetadata<HttpTrigger>(OperatorType.Trigger, {
          type: HttpTriggerType,
          method,
          path,
          requestClient: {
            fetcher: 'http',
            client: '@midwayjs/rpc',
          },
        })
      },
    } as Operator<void>
  }
}

export const Get = createHttpMethodOperator(HttpMethod.GET)
// packages/hooks-bundler/src/index.ts
const plugin = createBundlerPlugin(new MidwayBundlerAdapter())
export const vite = plugin.vite

// packages/bundler/src/index.ts
export function createBundlerPlugin(adapter: AbstractBundlerAdapter)

docs

site/docs/aspect.md

总结

midway.js 开发时间较早,但是开发思想并不落后,先后支持了IOC模式和hook模式,但源码阅读起来还是有一些历史债的,3.0版本的TS代码达到了252631行。目前v3的发展方向个人感觉不是很清晰,可能3.0是集大成的最好版本了。

参考

midway.js 源码分析 - 启动逻辑

midway.js 源码分析 - 依赖注入