启动和部署
Midway 提供了一个轻量的启动器,用于启动你的应用。我们为应用提供了多种部署模式,你既可以将应用按照传统的样子,部署到任意的服务器上(比如自己购买的服务器),也可以将应用构建为一个 Serverless 应用,Midway 提供跨多云的部署方式。
本地开发
这里列举的主要是本地使用 dev
命令开发的方式,有两种。
快速启动单个服务
在本地研发时,Midway 在 package.json
中提供了一个 dev
命令启动框架,比如:
- 使用 mwtsc
- 使用 @midwayjs/cli
{
"scripts": {
"dev": "mwtsc --watch --run @midwayjs/mock/app.js",
}
}
这是一个最精简的命令,他有如下特性:
- 1、使用
mwtsc
工具构建代码,成功后通过@midwayjs/mock
包中的app.js
文件读取构建后的代码启动项目 - 2、使用内置的 API(@midwayjs/core 的
initializeGlobalApplicationContext
)创建一个服务,不经过bootstrap.js
- 3、单进程运行
{
"script": {
"dev": "midway-bin dev --ts"
}
}
这是一个最精简的命令,他有如下特性:
- 1、使用
--ts
指定 TypeScript(ts-node)环境启动 - 2、使用内置的 API(@midwayjs/core 的
initializeGlobalApplicationContext
)创建一个服务,不经过bootstrap.js
- 3、单进程运行
在命令行运行下面的命令即可执行。
$ npm run dev
指定入口启动服务
由于本地的 dev 命令普通情况下和 bootstrap.js
启动文件初始化参数不同,有些用户担心本地开发和线上开发不一致,比如测试链路等。
这个时候我们可以直接传递一个入口文件给 dev
命令,直接使用入口文件启动服务。
- 使用 mwtsc
- 使用 @midwayjs/cli
{
"scripts": {
"dev": "mwtsc --watch --run bootstrap.js",
},
}
{
"script": {
"dev": "midway-bin dev --ts --entryFile=bootstrap.js"
}
}
部署到服务器
部署后和本地开发的区别
在部署后,有些地方和本地开发有所区别。
1、node 环境的变化
最大的不同是,服务器部署后,会直接使用 node 来启动项目。
- 如果使用了
mwtsc
开发项目,差距不是很大 - 如果使用了
@midwayjs/cli
,将不会使用ts-node
来启动项目,这意味着不再读取*.ts
文件
2、加载目录的变化
服务器部署后,只会加载构建后的 dist
目录,而本地开发则是加载 src
目录。
本地 | 服务器 | |
---|---|---|
appDir | 项目根目录 | 项目根目录 |
baseDir | 项目根目录下的 src 目录 | 项目根目录下的 dist 目录 |
3、环境的变化
服务器环境,一般使用 NODE_ENV=production
,很多库都会在这个环境下提供性能更好的方式,例如启用缓存,报错处理等。
4、日志文件
一般服务器环境,日志不 会打印到项目的 logs 目录,而是其他不会受到项目更新影响的目录,比如 home/admin/logs
,这样固定的目录,也方便其他工具采集日志。
部署的流程
整个部署分为几个部分,由于 Midway 是 TypeScript 编写,比传统 JavaScript 代码增加了一个构建的步骤,整个部署的过程如下。
由于部署和平台、环境非常相关,下面我们都将以 Linux 来演示,其他平台可以视情况参考。
编译代码和安装依赖
由于 Midway 项目是 TypeScript 编写,在部署前,我们先进行编译。在示例中,我们预先写好了构建脚本,执行 npm run build
即可,如果没有,在 package.json
中添加下面的 build
命令即可。
- 使用 mwtsc
- 使用 @midwayjs/cli
{
"scripts": {
"build": "mwtsc --cleanOutDir",
},
}
{
"scripts": {
"build": "midway-bin build -c"
},
}
虽然不是必须,但是推荐大家先执行测试和 lint。
一般来说,部署构建的环境和本地开发的环境是两套,我们推荐在一个干净的环境中构建你的应用。
下面的代码,是一个示例脚本,你可以保存为 build.sh
执行。
## 服务器构建(已经下载好代码)
$ npm install # 安装开发期依赖
$ npm run build # 构建项目
$ npm prune --production # 移除开发依赖
## 本地构建(已经安装好 dev 依赖)
$ npm run build
$ npm prune --production # 移除开发依赖
一般安装依赖会指定 NODE_ENV=production
或 npm install --production
,在构建正式包的时候只安装 dependencies 的依赖。因为 devDependencies 中的模块过大而且在生产环境不会使用,安装后也可能遇到未知问题。
执行完构建后,会出现 Midway 构建产物 dist
目录。
➜ my_midway_app tree
.
├── src
├── dist # Midway 构建产物目录
├── node_modules # Node.js 依赖包目录
├── test
├── bootstrap.js # 部署启动文件
├── package.json
└── tsconfig.json
构建时别名(alias path)的问题
别名是前端工具带来的习惯,而非 Node.js 的标准能力,目前使用有两种可选的方式:
打包压缩
构建完成后,你可以简单的打包压缩,上传到待发布的环境。
一般来说服务器运行必须包含的文件或者目录有 package.json
,bootstrap.js
,dist
,node_modules
。
上传和解压
有很多种方式可以上传到服务器,比如常见的 ssh/FTP/git
等。也可以使用 OSS 等在线服务进行中转。
启动项目
Midway 构建出来的项目是单进程的,不管是采用 fork
模式还是 cluster
模式,单进程的代码总是很容易的兼容到不同的体系中,因此非常容易被社区现有的 pm2/forever 等工具所加载,
我们这里以 pm2 来演示如何部署。
项目一般都需要一个入口文件,比如,我们在根目录创建一个 bootstrap.js
作为我们的部 署文件。
➜ my_midway_app tree
.
├── src
├── dist # Midway 构建产物目录
├── test
├── bootstrap.js # 部署启动文件
├── package.json
└── tsconfig.json
Midway 提供了一个简单方式以满足不同场景的启动方式,只需要安装我们提供的 @midwayjs/bootstrap
模块(默认已自带)。
$ npm install @midwayjs/bootstrap --save
然后在入口文件中写入代码,注意,这里的代码使用的是 JavaScript
。
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap.run();
虽然启动文件的代码很简单,但是我们依旧需要这个文件,在后续的链路追踪等场景中需要用到。
注意,这里不含 http 的启动端口,如果你需要,可以参考文档 修改。
这 个时候,你已经可以直接使用 NODE_ENV=production node bootstrap.js
来启动代码了,也可以使用 pm2 来执行启动。
我们一般推荐使用工具来启动 Node.js 项目,下面有一些文档可以进阶阅读。
启动参数
在大多数情况下,不太需要在 Bootstrap 里配置参数,但是依旧有一些可配置的启动参数选项,通过 configure
方法传入。
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap
.configure({
imports: [/*...*/]
})
.run();
属性 | 类型 | 描述 |
---|---|---|
appDir | string | 可选,项目根目录,默认为 process.cwd() |
baseDir | string | 可选,项目代码目录,研发时为 src ,部署时为 dist |
imports | Component[] | 可选,显式的组件引用 |
moduleDetector | 'file' | IFileDetector | false | 可选,使用的模块加载方式,默认为 file ,使用依赖注入本地文件扫描方式,可以显式指定一个扫描器,也可以关闭扫描 |
logger | Boolean | ILogger | 可选,bootstrap 中使用的 logger,默认为 consoleLogger |
ignore | string[] | 可选,依赖注入容器扫描忽略的路径,moduleDetector 为 false 时无效 |
globalConfig | Array<{ [environmentName: string]: Record<string, any> }> | Record<string, any> | 可选,全局传入的配置,如果传入对象,则直接以对象形式合并到当前的配置中,如果希望传入不同环境的配置,那么,以数组形式传入,结构和 importConfigs 一致。 |
示例,传入全局配置(对象)
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap
.configure({
globalConfig: {
customKey: 'abc'
}
})
.run();
示例,传入分环境的配置
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap
.configure({
globalConfig: [{
default: {/*...*/},
unittest: {/*...*/}
}]
})
.run();
使用 Docker 部署
编写 Dockerfile,构建镜像
步骤一:在当前目录下新增Dockerfile
FROM node:18
WORKDIR /app
ENV TZ="Asia/Shanghai"
COPY . .
# 如果各公司有自己的私有源,可以替换registry地址
RUN npm install --registry=https://registry.npm.taobao.org
RUN npm run build
# 如果端口更换,这边可以更新一下
EXPOSE 7001
CMD ["npm", "run", "start"]
步骤二: 新增 .dockerignore
文件(类似 git 的 ignore 文件),可以把 .gitignore
的内容拷贝到 .dockerignore
里面
步骤三:当使用 pm2 部署时,请将命令修改为 pm2-runtime start
,pm2 行为请参考 pm2 容器部署说明。
步骤四:构建 docker 镜像
$ docker build -t helloworld .
步骤五:运行 docker 镜像
$ docker run -itd -P helloworld
运行效果如下:
然后大写的 -P
由于给我们默认分配了一个端口,所以我们访问可以访问 32791
端口(这个 -P
是随机分配,我们也可以使用 -p 7001:7001
指定特定端口)
后续:发布 docker 镜像
优化
我们看到前面我们打出来的镜像有1个多G,可优化的地方:
- 1、我们可以采用更精简的 docker image 的基础镜像:例如 node:18-alpine,
- 2、其中的源码最终也打在了镜像中,其实这块我们可以不需要。
我们可以同时结合 docker 的 multistage 功能来做一些优化,这个功能请注意要在 Docker 17.05
版本之后才能使用。
FROM node:18 AS build
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
# 把源代码复制过去, 以便报错能报对行
COPY --from=build /app/src ./src
COPY --from=build /app/bootstrap.js ./
COPY --from=build /app/package.json ./
RUN apk add --no-cache tzdata
ENV TZ="Asia/Shanghai"
RUN npm install --production
# 如果端口更换,这边可以更新一下
EXPOSE 7001
CMD ["npm", "run", "start"]