Skip to content

工作空间

Nest 提供了两种代码组织模式:

  • 标准模式:适合构建以单个项目为中心的应用。这类应用拥有自己的依赖和配置,不需要针对模块共享或复杂构建优化。这是默认模式。
  • monorepo 模式:将代码产物视为一个轻量级 monorepo 的组成部分,更适合开发团队协作和/或多项目环境。它会自动化构建过程中的一部分工作,让你更容易创建并组合模块化组件,促进代码复用,简化集成测试,也更方便共享项目级资产,例如 eslint 规则和其他配置策略。与 Git submodule 等替代方案相比,它通常也更易用。monorepo 模式通过 nest-cli.json 中的 workspace 概念来协调 monorepo 内各组件之间的关系。

需要特别说明的是,几乎 Nest 的全部功能都与你选择哪种代码组织模式无关。这个选择唯一会影响的是项目的组合方式,以及构建产物如何生成。除此之外,从 CLI 到核心模块再到附加模块,其行为在两种模式下都是一致的。

另外,你也可以在任意时间轻松地从标准模式切换到monorepo 模式。因此,在你尚未明确哪种方式更合适之前,完全可以先延后这个决定。

标准模式

执行 nest new 时,会通过内置 schematic 为你创建一个新的项目。Nest 会自动完成以下事情:

  1. 根据你传给 nest newname 参数创建一个新文件夹。
  2. 用最小可运行 Nest 应用所需的默认文件填充该文件夹。你可以在 typescript-starter 仓库中查看这些文件。
  3. 额外生成 nest-cli.jsonpackage.jsontsconfig.json 等文件,用于配置并启用应用编译、测试和运行所需的各种工具。

从此之后,你就可以像本文档其余部分所介绍的那样,修改 starter 文件、添加新组件、安装依赖(例如 npm install)并继续开发。

Monorepo 模式

要启用 monorepo 模式,你需要先从一个标准模式结构开始,然后再添加项目。这里的“项目”可以是一个完整的应用(使用 nest generate app 添加到 workspace 中),也可以是一个类库(使用 nest generate library 添加到 workspace 中)。下面我们会详细说明这两种项目类型。此处最关键的一点是:向现有标准模式结构中添加项目这一动作,会把它转换成 monorepo 模式。

来看一个例子。

如果我们执行:

bash
$ nest new my-project

此时会创建出一个标准模式结构,大致如下:

plaintext
node_modules
src
  app.controller.ts
  app.module.ts
  app.service.ts
  main.ts
nest-cli.json
package.json
tsconfig.json
eslint.config.mjs

我们可以通过以下方式将其转换为 monorepo 模式:

bash
$ cd my-project
$ nest generate app my-app

执行后,nest 会把现有结构转换为 monorepo 模式。这会带来几个重要变化,目录结构会变成这样:

plaintext
apps
  my-app
    src
      app.controller.ts
      app.module.ts
      app.service.ts
      main.ts
    tsconfig.app.json
  my-project
    src
      app.controller.ts
      app.module.ts
      app.service.ts
      main.ts
    tsconfig.app.json
nest-cli.json
package.json
tsconfig.json
eslint.config.mjs

generate app schematic 会重新组织代码:把每个应用项目都移动到 apps 目录下,并在每个项目根目录中添加一个项目专属的 tsconfig.app.json 文件。原来的 my-project 应用会成为 monorepo 的默认项目,并与新添加的 my-app 一起并列位于 apps 目录中。后文会进一步说明默认项目。

警告

将标准模式结构转换为 monorepo,只适用于遵循 Nest 规范项目结构的项目。具体来说,转换过程中 schematic 会尝试把项目根目录下的 srctest 文件夹迁移到根目录下的 apps 子目录中。如果项目未采用这种结构,转换可能失败,或产生不可靠结果。

工作空间中的项目

Monorepo 使用 workspace 概念来管理内部实体。一个 workspace 由多个项目组成。项目可以是:

  • 应用:完整的 Nest 应用,包含用于启动应用的 main.ts 文件。除了编译和构建层面的差异外,workspace 中的应用项目在功能上与标准模式中的应用完全一致。
  • 类库:类库是一种将通用功能(模块、provider、controller 等)打包起来,并在其他项目中复用的方式。类库本身不能独立运行,也没有 main.ts 文件。详见类库

所有 workspace 都有一个默认项目(通常应当是一个应用类型项目)。它通过 nest-cli.json 顶层的 "root" 属性来定义,该属性指向默认项目的根目录。通常这就是你一开始通过标准模式创建,随后使用 nest generate app 转换为 monorepo 的那个项目。按照这种步骤操作时,这个属性会自动填充。

