Skip to main content

SWC (Speedy Web Compiler)

SWC(Speedy Web Compiler)是一个可扩展的基于 Rust 的平台,可用于编译和打包。 在 Nest CLI 中使用 SWC 是一个非常简单且有效的加速开发过程的方式。

note

SWC 大约比默认的 TypeScript 编译器快 20 倍

安装

首先,安装一些包:

npm i --save-dev @swc/cli @swc/core

入门

安装完成后,你可以使用 Nest CLI 的 swc 构建器,如下所示:

nest start -b swc
# 或者 nest start --builder swc
note

如果你的存储库是一个单体存储库,请查看此部分

你也可以在 nest-cli.json 文件中而不是传递 -b 标志, 只需将 compilerOptions.builder 属性设置为 "swc",如下所示:

{
"compilerOptions": {
"builder": "swc"
}
}

要自定义构建器的行为,你可以传递一个包含两个属性的对象,type"swc")和 options,如下所示:

"compilerOptions": {
"builder": {
"type": "swc",
"options": {
"swcrcPath": "infrastructure/.swcrc"
}
}
}

要在监视模式下运行应用程序,请使用以下命令:

nest start -b swc -w
# 或者 nest start --builder swc --watch

类型检查

SWC 本身不执行任何类型检查(与默认的 TypeScript 编译器相反),因此要打开它, 你需要使用 --type-check 标志:

nest start -b swc --type-check

此命令将指示 Nest CLI 在 SWC 旁边以 noEmit 模式运行 tsc,这将异步执行类型检查。 同样,你也可以在 nest-cli.json 文件中而不是传递 --type-check 标志, 只需将 compilerOptions.typeCheck 属性设置为 true,如下所示:

{
"compilerOptions": {
"builder": "swc",
"typeCheck": true
}
}

CLI 插件(SWC)

--type-check 标志将自动执行 NestJS CLI 插件并生成一个序列化的元数据文件,然后应用程序可以在运行时加载它。

SWC 配置

SWC 构建器已预配置以满足 NestJS 应用程序的要求。 但是,你可以通过在根目录中创建 .swcrc 文件并根据需要调整选项来自定义配置。

{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"baseUrl": "./"
},
"minify": false
}

单体存储库(Monorepo)

如果你的存储库是一个单体存储库,而不是使用 swc 构建器,你必须配置 webpack 使用 swc-loader

首先,安装所需的包:

npm i --save-dev swc-loader

安装完成后,在应用程序的根目录中创建一个 webpack.config.js 文件,并添加以下内容:

const swcDefaultConfig = require('@nestjs/cli/lib/compiler/defaults/swc-defaults').swcDefaultsFactory().swcOptions;

module.exports = {
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: swcDefaultConfig,
},
},
],
},
};

单体存储库和 CLI 插件

现在,如果使用 CLI 插件,swc-loader 将不会自动加载它们。 相反,你必须创建一个手动加载它们的单独文件。为此,在 main.ts 附近声明一个 generate-metadata.ts 文件, 并添加以下内容:

import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins';
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';

const generator = new PluginMetadataGenerator();
generator.generate({
visitors: [new ReadonlyVisitor({ introspectComments: true, pathToSource: __dirname })],
outputDir: __dirname,
watch: true,
tsconfigPath: 'apps/<name>/tsconfig.app.json',
});
note

在此示例中,我们使用了 @nestjs/swagger 插件,但你可以使用任何你选择的插件。

generate() 方法接受以下选项:

  • watch:是否监视项目的更改。
  • tsconfigPathtsconfig.json 文件的路径。相对于当前工作目录(process.cwd())。
  • outputDir:元数据文件将保存在其中的目录的路径。
  • visitors:将用于生成元数据的访问者数组。
  • filename:元数据文件的名称。默认为 metadata.ts
  • printDiagnostics:是否将诊断打印到控制台。默认为 true

最后,你可以在一个单独的终端窗口中使用以下命令运行 generate-metadata 脚本:

npx ts-node src/generate-metadata.ts
# 或者 npx ts-node apps/{YOUR_APP}/src/generate-metadata.ts

