Midway 开发指南

介绍

Midway 自 2013 年开始,基本保持着一年一个大版本的迭代速度进行更新,从 express 到 koa1/2,从未缺席。

如今,集团内外的 Node.js 大环境早已脱离原有的前后端分离体系,朝着全栈的方向大步迈进,有着欣喜,有着期待,对此,MidwayJs 团队不仅仅承担着引导,支撑集团 Node.js 应用的责任,也同时通过 Pandora.jsSandbox 等不同形式来让应用变的更加稳定,可靠。

在 2017 年,我们将集团内部的 Midway 5.3 升级到了基于 Koa2 的模型,全面支持了 async/await 的编码风格。

同年年中,我们开始计划将监控和数据采集能力进行抽象剥离,形成了全新 Pandora.js 工具,不仅仅服务于 Midway,也服务于全网,乃至外部所有的 Node.js 应用。

2018 年,MidwayJs 团队将基于 TypeScript,将 Midway6 在新的语言层面进行升级,让用户在开发体验上更近一步,对外部则是从第一个版本开始。

为什么选择 TypeScript ? 相信 这篇文章 会给你一些答案。

关于 Midway

Midway (中途岛) 品牌是淘宝技术部(前淘宝UED)前端部门研发的一款基于 Node.js 的全栈开发解决方案。它将搭配团队的其他产品,Pandora.js 和 Sandbox,将 Node.js 的开发体验朝着全新的场景发展,让用户在开发过程中享受到前所未有的愉悦感。

快速上手

安装 Node 环境

可以访问 Node.js 官网或者使用 nvm 等类似产品,不再赘述。

创建新应用

使用 midway-init 工具自动创建 midway 应用的目录结构:

$ npm i midway-init -g
$ midway-init

通过生成的 npm scripts 来驱动启动命令:

$ npm install
$ npm run dev

了解目录结构

midway 的目录和 eggjs 目录非常接近,但也有所区别,不同的地方在于:

  • ts 源码存放于 /src 目录下,编译后代码存放于 /dist
  • 以往的 app 等都迁移至 /src/app 下,作为 web 层
  • 传统的业务逻辑等,移动到其他目录,比如 lib/service
➜  midway6-test tree -I node_modules
.
├── README.md
├── README.zh-CN.md
├── dist                                ---- 编译后目录
├── logs                                ---- 本地日志目录
│   └── midway6-test                    ---- 日志应用名开头
│       ├── common-error.log            ---- 错误日志
│       ├── midway-agent.log            ---- agent 输出的日志
│       ├── midway-core.log             ---- 框架输出的日志
│       ├── midway-web.log              ---- koa 输出的日志
│       └── midway6-test-web.log
├── package.json
├── src                                 ---- 源码目录
│   ├── app                             ---- web 层目录
│   │   ├── controller                  ---- web 层 controller 目录
│   │   │   ├── home.ts
│   │   │   └── user.ts
│   │   ├── middleware (可选)            ---- web 层中间件目录
│   │   │   └── trace.ts
│   │   ├── public (可选)                ---- web 层静态文件目录,可以配置
│   │   ├── view (可选)
│   │   |   └── home.tpl                ---- web 层模板
│   ├── config
│   │   ├── config.default.ts
│   │   ├── config.local.ts
│   │   ├── config.prod.ts
│   │   ├── config.unittest.ts
│   │   └── plugin.ts
│   └── lib                             ---- 业务逻辑层目录,自由定义
│   │   └── service                     ---- 业务逻辑层,自由定义
│   │       └── user.ts
│   ├── interface.ts                    ---- 接口定义文件,自由定义
│   ├── app.ts                          ---- 应用扩展文件,可选
│   └── agent.ts                        ---- agent 扩展文件,可选
├── test
│   └── app
│       └── controller
│           └── home.test.ts
├── tsconfig.json
└── tslint.json