nest buildnest start 这类命令,在未显式提供项目名时,就会默认作用于默认项目。

例如,在前面的 monorepo 结构中执行:

bash
$ nest start

启动的会是 my-project 应用。若想启动 my-app,则需要执行:

bash
$ nest start my-app

应用

应用类型项目,也就是我们通常所说的“应用”,是可以运行和部署的完整 Nest 应用。你可以使用 nest generate app 创建应用类型项目。

该命令会自动生成项目骨架,其中包括来自 typescript starter 的标准 srctest 目录。与标准模式不同,monorepo 中的应用项目本身不会拥有 package.json.prettierrceslint.config.mjs 这类依赖与配置文件,而是统一使用 monorepo 顶层共享的依赖与配置。

不过,schematic 仍然会在项目根目录生成一个项目专属的 tsconfig.app.json 文件。这个配置文件会自动设置合适的构建选项,包括正确的编译输出目录。它会继承顶层(monorepo 级别)的 tsconfig.json,因此你既可以统一管理全局配置,也可以在项目级按需覆盖。

类库

如前所述,类库类型项目,也就是“类库”,本质上是一组 Nest 组件的封装,必须被组合进应用后才能运行。你可以通过 nest generate library 创建类库项目。至于哪些内容应当抽成类库、哪些应属于应用本身,这是一个架构设计层面的决定。详见类库章节。

CLI 属性

Nest 会将组织、构建和部署标准模式与 monorepo 模式项目所需的元数据保存在 nest-cli.json 文件中。随着你不断添加项目,Nest 会自动维护和更新这个文件,因此通常不需要你手动修改它。不过有些配置你可能会希望自行调整,因此理解这个文件的大致结构仍然很有帮助。

在前面创建 monorepo 的步骤执行完成后,nest-cli.json 大致如下:

javascript
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "apps/my-project/src",
  "monorepo": true,
  "root": "apps/my-project",
  "compilerOptions": {
    "webpack": true,
    "tsConfigPath": "apps/my-project/tsconfig.app.json"
  },
  "projects": {
    "my-project": {
      "type": "application",
      "root": "apps/my-project",
      "entryFile": "main",
      "sourceRoot": "apps/my-project/src",
      "compilerOptions": {
        "tsConfigPath": "apps/my-project/tsconfig.app.json"
      }
    },
    "my-app": {
      "type": "application",
      "root": "apps/my-app",
      "entryFile": "main",
      "sourceRoot": "apps/my-app/src",
      "compilerOptions": {
        "tsConfigPath": "apps/my-app/tsconfig.app.json"
      }
    }
  }
}

该文件分为两部分:

  • 一个全局区域,使用顶层属性控制标准模式与 monorepo 级别的设置
  • 一个顶层属性 "projects",保存每个项目的元数据;该部分只在 monorepo 模式下存在

顶层属性说明如下:

  • "collection":指向用于生成组件的 schematic 集合,通常不应修改
  • "sourceRoot":在标准模式下指向单个项目的源码根目录;在 monorepo 模式下指向默认项目的源码根目录
  • "compilerOptions":一个键值映射,用于指定编译选项;详见下文
  • "generateOptions":一个键值映射,用于指定全局 generate 选项;详见下文
  • "monorepo":仅在 monorepo 模式下存在,值总是 true
  • "root":仅在 monorepo 模式下存在,指向默认项目的项目根目录

全局编译选项

这些属性用于指定使用哪个编译器,以及影响所有编译步骤的通用选项。无论是 nest build 还是 nest start,也无论底层使用 tsc 还是 webpack,这些选项都会生效。

属性名值类型说明
webpackboolean若为 true,使用 webpack 编译器;若为 false 或未设置,则使用 tsc。在 monorepo 模式下默认是 true,标准模式下默认是 false。(已废弃,建议改用 builder
tsConfigPathstring仅在 monorepo 模式下使用。当调用 nest buildnest start 且未指定 project 时,指向默认项目所使用的 tsconfig.json 文件。
webpackConfigPathstring指向 webpack 配置文件。如果未指定,Nest 会尝试读取 webpack.config.js
deleteOutDirboolean若为 true,每次调用编译器前,都会先删除编译输出目录(由 tsconfig.json 配置,默认是 ./dist)。
assetsarray允许在编译步骤开始时自动分发非 TypeScript 资源文件(注意:在 --watch 增量编译模式下,资源分发不会自动触发)。详见下文。
watchAssetsboolean若为 true,则以 watch 模式监听全部非 TypeScript 资源。若需要更细粒度控制,请参见下文“Assets”。
manualRestartboolean若为 true,则启用快捷键 rs 手动重启服务。默认值为 false
builderstring/object指定用于编译项目的 buildertscswcwebpack)。如需自定义 builder 行为,可传入对象,其中包含 typetscswcwebpack)和 options
typeCheckbooleanbuilderswc 时,若为 true,则启用类型检查。默认值为 false

