【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
项目地址:
https://gitcode.com/gh_mirrors/mi/midway
点击查看 免费下载
本篇以 Midway 3(Node.js Serverless 框架)为背景,系统讲解应用从本地开发到生产部署的完整链路:本地 dev 启动的两种方式与入口差异、服务器部署时构建产物与运行环境的区别、基于 pm2 的进程启动、Bootstrap 启动参数、Docker 多阶段镜像构建与 docker-compose 编排,以及面向 Serverless/私有化场景的单文件构建(ncc)与二进制文件(pkg)交付方案。读完本文,你将掌握 Midway 项目在不同目标环境下的可复制部署脚本、可配置的启动参数,以及部署失败时基于日志与进程状态的排查思路。
本地开发
Midway 提供了一套轻量启动器,既可以把应用按照传统方式部署到任意服务器(例如自购 VPS),也可以将应用构建为 Serverless 应用跨多云部署。在本地研发阶段,主要有两种使用 dev 命令启动的方式。
快速启动单个服务
本地研发时,Midway 在 package.json 中提供 dev 命令启动框架,可按项目脚手架分别采用两种写法。
使用 mwtsc:
{
"scripts": {
"dev": "mwtsc –watch –run @midwayjs/mock/app.js"
}
}
这个最精简的命令具备如下特性:
- 1、使用 mwtsc 工具构建代码,成功后通过 @midwayjs/mock 包中的 app.js 文件读取构建后的代码启动项目;
- 2、使用内置 API(@midwayjs/core 的 initializeGlobalApplicationContext)创建一个服务,不经过 bootstrap.js;
- 3、单进程运行。
使用 @midwayjs/cli:
{
"script": {
"dev": "midway-bin dev –ts"
}
}
特性如下:
- 1、使用 –ts 指定 TypeScript(ts-node)环境启动;
- 2、使用内置 API(@midwayjs/core 的 initializeGlobalApplicationContext)创建一个服务,不经过 bootstrap.js;
- 3、单进程运行。
在命令行执行 npm run dev 即可。
$ npm run dev
从仓库源码看,@midwayjs/mock/app.js 是 mock 包提供的默认启动脚本,它解析进程参数后调用 createApp 以 process.cwd() 作为 appDir、以 dist 目录作为 baseDir 创建应用,并在收到 SIGINT/SIGQUIT/SIGTERM 信号时执行 close(app) 优雅退出(见 packages/mock/app.js、packages/mock/src/creator.ts)。而 initializeGlobalApplicationContext 是 @midwayjs/core 的全局启动入口,负责初始化 logger、mock、framework、tracing、lifecycle 等一系列服务(见 packages/core/src/setup.ts)。
指定入口启动服务
由于本地 dev 命令默认情况下与 bootstrap.js 启动文件的初始化参数不同,有些用户担心本地开发与线上开发不一致(例如测试链路等)。此时可以直接给 dev 命令传递一个入口文件,直接使用入口文件启动服务。
使用 mwtsc:
{
"scripts": {
"dev": "mwtsc –watch –run bootstrap.js"
}
}
使用 @midwayjs/cli:
{
"script": {
"dev": "midway-bin dev –ts –entryFile=bootstrap.js"
}
}
这样本地开发就走与线上一致的 bootstrap.js 入口链路,最大程度消除本地与生产环境的启动差异。
部署到服务器
部署后和本地开发的区别
部署到服务器后,有几个环节与本地开发存在明显差异。
1、node 环境的变化
最大的不同是,服务器部署后直接使用 node 来启动项目。
- 如果使用 mwtsc 开发项目,差距不是很大;
- 如果使用 @midwayjs/cli,将不会使用 ts-node 启动项目,意味着不再读取 *.ts 文件。
2、加载目录的变化
服务器部署后,只会加载构建后的 dist 目录,而本地开发加载的是 src 目录。
| appDir | 项目根目录 | 项目根目录 |
| baseDir | 项目根目录下的 src 目录 | 项目根目录下的 dist 目录 |
这一逻辑在 @midwayjs/bootstrap 的 BootstrapStarter 中也有对应实现:appDir 默认取 process.cwd(),baseDir 若未显式指定,则根据是否 TypeScript 环境决定——ts 环境下为 src,非 ts 环境下为 dist(见 packages/bootstrap/src/bootstrap.ts)。
3、环境的变化
服务器环境一般使用 NODE_ENV=production,很多库会在该环境下提供性能更好的方式,例如启用缓存、报错处理等。
4、日志文件
一般服务器环境,日志不会打印到项目的 logs 目录,而是其他不会受到项目更新影响的目录,比如 home/admin/logs,这样固定的目录也方便其他工具采集日志。
部署的流程
整个部署分为几个部分。由于 Midway 是 TypeScript 编写,比传统 JavaScript 代码增加了一个构建步骤,整体部署流程为:编译代码 → 安装依赖 → 打包压缩 → 上传解压 → 启动项目。由于部署和平台、环境非常相关,下文均以 Linux 演示,其他平台可酌情参考。
编译代码和安装依赖
由于 Midway 项目是 TypeScript 编写,部署前需要先编译。示例中预先写好了构建脚本,执行 npm run build 即可;如果没有,在 package.json 中添加下面的 build 命令即可。
使用 mwtsc:
{
"scripts": {
"build": "mwtsc –cleanOutDir"
}
}
使用 @midwayjs/cli:
{
"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 的标准能力,目前有两种可选方式:
- 1、使用 Node.js 自带的子路径导入方案(subpath imports);
- 2、使用额外工具在编译时处理。
关于别名的处理细节,仓库文档 site/docs/faq/alias_path.md 有更完整的说明:本地 dev 阶段(ts-node)可以在 src/configuration.ts 顶部引入 tsconfig-paths/register 来 hook 模块路径解析;测试环境可以利用 Jest 配置中的 moduleNameMapper 替换加载模块;编译后则建议借助社区库在 ts 编译时做路径替换,否则部署后仍会输出带 @ 前缀的路径导致文件找不到。
打包压缩
构建完成后,可以简单地打包压缩,上传到待发布的环境。
注意:一般来说服务器运行必须包含的文件或目录有 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 的启动端口,如果需要,可以参考文档修改。Koa 框架默认在 config.default 中提供 7001 的默认端口参数,修改 koa.port 即可变更,例如改为 6001;单测环境因 supertest 需要,port 配置为 null;此外也可以通过 midway-bin dev –ts –port=6001 临时覆盖配置中的端口(见 site/versioned_docs/version-3.0.0/extensions/koa.md)。
此时,已经可以直接使用 NODE_ENV=production node bootstrap.js 启动代码,也可以使用 pm2 来执行启动。推荐使用工具来启动 Node.js 项目,进阶阅读:
- pm2 使用文档
- cfork 使用文档
从源码结构看,@midwayjs/bootstrap 的 Bootstrap 类在 run() 时会对 SIGINT/SIGQUIT/SIGTERM 信号与 exit 事件注册处理,并监听 uncaughtException 与 unhandledRejection;同时支持通过 MIDWAY_FORK_MODE 环境变量(cluster/thread)启用对应的事件总线,供多进程场景使用(见 packages/bootstrap/src/bootstrap.ts)。这解释了为什么生产环境推荐用 pm2 等进程管理工具承载——由工具负责守护、重启与集群管理,而 bootstrap.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();
从源码看,Bootstrap.configure() 除了透传上述配置给 BootstrapStarter 外,还会在显式指定 appDir 且与 process.cwd() 不一致时执行 process.chdir(appDir),以解决三方框架内部依赖 process.cwd() 查找 node_modules 的问题(见 packages/bootstrap/src/bootstrap.ts)。
使用 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 7001:7001 指定特定端口。
后续: 发布 docker 镜像时,可以推送构建好的镜像到 DockerHub,也可以推送到自建的镜像仓库(例如 Harbor),具体流程分别参考 Docker 与 Harbor 的官方文档。
优化
前面打出来的镜像有 1 个多 G,可优化的地方:
- 1、采用更精简的基础镜像,例如 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"]
当前示例的结果只有 207MB,相比原有的 1.26G 省了很多空间。其原理是:第一阶段 node:18 负责完整安装 dev 依赖并执行 npm run build 产出 dist;第二阶段 node:18-alpine 只拷贝构建产物、入口文件与 package.json,再执行 npm install –production 仅安装运行时依赖,从而剥离掉庞大的开发依赖与源码构建工具链。
结合 Docker-Compose 运行
在 docker 部署的基础上,还可以结合 docker-compose 配置项目依赖的服务,实现快速部署整个项目。下面以 midway 结合 redis 为例,使用 docker-compose 快速部署整个项目。
步骤一:编写 Dockerfile
按照上文使用 Docker 部署的方式编写 Dockerfile(见 site/versioned_docs/version-3.0.0/deployment.md 中的"编写 Dockerfile,构建镜像"小节)。
步骤二:编写 docker-compose.yml
新增 docker-compose.yml 文件,内容如下(此处模拟 midway 项目需要使用 redis):
# 项目的根目录,与Dockerfile文件同级
version: "3"
services:
web:
build: .
ports:
– "7001:7001"
links:
– redis
depends_on:
– redis
redis:
image: redis
步骤三:修改配置
修改 redis 的配置文件,内容如下(配置 redis 请参考 redis 组件):
// src/config/config.default.ts
export default {
// …
redis: {
client: {
port: 6379, // redis容器的端口
host: "redis", // 这里与docker-compose.yml文件中的redis服务名称一致
password: "", //默认没有密码,请自行修改为redis容器配置的密码
db: 0,
},
},
}
注意,redis.client.host 必须与 docker-compose.yml 中 redis 服务的名称保持一致,compose 网络会以服务名作为容器间通信的域名。
步骤四:构建
$ docker-compose build
步骤五:运行
$ docker-compose up -d
后续
更多关于 docker-compose 的用法,请参考 Docker Compose 官方文档。
单文件构建部署
在某些场景下,将项目构建为单文件,部署的文件可以更小,更容易分发部署,在一些场景下特别高效,如:
- Serverless 场景,单文件可以更快地部署;
- 私密场景,单文件可以更容易地做加密混淆。
Midway 从 v3 开始支持将项目构建为单文件。
不支持的情况有:
- egg 项目(@midwayjs/web);
- 入口处 importConfigs 使用路径形式引入配置的应用、组件;
- 未显式依赖的包,或者包里有基于约定的文件。
前置依赖
单文件构建有一些前置依赖需要安装:
## 用于生成入口
$ npm i @midwayjs/bundle-helper –save-dev
## 用于构建单文件
## 装到全局
$ npm i @vercel/ncc -g
## 或者装到项目(推荐)
$ npm i @vercel/ncc –save-dev
代码调整
有一些可能的调整,列举如下。
1、配置格式调整
必须将项目引入的配置调整为对象模式(见 site/versioned_docs/version-3.0.0/env_config.md)。Midway 的官方组件都已经调整为该模式,如果有自己编写的组件,也请调整为该模式才能构建为单文件。
Midway v2/v3 均支持配置以"对象模式"加载。
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
import * as DefaultConfig from './config/config.default';
import * as LocalConfig from './config/config.local';
@Configuration({
importConfigs: [
{
default: DefaultConfig,
local: LocalConfig
}
]
})
export class MainConfiguration {
}
2、默认导出的情况
由于 ncc 构建器的默认行为,请不要在依赖注入相关的代码中使用默认导出。比如:
export default class UserSerivce {
// …
}
编译后会导致 UserSerivce 无法注入,应改用具名导出。
3、数据源 entities 相关
数据源依赖的扫描路径也是不支持的:
export default {
typeorm: {
dataSource: {
default: {
// …
entities: [
'/abc',// 不支持
]
},
}
}
如果 entities 特别多,可以编写一个 js 文件,扫描出 entities 后,生成一个文件到目录下,在每次构建时执行。
修改入口文件
修改入口 bootstrap.js 为下列代码:
const { Bootstrap } = require('@midwayjs/bootstrap');
// 显式以组件方式引入用户代码
Bootstrap.configure({
// 这里引用的是编译后的入口,本地开发不走这个文件
imports: require('./dist/index'),
// 禁用依赖注入的目录扫描
moduleDetector: false,
}).run()
这里的关键点有两个:一是通过 imports 显式引入 @midwayjs/bundle-helper 生成的编译后入口,代替原来的目录扫描;二是把 moduleDetector 设为 false,关闭依赖注入的本地文件扫描——这与上文启动参数表中 moduleDetector: 'file' | IFileDetector | false 的语义一一对应,单文件场景下没有可供扫描的目录结构,必须显式关闭。
构建
单文件构建的编译需要几个步骤:
- 1、将项目 ts 文件构建为 js;
- 2、使用额外编译器,将所有的 js 文件打包成一个文件。
可以将上面的流程编写为下面的两条命令,放在 package.json 的 scripts 字段中:
"scripts": {
// …
"bundle": "bundle && npm run build && ncc build bootstrap.js -o build",
"bundle_start": "NODE_ENV=production node ./build/index.js"
}
包含三个部分:
- bundle 是将所有的项目代码以组件的形式导出,并生成一个 src/index.ts 文件,该命令由 @midwayjs/bundle-helper 提供;
- npm run build 是基础的 ts 项目构建,将 src/**/*.ts 构建为 dist/**/*.js;
- ncc build bootstrap.js -o build 以 bootstrap.js 为入口构建为一个单文件,最终生成到 build/index.js。
编写完成后,执行命令:
$ npm run bundle
注意,构建过程中可能有错误,比如 ts 定义错误、入口生成语法不正确等情况,需要手动修复。
编译完成后,启动项目:
$ npm run bundle_start
如果启动访问没问题,那么就可以拿着构建的 build 目录做分发了。
二进制文件部署
将 Node.js 打包为一个单独的可执行文件,部署时直接拷贝执行即可。这种方式包含了 node 运行时、业务代码,有利于保护知识产权。常见的将 Node.js 打包为可执行文件的工具有 pkg、nexe、node-packer、enclose 等,下面以最常见的 pkg 包作为示例。
前置依赖
二进制文件部署有一些前置依赖需要安装:
## 用于生成入口
$ npm i @midwayjs/bundle-helper –save-dev
## 用于构建二进制文件
## 装到全局
$ npm i pkg -g
## 或者装到项目(推荐)
$ npm i pkg –save-dev
代码调整
和单文件构建部署的调整相同,即配置改为对象模式、避免依赖注入代码中的默认导出、数据源 entities 不使用扫描路径,请参考上文"单文件构建部署"的"代码调整"小节。
修改入口文件
和单文件构建部署的修改相同,即 bootstrap.js 显式以组件方式引入 ./dist/index 并关闭 moduleDetector,请参考上文。
构建
首先需要对 pkg 进行配置,主要内容在 package.json 的 bin 和 pkg 字段下:
- bin 指定为入口文件,即 bootstrap.js;
- pkg.scripts 构建后的目录,使用 glob 语法包括了 dist 下的所有 js 文件;
- pkg.assets 如果有一些静态资源文件,可以在这里配置;
- pkg.targets 构建的平台产物,是下列选项的组合(示例中指定了 mac + node18):
- nodeRange (node8), node10, node12, node14, node16 or latest
- platform alpine, linux, linuxstatic, win, macos, (freebsd)
- arch x64, arm64, (armv6, armv7)
- pkg.outputPath 构建产物的地址,为了和 ts 输出分开,选择了 build 目录。
package.json 参考示例:
{
"name": "my-midway-project",
// …
"devDependencies": {
// …
"@midwayjs/bundle-helper": "^1.2.0",
"pkg": "^5.8.1"
},
"scripts": {
// …
"pkg": "pkg . -d > build/pkg.log",
"bundle": "bundle && npm run build"
},
"bin": "./bootstrap.js",
"pkg": {
"scripts": "dist/**/*.js",
"assets": [],
"targets": [
"node18-macos-arm64"
],
"outputPath": "build"
},
// …
}
更为细节的部分请参考 pkg 的文档。上面的实例中,pkg 命令的 -d 参数是为了输出调试信息到特定文件,可以自行删减。
二进制文件构建的编译需要几个步骤:
- 1、生成 src/index.ts 入口文件,将项目 ts 文件构建为 js;
- 2、使用 pkg,生成特定平台的构建产物。
执行命令:
$ npm run bundle
$ npm run pkg
正确的话,可以在 build 目录下看到一个 my-midway-project 文件(对应 package.json 的 name 字段),直接执行即可运行。
注意:pkg.targets 必须按目标运行平台显式指定(例如 Linux 服务器通常需要 node18-linux-x64),macOS 上构建出的产物不能直接拷贝到 Linux 服务器执行。
部署失败的问题
部署后由于和环境相关,情况更为复杂。如果部署到服务器之后碰到问题,请查看 服务器启动失败排查。
综合该排查文档,启动失败通常可以从以下维度入手:
- 快速定位代码问题:在 Linux 上使用 ps aux | grep node 检查进程是否存在、进程数量是否正确;打开项目的日志目录查看 common-error.log 文件内容,根据最新堆栈排查原因;同时关注启动的控制台日志(例如 pm2 logs)相关信息。
- 可能的环境问题:文件不完整或不是最新时,确认部署前执行过 npm run dev(本地启动成功)、npm run build(已编译出 dist 目录)、npm run start(本地以 js 文件执行成功),并检查服务器上 node_modules、dist 目录是否存在且为最新;除此之外,启动用户的权限问题、环境变量、系统内核与网络环境等也都可能成为启动失败的诱因。
小结
本文围绕 Midway 3 的启动与部署链路,完整覆盖了本地 dev 的两种启动方式与入口一致性方案、服务器部署的构建与目录差异、Bootstrap.configure 全部启动参数、pm2 承载下的 bootstrap.js 入口、Docker 基础镜像与多阶段构建优化、docker-compose 编排外部依赖,以及面向 Serverless 和私有化场景的单文件构建与二进制交付。实际部署时,建议遵循"干净环境构建 → 只保留 package.json/bootstrap.js/dist/node_modules → 日志固定目录 → 进程工具守护"这条主线,即可在传统服务器、容器与 Serverless 三种形态间平滑切换。
赞
【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
项目地址:
https://gitcode.com/gh_mirrors/mi/midway
点击查看 免费下载
相关推荐
PicGo 3.0新特性全解:PicGo Cloud云相册与端到端加密配置同步深度体验
drawio-desktop 免费跨平台图表编辑完整指南
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心






评论前必须登录!
注册