介绍
本章将介绍,Nest的工程结构包括CLI 和 monorepo
如何在Main中获取上下文?
有时候我们希望能够在 Main 中获取 AppModule 的上下文,为此你可以这样写
const app = await NestFactory.createApplicationContext(AppModule);
// 如果你想获取 AppModule 上下文 可以直接 使用下面的代码 (注意我们看下面的例子 ConfigService是 在AppModule 已经注册过的)
const configService = app.get(ConfigService) as ConfigService;
const tasksService = app.select(TasksModule).get(TasksService, { strict: true });
关于CLI
我们从一开始就在使用 Nest 提供的 CLI 不管是创建项目骨架还是各个模块都是用的它,实际上你可以单独的使用 nest不一定需要使用他的 CLI,主要还是看 实际的需求,在Newegg 我们更多的并不是依赖Nest的CLI . 从便利程度来看 使用Nest CLI 效率是有一定提升的,特别是对于一般的需求而言
简单的了解一下
我们看看 简单的一些命令 (ng 即视感)
我们可以使用 npx 或者 npm 或者 yarn全局安装 这个CLI
$ npm install -g @nestjs/cli
# 查看帮助
$ nest --help
# 生成器
$ nest generate --help
# 下面是一个简单的创建的模板项目的命令 (标准模式)
$ nest new my-nest-project
关于工程结构和设计
关于项目的工程结构,一般而言 nest new 我们仅面对的是 一个 “标准工程”,不过Nest CLI 还提支持其他的工程管理模式,比如 Multiple projects、Lib库 他们是用大多数情况下使用 monorepo 来管理的。
下面是一些摘要, 我们展示了不同工程管理模式下的 异同
工程设置 | 标准模式 | Monorepo模式 |
---|---|---|
Multiple projects | 分开文件系统结构 | 单个文件系统结构 |
node_modules & package.json | 单独的实例 | 可以允许部分依赖互相引用和模块共享 |
默认的编译器 | tsc | webpack |
编译设置 | 单独指定 | 每个项目可以自定义 |
Config files like , , etc..eslintrc.js.prettierrc | 单独指定 | 每个项目可以自定义 |
nest build and commandsnest start | 目标自动默认为上下文中的(仅)项目 | 目标默认为单存储库中的默认项目 |
Libraries | 手动管理,通常通过 npm 打包 内置支持 | 内置支持,包括路径管理和Bundle |
看起来有点迷糊我们一会儿实际上来看看就晓得了
CLI的一些基本的指令
# 大部分情况下 都遵循下面的 👇的规范
$ nest commandOrAlias requiredArg [optionalArg] [options]
# 比如这个 表示创建项目 然后去run一下
$ nest new my-nest-project --dry-run
# 我们还有一些缩写形式 比如
$ nest n my-nest-project -d
# 如果你不知道 某个命令的别名 请使用
$ nest new --help
WorkSpace
本小节我们来 分析一下 Nestjs 的 CLI生成的项目的骨架 的这些目录和其具体作用,以及 monorepo 模式下下的工程结构和其说明
概述
一般而言在Nest中我们有两种模式来组织代码 ( 标准模式 和 monorepo 模式 ), 所谓的标准模式 就是Nest CLI 直接New 的那种,比较适合独立性较强的项目/service。monorepo 模式问就不详细讲了,Nest CLI 提供了 使用CLI 命令直接生成monorepo 结构的指令很方便,适合相对复杂的项目。
monorepo模式
我们需要按照下面的步骤一步一步 的生成这样的项目结构
# 先生成 标准模式项目( 这样你将会得到一个完整的标准Nest项目骨架 )
$ nest new project
$ cd ./project
# 然后用 CLI 去生存 mono repo 需要的子项目
$ nest generate app my-app
这样就会生成一个 monorepo 的项目它的结构将会是下面的这样样子
├── README.md
├── apps
│ ├── app1
│ │ ├── src
│ │ │ ├── app1.controller.spec.ts
│ │ │ ├── app1.controller.ts
│ │ │ ├── app1.module.ts
│ │ │ ├── app1.service.ts
│ │ │ └── main.ts
│ │ ├── test
│ │ │ ├── app.e2e-spec.ts
│ │ │ └── jest-e2e.json
│ │ └── tsconfig.app.json
│ ├── app2
│ │ ├── src
│ │ │ ├── app2.controller.spec.ts
│ │ │ ├── app2.controller.ts
│ │ │ ├── app2.module.ts
│ │ │ ├── app2.service.ts
│ │ │ └── main.ts
│ │ ├── test
│ │ │ ├── app.e2e-spec.ts
│ │ │ └── jest-e2e.json
│ │ └── tsconfig.app.json
│ └── nestjs-http-server-template
│ ├── src
│ │ ├── app.controller.ts
│ │ ├── app.module.ts
│ │ ├── app.service.ts
│ │ └── main.ts
│ ├── test
│ │ ├── app.e2e-spec.ts
│ │ └── jest-e2e.json
│ └── tsconfig.app.json
├── nest-cli.json
├── package.json
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
生成完这样的项目之后 它会 往 nest-cli.json 里面写配置,以供nest cli 再run 的时候去读取它 nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "apps/nestjs-http-server-template/src",
"monorepo": true,
"root": "apps/nestjs-http-server-template",
"compilerOptions": {
"webpack": true,
"tsConfigPath": "apps/nestjs-http-server-template/tsconfig.app.json"
},
"projects": {
"nestjs-http-server-template": {
"type": "application",
"root": "apps/nestjs-http-server-template",
"entryFile": "main",
"sourceRoot": "apps/nestjs-http-server-template/src",
"compilerOptions": {
"tsConfigPath": "apps/nestjs-http-server-template/tsconfig.app.json"
}
},
"app1": {
"type": "application",
"root": "apps/app1",
"entryFile": "main",
"sourceRoot": "apps/app1/src",
"compilerOptions": {
"tsConfigPath": "apps/app1/tsconfig.app.json"
}
},
"app2": {
"type": "application",
"root": "apps/app2",
"entryFile": "main",
"sourceRoot": "apps/app2/src",
"compilerOptions": {
"tsConfigPath": "apps/app2/tsconfig.app.json"
}
}
}
}
# 接下来这样 run 就ok (注意要在工程的root 目录)
$ yarn start app2
$ yarn start app1
接下来我们研究一下 这个 nest-cli.json 的配置到底是干什么用的
研究一下这个 JSON
还是看这份JSON 文件,它分为了两个部分
项目顶级的全局的,用来控制标准项目/单工程的配置
子project的单独的配置 ( monorepo 型项目 )
不管是项目级别的还是 全局级别的, 大部分的配置项都大同小异,现在我们首先说说
全局性的配置项目
- "collection":指向用于生成元件的原理图集合;通常不应更改此值
- "sourceRoot":指向标准模式结构中单个项目的源代码根,或单存储库模式结构中默认项目的源代码根目录
- "compilerOptions":带有指定编译器选项的键和指定选项设置的值的映射;请参阅下面的详细信息
- "generateOptions":带有指定全局生成选项的键和指定选项设置的值的映射;请参阅下面的详细信息
- "monorepo":(仅限单存储库)对于单存储库模式结构,此值始终为true
- "root":(仅限 monorepo)指向默认项目的项目根目录
他们在 json 中就是这段配置
{
"collection": "@nestjs/schematics",
"sourceRoot": "apps/nestjs-http-server-template/src",
"monorepo": true,
"root": "apps/nestjs-http-server-template",
"compilerOptions": {
"webpack": true,
"tsConfigPath": "apps/nestjs-http-server-template/tsconfig.app.json"
},
"projects": {}
}
接下来我们进行 更加详细的拆解 编译器配置 注意啊 这里指的就是 compilerOptions
它在JSON中就是这个配置
++++
"compilerOptions": {
"webpack": true,
"tsConfigPath": "apps/nestjs-http-server-template/tsconfig.app.json"
},
"projects": {
++++
它的可选项 和具体作用 见下表
属性 | 类型 | 描述 |
---|---|---|
webpack | 布尔 | 如果 ,请使用 webpack 编译器。如果不存在,请使用 。在单存储库模式下,默认值为 (使用 webpack),在标准模式下,默认值为 (使用 )。详情见下文。truefalsetsctruefalsetsc |
tsConfigPath | 字符串 | (仅限单存储库)指向包含将在调用时使用的设置的文件(例如,在生成或启动默认项目时)。tsconfig.jsonnest buildnest startproject |
webpackConfigPath | 字符串 | 指向 webpack 选项文件。如果未指定,Nest 将查找该文件。有关更多详细信息,请参阅下文。webpack.config.js |
deleteOutDir | 布尔 | 如果 ,无论何时调用编译器,它都会首先删除编译输出目录(如 中配置的那样,其中默认值为 )。truetsconfig.json./dist |
assets | 数组 | 允许在编译步骤开始时自动分发非 TypeScript 资产(在模式下的增量编译中不会发生资产分发)。详情见下文。--watch |
watchAssets | 布尔 | 如果 ,则在监视模式下运行,监视所有非 TypeScript 资源。(有关要监视的资产的更精细控制,请参阅下面的资产部分)。true |
manualRestart | 布尔 | 如果为 ,则启用手动重新启动服务器的快捷方式。默认值为 。truersfalse |
生成器配置generate
有的时候我们经常会使用 nest 的cli 进行 generate 操作比如 generate controller / service / module, 它也可以通过 nest-cli.json 进行配置
"generateOptions": {
// "spec": false 全局都禁止
"spec": {
"service": false // 针对某一类
}
},
// 当然这个 generateOptions 既可以 方在全局 也可以放到 子项目中去
指定编译器
一般情况下(标准工程)都是用webpack 进行编译的,如果我们不希望webpack 进行编译,可以给一个false ,这样它就会切到 tsc 进行编译.
"compilerOptions": {
"webpack": true,
.....
}
webpack配置
如果你希望自己可以配置 那么可以在根目录下 写具体的配置
~ webpack.config.js
module.exports = function (options) {
return {
...options,
externals: [],
};
};
资产
assets 配置 我们可以配置 assets (非ts )文件 构建方式,比如 cv 到 项目root 目录下的doc 下去
"app2": {
"type": "application",
"root": "apps/app2",
"entryFile": "main",
"sourceRoot": "apps/app2/src",
"compilerOptions": {
"tsConfigPath": "apps/app2/tsconfig.app.json",
"assets": [
{
"include": "static/**/*",
"outDir":"./doc/",
"watchAssets": true
}
]
}
}
Libraries
刚才我们看的大多数是 monorepo 的app (我的意思是 apps 下都是 类似的结构标准Nest 工程骨架),一般而言就职过规模比较大的公司,对于TOC的项目 会相对更复杂一些 ,往往会使用git submodule + learn 之类的工作来管理,把 通用的 都归类到 某个 共用库去。 而这个共有的库,可以发布到 公司的npm 私有域下,全公司 都可以共享,在大规模协作中,这种方式还是非常的高效的. 比如我所在的Newegg 就是这样的模式. 在Nest 中。默认就提供来了构建这种模式的 cli 程序,十分的方便
$ nest g library my-library
What prefix would you like to use for the library (default: @app)?
# 这样就好了 使用的使用 只需要 @app/xxxx, @app/xxxx
├── libs
│ └── app
│ ├── src
│ │ ├── app.module.ts
│ │ ├── app.service.spec.ts
│ │ ├── app.service.ts
│ │ └── index.ts
│ └── tsconfig.lib.json
├── nest-cli.json
├── package.json
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
然后它的配置 和大包什么的,都会统一的加到 nest-cli.json中
"project": {
+++
"app": {
"type": "library",
"root": "libs/app",
"entryFile": "index",
"sourceRoot": "libs/app/src",
"compilerOptions": {
"tsConfigPath": "libs/app/tsconfig.lib.json"
}
}
}
对于tsconfig 的配置 也会一起自动更新掉
~ tsconfig.json
"paths": {
"@app/app": [
"libs/app/src"
],
"@app/app/*": [
"libs/app/src/*"
]
}
}
}
比如 我现在 在App2 中使用它 我在 lib下定义的工具
~ libs/app
// 随机生成一段文字
export function randomText() {
return Math.random().toString(36).substring(2);
}
~ app2 中使用
import { randomText } from '@app/app';
@Controller()
export class App2Controller {
constructor(private readonly app2Service: App2Service) {}
@Get()
getHello(): string {
return randomText();
}
}
直接去run app2 就能看得倒效果
yarn start:dev app2
但是更重要的一点的是: 打包的时候会有点奇怪 他生成的目录结构是这样的
- dist
apps
└── app2
├── apps
│ └── app2
│ └── src
│ ├── app2.controller.js
│ ├── app2.controller.js.map
│ ├── app2.module.js
│ ├── app2.module.js.map
│ ├── app2.service.js
│ ├── app2.service.js.map
│ ├── main.js
│ └── main.js.map
├── libs
│ └── app
│ └── src
│ ├── index.js
│ ├── index.js.map
│ └── service
│ ├── index.js
│ └── index.js.map
└── tsconfig.app.tsbuildinfo