如上,由框架约定的目录,Midway 使用 EggJs 作为 Web 层容器,承载请求控制器和传统 MVC 层的工作,这一块由于受到限制,有着一定的目录约定:

  • src/app/router.ts 可选,用于配置 URL 路由规则,具体参见 Router
  • src/app/controller/** 用于解析用户的输入,处理后返回相应的结果,具体参见 Controller
  • src/app/middleware/** 可选,用于编写中间件,具体参见 Middleware
  • src/app/extend/** 可选,用于框架的扩展,具体参见框架扩展
  • src/config/config.{env}.ts 用于编写配置文件,具体参见配置
  • src/config/plugin.ts 用于配置需要加载的插件,具体参见插件
  • test/** 用于单元测试,具体参见单元测试
  • src/app.ts 和 agent.ts 用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js的作用参见Agent机制

而其他由于 Egg 插件的限制,可能有些目录也会有相应的约定,比如:

  • src/app/public/** 用于放置静态资源,可选,具体参见内置插件 egg-static
  • src/app/view/** 用于放置模板文件,可选,由模板插件约定,具体参见模板渲染

我们会发现常见的代码都会存放于 /src 目录下,由于 ts 的特殊性,在服务器上会通过打包构建为 *.js 文件存放于 /dist 目录。将源文件和编译后文件分开是我们最开始的初衷。

而除了 app 目录以外的其他目录,在 midway 体系下并没有严格的规定,大体可以按照逻辑分层,比如按照传统的 web, biz, service, manager, dao 等进行分层进行创建目录就非常不错。

TIP

由于 Midway 采用了自动扫描装配,依赖注入等特性,无需在特定的目录下受到限制,使得在全栈应用开发的时候,保持了不错的开发体验。

快速开发引导

想要快速上手 midway,除了需要了解一些基础的东西:

  • 虽然可以直接用 js 的语法书写,但是你最好了解 TypeScript,这里有个 快速介绍
  • 尽可能使用面向对象的思想来编码,它的经久不衰是有道理的,使用 class 机制能够方便的融入我们的新特性。
  • 了解 midway 的依赖注入体系,以及常用的装饰器,这里做了 依赖注入的介绍
  • 如果你在 midway 的文档中没有找到你想要的东西,记住可以去 Egg 的文档找找,或者 向我们提 Issue

和 Egg 体系相同的部分

这部分的内容和 Egg 体系基本是相同的,大体不同的是后缀的区别 *.ts,以及根目录(midway 的根目录在 src)。

运行环境

目前没有做特殊处理,完全一样,查看运行环境文档

配置

框架支持根据环境来加载配置,定义多个环境的配置文件,唯一不同的是后缀的区别,具体环境请查看运行环境配置

src/config
|- config.default.ts
|- config.prod.ts
|- config.unittest.ts
`- config.local.ts

Web 中间件

除了目录在 src/app/middleware 以及后缀名为 *.ts ,其余完全一样,查看中间件文档

Router 路由

src/app/router.ts 文件依旧可用,推荐使用 midway 体系的 路由装饰器,egg 的路由文档在这里

框架扩展

针对框架自身的扩展点,依旧保留可用,目录变为 src/app/*.ts,文档查看 框架扩展

启动自定义

启动自定义依旧保留可用,目录变为 src/app.ts,文档查看 启动自定义

如果想在 app.ts 中调用 IoC 中的对象,可以通过以下方法。

// app.js
module.exports = app => {
  app.beforeStart(async () => {

    // 从全局作用域拿单例对象
    const obj = await app.applicationContext.getAsync('xxx');

    // 从请求作用域拿对象
    const ctx = app.createAnonymousContext();
    const obj = await ctx.requestContext.getAsync('xxx');

  });
};

路由和控制器

midway 使用 koa-router 作为路由的承载者,同时在 ts 的语法上做了一些简化,我们将路由和控制器放在了一起,使用装饰器来标注路由。

由于 midway 采用了 IoC 自扫描机制,使得在一定程度上弱化了目录结构约定,通过装饰器的机制,可以非常方便的进行解耦,按业务逻辑拆分等。

现在可以在任意目录下创建 controller,不再限定 app/controller 目录,同理,其他装饰器也不限定。

现在可以做到比如 src/web/controller 下放 controller,也可以按业务维度分,比如 user 目录,包含跟用户相关所有的 controller/service/dao 等,对微服务或者 serverless 比较友好。

路由装饰器

在新的 ts 体系中,我们的控制器目录为 app/controller ,我们在其中编写 *.ts 文件。例如下面的 userController.ts ,我们提供了一个获取用户的接口。

import { provide, controller, inject, get } from 'midway';

@provide()
@controller('/user')
export class UserController {

  @inject('userService')
  service: IUserService;

  @get('/:id')
  async getUser(ctx): Promise<void> {
    const id: number = ctx.params.id;
    const user: IUserResult = await this.service.getUser({id});
    ctx.body = {success: true, message: 'OK', data: user};
  }
}

我们创建了 @controller 装饰器用来定义这个类为 Controller,同时,提供了方法装饰器用于标注请求的类型。

小贴士

便于大家理解,@controller 的参数为字符串 pattern,我们会将这个值传入 router.prefix(prefix) 的参数中。

midway 针对 web 请求,提供了和 koa-router 对应的方法装饰器,列表如下。

  • @get
  • @post
  • @del
  • @put
  • @patch
  • @options
  • @head
  • @all

这几个装饰器用于修饰不同的异步方法,同时对应到了 koa-router 的相应的方法。和原有提供的控制器一样,每个控制器都为异步方法,参数为 koa 上下文。

@get('/:id')
async getUser(ctx): Promise<void> {
    // TODO ctx...
}

路由绑定

在以往框架的写法中,提供的 app/router 文件,虽然可以直接使用,但是由于控制器被 IoC 管控的关系,会有一些区别。

和以往的写法不同的是,我们需要从容器中拿到对应的控制器实例,并绑定到路由的 pattern 上

假如我们有一个控制器,同时没有提供 @controller 装饰器,表明他不是一个控制器,在应用初始化时不会自动绑定到某个路由上,但是由于有 @provide 装饰器,他会被 IoC 容器所加载。

// app/controller/api.ts

@provide()
export class BaseApi {
  async index(ctx) {
    ctx.body = 'index';
  }
}

假如我们希望这个控制器可以被外部的路由使用。

// app/router.ts

module.exports = function(app) {
  app.get('/api/index', app.generateController('baseApi.index'));
};

midway 扩展了一个 app.generateController 的方法来简化绑定的这个步骤,参数为 ClassName.methodName 的字符串形式。

路由优先级

在单页应用下,经常会出现 /* 这种路由,在原本的路由文件中,我们可以通过调整代码的顺序,来使的路由的匹配顺序发生变化。而由于使用了装饰器的关系,在新的体系无法控制文件扫描和加载顺序,这就使得路由匹配的顺序不可控。

midway 提供了 @priority(priority: number) 装饰器,用于修饰 class,定义路由的优先级,默认的路由优先级为 0,可以设置负数让优先级降低。

@provide()
@priority(-1)
@controller('/')
export class HomeController {

  @get('/hello')
  async index(ctx) {
    ctx.body = 'hello';
  }

  @get('/*')
  async all(ctx) {
    ctx.body = 'world';
  }
}

路由中间件

有时候我们会有在特定路由上加载中间件的需求,在之前的版本只能通过定义 router.ts 文件来解决部分需求,而在新版本中,我们扩展了装饰器的能力,使之可以在特定场景下增加 web 中间件。

现在可以提供一个 middleware(任意目录),比如 src/app/middleware/api.ts

import { Middleware, WebMiddleware, provide } from 'midway';

@provide()
export class ApiMiddleware implements WebMiddleware {

  @config('hello')
  helloConfig;

  resolve(): Middleware {
    return async (ctx, next) => {
      ctx.api = '222' + this.helloConfig.b;
      await next();
    };
  }

}

由于是 class,依旧可以使用 inject/plugin/config 等装饰器修饰。

TIP

推荐使用 WebMiddleware 接口来规范你的 web 中间件。

@provide()
@controller('/', {middleware: ['homeMiddleware']})
export class My {

  @inject()
  ctx;

  @get('/', {middleware: ['apiMiddleware']})
  async index() {
    this.ctx.body = this.ctx.home + this.ctx.api;
  }
}

@controller@get/post 等路由装饰器上都提供了 middleware 参数。

这里的 middleware 参数是一个数组,可以传多个字符串或者 koa middleware,如果是字符串,会从 IoC 容器中获取对应的 WebMiddleware 接口实例的 resolve 方法的结果。

也可以直接传递 koa middleware

const mw: Middleware = async (ctx, next) => {
  ctx.home = '4444';
  await next();
};

const newMiddleware = (data): Middleware => {
  return async (ctx, next) => {
    ctx.api = data;
    await next();
  };
};

@provide()
@controller('/', {middleware: ['homeMiddleware', mw]})
export class My {

  @inject()
  ctx;

  @get('/api', {middleware: ['apiMiddleware', newMiddleware('5555')]})
  async index() {
    this.ctx.body = this.ctx.home + this.ctx.api;
  }
}

TIP

这种方式只用于某个路由下的中间件,如果你希望使用全局中间件,那么请依旧使用 egg 的那种形式。

中间件注入的特殊性

由于中间件在生命周期的特殊性,会在应用请求前就被加载(绑定)到路由上,所以无法和上下文关联。

中间件类固定为单例(Singleton),所有注入的内容都为单例,包括但不限于 @config/@logger/@plugin 等。

这意味着你可以注入一个 service,但是这个 service 中无法注入 ctx 属性。

这个时候,你必须在 resolve 方法中,通过调用 ctx.requestContext.getAsync('xxx') 的方式来创建请求作用域实例,和上下文绑定。

@provide()
export class ApiMiddleware implements WebMiddleware {

  @inject()
  myService;  // 由于中间件实例属于单例,这个实例即使注入也无法获取到 ctx

  resolve(): Middleware {
    return async (ctx, next) => {
      // 必须通过从请求作用域中获取对象的方式,来绑定上下文
      ctx.service = await ctx.requestContext.getAsync('myService');
      await next();
    };
  }

}

一个方法挂载多个路由

新版本实现了在同一方法上可以挂载多个路由的能力。

@provide()
@controller('/', {middleware: ['homeMiddleware']})
export class My {

  @inject()
  ctx;

  @get('/', {middleware: ['apiMiddleware']})
  @post('/api/data')
  async index() {
    this.ctx.body = this.ctx.home + (this.ctx.api || '');
  }
}

这样请求进来, post 和 get 拿到的结果是不一样的(get请求挂载了额外的中间件)。

框架增强注入

midway 默认使用 injection 这个包来做依赖注入,虽然 @inject 装饰器能满足大多数业务的需求,但是对于框架来说,还有需要扩展和使用的地方,比如插件,配置等等。

注入插件

midway 除了支持 eggjs 原本的 app.xx 的插件用法,为了和框架解耦,同时,也可以通过 @plugin 装饰器来注入插件。

我们以 egg-jwt 插件为例,这个插件提供了 app.jwt 对象,而 @plugin 装饰器,则是类似于直接从 app 对象上拿属性。

比如 @plugin('jwt'),其实就是 app['jwt'],这样的写法,就可以和 app 对象进行解耦。

import { provide, plugin } from 'midway';

@provide()
export class BaseService {

  @plugin()
  jwt;

}

注入配置

在 midway 中不同环境的 config 都会挂载到 app.config 中,但是不是所有的业务逻辑都会依赖 app 对象,所以我们构造了 @config 装饰器来获取配置对象。

假如 config.default.ts 中有一些代码。

export const hello = 1;
import { provide, config } from 'midway';

@provide()
export class BaseService {

  @config('hello')
  config;   // 1

}

通过这样,我们可以把 config 中的值直接注入到业务逻辑中。

注册定时任务

midway 的定时任务是基于 egg 定时任务提供了更多 typescript 以及装饰器方面的支持。定时任务可以存放在任意目录,例如 src/schedule 目录下,可以配置定时任务的属性和要执行的方法。例如:

// src/schedule/hello.ts
import { provide, schedule, CommonSchedule } from 'midway';

@provide()
@schedule({
  interval: 2333, // 2.333s 间隔
  type: 'worker', // 指定某一个 worker 执行
})
export class HelloCron implements CommonSchedule {

  // 定时执行的具体任务
  async exec(ctx) {
    ctx.logger.info(process.pid, 'hello');
  }
}

TIP

推荐使用 CommonSchedule 接口来规范你的计划任务类。

注入日志对象

在原有逻辑中,日志对象也都挂载在 app.loggers 中,通过在 config 中配置的 key 来生成不同的日志实例对象,比如插件的日志,链路的日志等。

比如自定义一个日志 myLogger,这个时候,日志的 key 则为 myLogger

module.exports = appInfo => {
  return {
    customLogger: {
      myLogger: {
        file: path.join(appInfo.root, 'logs/xx.log'),
      },
    },
  };
};

这个时候可以用 @logger 来获取日志实例。

import { provide, logger } from 'midway';

@provide()
export class BaseService {

  @logger('myLogger')
  logger;

}

请求作用域中的日志

midway 在新版本中默认对所有对象开启了请求作用域,处于该作用域下的对象,都会包含一个默认的日志对象。

TIP

该 logger 对象是在请求链路开始就埋入到 IoC 容器中,所以可以通过 @inject 可以获取该对象, key 就为 logger,如果和属性同名则可以不填。

@provide()
export class BaseService {

  @inject()
  logger;

  // 也可以直接传入 key
  // @inject('logger')
  // logger;


}

框架扩展方法

抛开 eggjs 对 koa 的 application/context/request/response 的扩展点,midway 在 IoC 方面也做了一些扩展。

Application 扩展

具体接口见 API 文档

baseDir

由于 typescript 的关系,midway 的 app.baseDir 在开发时期指向了 /src 目录,而在构建之后部署阶段指向了 /dist 目录。

appDir

针对 baseDir 修改的情况,我们引入了一个新的 app.appDir 属性,用于指向应用根目录。

applicationContext

app.applicationContext 用于全局作用域的 IoC 容器,所有的单例对象都存放于该属性,可以从中获取到单例对象。

await app.applicationContext.getAsync('xxx')

pluginContext

插件容器,用于存在现有的所有挂载在 app 上的插件实例。

await app.pluginContext.getAsync('插件名')

Context 扩展

requestContext

针对请求作用域的情况,我们在 context 对象上扩展了一个 requestContext 属性。

applicationContext 相同,也是 IoC 容器,用于存放一次请求链路上的对象,当请求结束后,该容器会被清空。

await ctx.requestContext.getAsync('xxx')

应用测试

经过大量的实践,我们沉淀出了一套标准的测试工具集。

测试目录结构

我们约定 test 目录为存放所有测试脚本的目录,测试所使用到的 fixtures 和相关辅助脚本都应该放在此目录下。

测试脚本文件统一按 ${filename}.test.ts 命名,必须以 .test.ts 作为文件后缀。

一个应用的测试目录示例:

test
├── controller
│   └── home.test.ts
├── hello.test.ts
└── service
    └── user.test.ts

测试命令

在脚手架中,我们已经将常见的命令进行内置,可能略微有些不同,大致代码如下。

{
  "scripts": {
    "test": "midway-bin test --ts",
    "cov": "midway-bin cov --ts"
  }
}

然后就可以按标准的 npm test来运行测试了。

npm test

> unittest-example@ test /Users/harry/midwayj/examples/unittest
> midway-bin test --ts

  test/hello.test.ts
    ✓ should work

  1 passing (10ms)

开始测试

在测试运行之前,我们首先要创建应用的一个 app 实例, 通过它来访问需要被测试的 Controller、Middleware、Service 等应用层代码。

通过 midway-mock,结合 Mocha 的 before 钩子就可以便捷地创建出一个 app 实例。

// test/controller/home.test.js
import { mm } from 'midway-mock';

describe('test/controller/home.test.ts', () => {
  let app;
  before(() => {
    // 创建当前应用的 app 实例
    app = mm.app();
    // 等待 app 启动成功,才能执行测试用例
    return app.ready();
  });
});

这样我们就拿到了一个 app 的引用,接下来所有测试用例都会基于这个 app 进行。 更多关于创建 app 的信息请查看 mm.app(options) 文档。

每一个测试文件都需要这样创建一个 app 实例非常冗余,因此 midway-mock 提供了一个 bootstrap 文件,可以直接从它上面拿到我们所常用的实例:

// test/controller/home.test.ts
import { app, mock, assert } from 'midway-mock/bootstrap';

describe('test/controller/home.test.ts', () => {
  // test cases
});

Controller 测试

我们可以通过 app.httpRequest() SuperTest 发起一个真实的 HTTP 请求来进行测试。app.httpRequest() 是 midway-mock 封装的 SuperTest 请求实例。

例如我们要给 app/controller/home.ts

import { controller, get, provide } from 'midway';

@provide()
@controller('/')
export class HomeController {
  @get('/')
  async index(ctx) {
    ctx.body = `Welcome to midwayjs!`;
  }
}

写一个完整的单元测试,它的测试代码 test/controller/home.test.ts 如下:

import { app, assert } from 'midway-mock/bootstrap';

describe('test/controller/home.test.ts', () => {
  it('should GET /', () => {
    // 对 app 发起 `GET /` 请求
    return app
      .httpRequest()
      .get('/')
      .expect('Welcome to midwayjs!') // 期望 body 是 hello world
      .expect(200); // 期望返回 status 200
  });
});

通过基于 SuperTest 的 app.httpRequest() 可以轻松发起 GET、POST、PUT 等 HTTP 请求,并且它有非常丰富的请求数据构造接口,请查看 SuperTest 文档。

Service 测试

由于 midway 提倡使用 IoC 的方式来定义 service,所以编码与测试都与 eggjs 有明显的区别。

例如 src/lib/service/user.ts:

import { provide } from 'midway';
import { IUserService, IUserOptions, IUserResult } from '../../interface';

// 装载 service 到 IoC 容器
@provide('userService')
export class UserService implements IUserService {
  async getUser(options: IUserOptions): Promise<IUserResult> {
    return new Promise<IUserResult>((resolve) => {
      // 10ms 之后返回用户数据
      setTimeout(() => {
        resolve({
          id: options.id,
          username: 'mockedName',
          phone: '12345678901',
          email: 'xxx.xxx@xxx.com',
        });
      }, 10);
    });
  }
}

编写单元测试 test/service/user.test.ts

import { app, assert } from 'midway-mock/bootstrap';
import { IUserService } from '../../src/interface';

describe('test/service/user.test.ts', () => {
  it('#getUser', async () => {
    // 取出 userService
    const user = await app.applicationContext.getAsync<IUserService>('userService');
    const data = await user.getUser({ id: 1 });
    assert(data.id === 1);
    assert(data.username === 'mockedName');
  });
});

app.applicationContext 是 IoC 容器的应用上下文, 通过它可以异步取出注入的 service,并使用 service 进行测试。完整 demo 可以参见 midway-test-demo

使用 Jest

Midway 在单元测试框架上,不仅支持 Mocha,也对 Jest 做了相应支持。具体使用步骤如下:

1.在项目根目录安装以下依赖:

$ npm install jest @types/jest ts-jest -D
  1. 修改 tsconfig.json,避免 Mocha 与 Jest 的类型定义文件冲突
{
  "compilerOptions": {
    "types": ["jest"]
  }
}
  1. 在项目根目录下新增 jest.config.js 文件,内容如下:
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'midway-bin/jest/env.js'
};
  1. 配置 npm scripts,修改 test 命令
{
  "scripts": {
    "test": "jest"
  }
}
  1. 运行 npm scripts,即可使用 Jest 完成单测
npm run test

TIP

我们也提供了可运行 demo 供大家参考:demo-unittest-jest

部署

构建打包

由于 TypeScript 的特殊性,本地开发可以有 ts-node 等类似的工具进行开发,而在服务器端运行的时候,我们希望可以通过 js 来运行,这中间就需要编译工具。

幸好 TypeScript 官方提供了 tsc 工具来帮助这个过程,而编译时会自动调用 tsconfig.json 来做一些编译时处理,midway 默认提供了一份该文件,用户也可以进行自定义。

同时,在脚手架中,我们提供了 build 命令帮助用户更好的生成文件。

TIP

推荐在发布前本地进行 build,并通过 npm run start_build 进行启动尝试,减少服务器端构建错误。

"start_build": "npm run build && NODE_ENV=development midway-bin dev"

通过 start_build 启动的应用,将会自动本地编译,然后启动 dist 目录中的文件。

如果有一些自定义的文件需要在打包时拷贝,可以参考 自定义打包

通过内置的启动文件

midway 提供了一个内置的 server.js 来作为应用的启动入口,在大部分情况下,可以通过直接 require 该文件来进行启动。

比如使用 pm2 的场景下。

// xxx.js
require('midway/server');

或者使用我们 pandora 的场景下,会生成 procfile.js 文件,内容如下。

'use strict';

module.exports = pandora => {
  pandora
    .fork('[your app name]', require.resolve('midway/server'));
};

通过内置的 server 文件,可以自动启动应用。

egg-scripts 方式

在以往的 egg 应用中,egg-scripts 也可以直接启动,但是不支持 启动参数传递

具体的文档请查看 使用 egg-scripts 应用部署

启动参数传递

我们设计了一个机制,在 package.json 中配置服务器设置,只有依赖了 midway/server 文件才可以使用。

支持的参数见 启动参数,同时,midway 框架额外增加了几个参数。

  • typescript {boolean} 如果为true,则会开启 ts 模式,加载 src 或者 dist 目录,默认内部会进行判断,无需手动处理
  • srcDir {string} 源码路径,默认为 src
  • targetDir {string} 编译后路径,默认为 dist
{
  "midway-server-options": {
    "workers": 1,
    "port": 3000
  }
}

如果觉得不足,还可以使用 js 或者 json 文件进行定义。

{
  "midway-server-options": "./server.json"    // xxx.js
}

// in json
{
  "workers": 1
}

// in js
module.exports = {
  workers: 1
}

其他一些情况

windows 支持

由于在 windows 上开发体验不是特别友好,以及一些库缺乏支持,在大部分情况下,我们优先推荐在 mac/linux 下开发 Node.js 应用。

需要注意的是,由于 windows 对设置环境变量的同步,默认生成的脚手架可能需要调整,主要是环境变量的部分。

比如开发命令,在设置环境的时候需要使用 set 以及中间需要增加 && 以连接命令。

{
  "dev": "set NODE_ENV=local && midway-bin dev --ts"
}