close

NestJS

Rspack 可以用于构建 NestJS 应用。相比直接使用 tsc,Rspack 可以打包应用代码、通过 SWC 转译 TypeScript、支持开发阶段 HMR,并在需要时将运行时依赖作为 external 处理。

如何使用

请先参考通用的 Node.js 应用配置,再补充本文介绍的 NestJS 配置。你也可以参考 rstack-examples 中的 NestJS example,其中包含完整的 rspack.config.mjs、开发脚本、生产构建脚本和 HMR 入口。

示例

rstack-examples 仓库包含了使用 Rspack 构建 NestJS 应用的完整示例。

安装依赖

先按 Node.js 指南安装通用的 Node.js 构建依赖,然后安装 NestJS 运行时依赖:

npm
yarn
pnpm
bun
deno
npm add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs

基本概念

NestJS 应用运行在 Node.js 中,因此大部分配置都与通用的 Node.js 配置 一致。除此之外,NestJS 还需要 decorator metadata,框架会在运行时读取这些信息。

  • Decorator metadata:启用 TypeScript decorators 和 decorator metadata,保证依赖注入、控制器、路由、守卫、拦截器等 NestJS 能力正常工作。
  • HMR cleanup:开发阶段 HMR 更新服务端 bundle 时,需要在接受更新前关闭旧的 Nest 应用实例。
  • Production minification:保留 class name 和 function name,因为 NestJS 可能会通过 metadata reflection 和 execution context API 使用对应的引用。

配置 Rspack

以下配置改编自 NestJS 示例,它组合了通用 Node.js 配置和 NestJS 所需的 decorator metadata 选项:

rspack.config.mjs
// @ts-check
import { defineConfig } from '@rspack/cli';
import { rspack } from '@rspack/core';
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
import nodeExternals from 'webpack-node-externals';

export default defineConfig({
  context: import.meta.dirname,
  target: 'node',
  entry: {
    main:
      process.env.NODE_ENV === 'production'
        ? './src/main.ts'
        : ['@rspack/core/hot/poll?100', './src/main.ts'],
  },
  output: {
    clean: true,
  },
  resolve: {
    extensions: ['...', '.ts', '.tsx', '.jsx'],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: 'builtin:swc-loader',
          options: {
            detectSyntax: 'auto',
            jsc: {
              parser: {
                decorators: true,
              },
              transform: {
                legacyDecorator: true,
                decoratorMetadata: true,
              },
            },
          },
        },
      },
    ],
  },
  optimization: {
    minimizer: [
      new rspack.SwcJsMinimizerRspackPlugin({
        minimizerOptions: {
          compress: {
            keep_classnames: true,
            keep_fnames: true,
          },
          mangle: {
            keep_classnames: true,
            keep_fnames: true,
          },
        },
      }),
    ],
  },
  plugins: [
    process.env.NODE_ENV !== 'production' &&
      new RunScriptWebpackPlugin({
        name: 'main.js',
        autoRestart: false,
      }),
  ],
  devServer: {
    devMiddleware: {
      writeToDisk: true,
    },
  },
  externals: [
    nodeExternals({
      allowlist: [/@rspack\/core\/hot\/poll/],
    }),
  ],
});

配置脚本

开发阶段使用 rspack dev,生产构建使用 rspack build

package.json
{
  "scripts": {
    "build": "rspack build",
    "dev": "rspack dev",
    "start": "node dist/main.js"
  }
}
  • dev:运行 Rspack Dev Server,将服务端 bundle 写入磁盘,并启动 dist/main.js
  • build:创建不包含 HMR polling 入口的生产 bundle。
  • start:使用 Node.js 运行生产 bundle。

配置 TypeScript decorators

NestJS 使用 @Module()@Controller()@Injectable()@Get() 等 decorators。需要配置 builtin:swc-loader,让 SWC 能够解析这些 decorators,并生成 NestJS 需要的 metadata:

rspack.config.mjs
export default {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: 'builtin:swc-loader',
          options: {
            detectSyntax: 'auto',
            jsc: {
              parser: {
                decorators: true,
              },
              transform: {
                legacyDecorator: true,
                decoratorMetadata: true,
              },
            },
          },
        },
      },
    ],
  },
};

如果项目同时运行 tsc,请在 tsconfig.json 中启用 experimentalDecoratorsemitDecoratorMetadata,让 TypeScript 和 Rspack 使用一致的 decorator 配置:

tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

配置开发 HMR

只在开发入口中添加 @rspack/core/hot/poll。如果生产 bundle 中包含它,Node.js 应用会在运行时失败,因为 build mode 下不会启用 HMR:

rspack.config.mjs
export default {
  entry: {
    main:
      process.env.NODE_ENV === 'production'
        ? './src/main.ts'
        : ['@rspack/core/hot/poll?100', './src/main.ts'],
  },
};

在 NestJS bootstrap 文件中添加 HMR 处理,在接受更新前关闭旧的应用实例:

src/main.ts
declare const module: any;

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}

bootstrap();

通用的 Rspack entry、writeToDiskRunScriptWebpackPlugin 和 externals allowlist 设置请参考 Node.js - 配置开发 HMR。如果你的项目需要 ESM 输出,请参考 Node.js - 使用 ESM 输出

配置生产压缩

压缩 NestJS 服务端 bundle 时,需要保留 class name 和 function name。NestJS 可能会通过 metadata reflection 和 execution context API 使用对应的 class 与 handler 引用。

rspack.config.mjs
import { rspack } from '@rspack/core';

export default {
  optimization: {
    minimizer: [
      new rspack.SwcJsMinimizerRspackPlugin({
        minimizerOptions: {
          compress: {
            keep_classnames: true,
            keep_fnames: true,
          },
          mangle: {
            keep_classnames: true,
            keep_fnames: true,
          },
        },
      }),
    ],
  },
};

Native node modules

如果 NestJS 依赖中包含 Node.js native addon(.node 模块),请参考 Node.js - Native node modules,使用 asset/resource 输出 addon,并让 Node.js 在运行时加载。