全局 generate 选项

这些属性用于指定 nest generate 命令的默认选项。

属性名值类型说明
specboolean 或 object若为 boolean,true 表示默认生成 spec 文件,false 表示默认不生成。CLI 命令行上传入的 flag 会覆盖该设置,项目级 generateOptions 也会覆盖它。若为 object,则每个键表示某个 schematic 名称,对应的布尔值控制该 schematic 默认是否生成 spec。
flatboolean若为 true,则所有 generate 命令都默认生成扁平结构。

下面的例子表示:默认对所有项目关闭 spec 文件生成:

javascript
{
  "generateOptions": {
    "spec": false
  }
}

下面的例子表示:默认对所有项目启用扁平文件生成:

javascript
{
  "generateOptions": {
    "flat": true
  }
}

下面的例子表示:仅对 service schematic 禁用 spec 文件生成:

javascript
{
  "generateOptions": {
    "spec": {
      "service": false
    }
  }
}

警告

spec 使用对象形式时,generation schematic 的 key 目前不会自动处理别名。这意味着如果你只写 service: false,但生成服务时使用了别名 s,spec 文件仍然会被生成。

为了让完整命令名和别名都按预期工作,请同时写上二者,例如:

javascript
{
  "generateOptions": {
    "spec": {
      "service": false,
      "s": false
    }
  }
}

项目级 generate 选项

除了全局 generate 选项外,你也可以为某个项目单独指定 generate 选项。项目级 generate 选项的格式与全局 generate 选项完全一致,只是它们直接定义在具体项目上。

项目级 generate 选项会覆盖全局 generate 选项。

javascript
{
  "projects": {
    "cats-project": {
      "generateOptions": {
        "spec": {
          "service": false
        }
      }
    }
  }
}

警告

generate 选项的优先级顺序如下:CLI 命令行中显式传入的选项优先级最高;项目级选项会覆盖全局选项。

指定编译器

之所以两种模式默认使用不同的编译器,是因为对于更大型的项目(例如 monorepo 中更常见的情况),webpack 在构建速度以及将所有项目组件打成单个文件方面通常具有显著优势。如果你希望生成单独文件,可以将 "webpack" 设为 false,这样构建过程就会改用 tsc(或 swc)。

Webpack 选项

webpack 配置文件中可以写标准的 webpack 配置项。例如,如果你希望 webpack 也打包 node_modules(默认情况下它们会被排除),可以在 webpack.config.js 中加入:

javascript
module.exports = {
  externals: [],
};

由于 webpack 配置文件本身就是 JavaScript 文件,你甚至可以导出一个函数,接收默认配置并返回修改后的对象:

javascript
module.exports = function (options) {
  return {
    ...options,
    externals: [],
  };
};

Assets

TypeScript 编译会自动把编译产物(.js.d.ts 文件)输出到指定目录。有时你也会希望同时分发一些非 TypeScript 文件,比如 .graphql 文件、图片、.html 文件或其他静态资源。这使得你可以把 nest build(以及任何初始编译步骤)视为一个轻量级的开发构建步骤,在其中一边编辑非 TypeScript 文件,一边持续编译和测试。

这些资源文件应位于 src 目录下,否则不会被复制。

assets 键的值应为一个数组,数组中的每个元素描述一组要分发的文件。元素可以是简单字符串,使用类似 glob 的模式,例如:

typescript
"assets": ["**/*.graphql"],
"watchAssets": true

如果你需要更细粒度控制,数组元素也可以是对象,支持以下键:

  • "include":使用 glob 风格指定需要分发的资源
  • "exclude":使用 glob 风格指定要从 include 中排除的资源
  • "outDir":指定资源输出目录(相对于项目根目录)。默认使用编译输出目录
  • "watchAssets":boolean;若为 true,则 watch 模式下监听指定资源

例如:

typescript
"assets": [
  { "include": "**/*.graphql", "exclude": "**/omitted.graphql", "watchAssets": true }
]

警告

如果你在顶层 compilerOptions 中设置了 watchAssets,它会覆盖 assets 属性中每个对象的 watchAssets 配置。

项目属性

该部分仅存在于 monorepo 模式结构中。通常不应手动修改这些属性,因为 Nest 会依赖它们来定位 monorepo 中各项目及其配置项。

基于 NestJS 官方文档翻译