常见陷阱(Common pitfalls)

如果你在应用程序中使用 TypeORM/MikroORM 或任何其他 ORM,可能会遇到循环引用的问题。 SWC 不能很好地处理循环引用,因此你应该使用以下解决方法:

@Entity()
export class User {
@OneToOne(() => Profile, (profile) => profile.user)
profile: Relation<Profile>; // <--- 在这里使用 "Relation<>" 类型,而不是只是 "Profile"
}
tip

关系类型是从 typeorm 包导出的。

这样做可以防止属性的类型保存在转义代码中的属性元数据中,从而防止循环依赖问题。

如果你的 ORM 没有提供类似的解决方法,你可以自己定义包装类型:

/**
* 用于绕过 ESM 模块循环依赖问题的包装类型
* 由于反射元数据保存属性的类型,导致此问题。
*/
export type WrapperType<T> = T; // WrapperType === Relation

在项目中所有循环依赖注入中,你还需要使用上面描述的自定义包装类型:

@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => ProfileService))
private readonly profileService: WrapperType<ProfileService>,
) {};
}

Jest + SWC

要在 Jest 中使用 SWC,你需要安装以下包:

npm i --save-dev jest @swc/core @swc/jest

安装完成后,更新 package.json/jest.config.js 文件(取决于你的配置)并添加以下内容:

{
"jest": {
"transform": {
"^.+\\.(t|j)s?$": ["@swc/jest"]
}
}
}

此外,你还需要将以下 transform 属性添加到 .swcrc 文件中:legacyDecoratordecoratorMetadata

{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"baseUrl": "./"
},
"minify": false
}

如果在项目中使用 NestJS CLI 插件,你必须手动运行 PluginMetadataGenerator。 导航到此部分以了解更多信息

Vitest

Vitest 是一个快速轻量级的测试运行器,设计用于与 Vite 一起使用。 它提供了一个现代、快速且易于使用的测试解决方案,可与 NestJS 项目集成。

安装

首先,安装所需的包:

npm i --save-dev vitest unplugin-swc @swc/core @vitest/coverage-v8

配置

在应用程序的根目录中创建一个 vitest.config.ts 文件,并添加以下内容:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
root: './',
},
plugins: [
// 这是使用 SWC 构建测试文件所必需的
swc.vite({
// 明确将模块类型设置为避免从 .swcrc 配置文件继承此值
module: { type: 'es6' },
}),
],
});

此配置文件设置 Vitest 环境、根目录和 SWC 插件。 你还应该为 e2e 测试创建一个单独的配置文件, 其中包含一个指定测试路径正则表达式的额外的 include 字段:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
include: ['**/*.e2e-spec.ts'],
globals: true,
root: './',
},
plugins: [swc.vite()],
});

此外,你可以设置alias选项以支持测试中的 TypeScript 路径:

import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
include: ['**/*.e2e-spec.ts'],
globals: true,
alias: {
'@src': './src',
'@test': './test',
},
root: './',
},
resolve: {
alias: {
'@src': './src',
'@test': './test',
},
},
plugins: [swc.vite()],
});

更新 E2E 测试中的导入

将任何使用 import * as request from 'supertest' 的 E2E 测试导入更改为 import request from 'supertest'。 这是必要的,因为在与 Vite 捆绑在一起时,Vitest 期望 supertest 的默认导入。 在这种特定设置中使用命名空间导入可能会导致问题。

最后,将 package.json 文件中的测试脚本更新为以下内容:

{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:cov": "vitest run --coverage",
"test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false",
"test:e2e": "vitest run --config ./vitest.config.e2e.ts"
}
}

这些脚本可配置 Vitest 以运行测试、观察变更、生成代码覆盖率报告和调试。 test:e2e 脚本专门用于使用自定义配置文件运行 E2E 测试。

有了这些设置,您现在就可以享受在 NestJS 项目中使用 Vitest 所带来的好处, 包括更快的测试执行速度和更现代化的测试体验。

note

您可以在此存储库中查看一个工作示例