今天是《nestjs搭建通用业务框架》系列的第2篇,主要是介绍如何使用nestjs的CLI工具快速初始化项目,了解项目的启动与调试。PS: 调试技巧非常的重要,有利于后续学习框架的核心的原理。
node环境准备 node与npm的版本
1 2 3 4 5 ➜ node -v nv14.15.1 ~ ➜ npm -v 6.14.8
大家使用node官方的LTS的版本即可,下载地址
安装可以使用nrm
或者npm config set registry https://registry.npm.taobao.org/
来进行加速
安装CLI 1 2 3 4 5 // 全局安装cli工具➜ npm i -g @nestjs/cli /Users/m acos/.nvm/ versions/node/ v14.15.1 /bin/ nest -> /Users/m acos/.nvm/ versions/node/ v14.15.1 /lib/ node_modules/@nestjs/ cli/bin/ nest.js+ @nestjs/cli@7.5 .6 added 15 packages from 4 contributors, removed 1080 packages and updated 262 packages in 12.239 s
初始化项目nest new [project-name]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ~/Projects/nestjs ➜ nest new nestjs-common-template ⚡ We will scaffold your app in a few seconds.. CREATE nestjs-common-template/.eslintrc.js (631 bytes) CREATE nestjs-common-template/.prettierrc (51 bytes) CREATE nestjs-common-template/README.md (3339 bytes) CREATE nestjs-common-template/nest-cli.json (64 bytes) CREATE nestjs-common-template/package.json (1984 bytes) CREATE nestjs-common-template/tsconfig.build.json (97 bytes) CREATE nestjs-common-template/tsconfig.json (339 bytes) CREATE nestjs-common-template/src/app.controller.spec.ts (617 bytes) CREATE nestjs-common-template/src/app.controller.ts (274 bytes) CREATE nestjs-common-template/src/app.module.ts (249 bytes) CREATE nestjs-common-template/src/app.service.ts (142 bytes) CREATE nestjs-common-template/src/main.ts (208 bytes) CREATE nestjs-common-template/test/app.e2e-spec.ts (630 bytes) CREATE nestjs-common-template/test/jest-e2e.json (183 bytes) ? Which package manager would you ❤️ to use? (Use arrow keys) ❯ npm yarn ▹▹▹▹▸ Installation in progress... ☕ 🚀 Successfully created project nestjs-common-template 👉 Get started with the following commands: $ cd nestjs-common-template $ npm run start Thanks for installing Nest 🙏 Please consider donating to our open collective to help us maintain this package. 🍷 Donate: https://opencollective.com/nest
运行项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 ~/Projects/nestjs took 22s 385ms ➜ cd nestjs-common-template/ nestjs-common-template on HEAD [?] is 📦 v0.0.1 via ⬢ v14.15.1 ➜ npm run start > nestjs-common-template@0.0.1 start /Users/macos/Projects/nestjs/nestjs-common-template > nest start [Nest] 5918 - 2021/03/09 下午11:06:10 [NestFactory] Starting Nest application... [Nest] 5918 - 2021/03/09 下午11:06:10 [InstanceLoader] AppModule dependencies initialized +47ms [Nest] 5918 - 2021/03/09 下午11:06:10 [RoutesResolver] AppController {}: +4ms [Nest] 5918 - 2021/03/09 下午11:06:10 [RouterExplorer] Mapped {, GET} route +2ms [Nest] 5918 - 2021/03/09 下午11:06:10 [NestApplication] Nest application successfully started +1ms
下面可以打开浏览器来访问http://localhost:3000
可以看到hello world
的字样。
说明我们的项目启动成功了。
项目目录与package.json
先来看看项目的工程目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 . ├── README.md ├── dist │ ├── app.controller .d .ts │ ├── app.controller .js │ ├── app.controller .js .map │ ├── app.module .d .ts │ ├── app.module .js │ ├── app.module .js .map │ ├── app.service .d .ts │ ├── app.service .js │ ├── app.service .js .map │ ├── main .d .ts │ ├── main .js │ ├── main .js .map │ └── tsconfig.build .tsbuildinfo ├── nest-cli.json ├── package-lock.json ├── package.json ├── src // 源文件 │ ├── app.controller .spec .ts │ ├── app.controller .ts │ ├── app.module .ts │ ├── app.service .ts │ └── main .ts ├── test │ ├── app.e2e-spec .ts │ └── jest-e2e.json ├── tsconfig.build .json └── tsconfig.json
然后我们再来看看package.json
,其中有两个部分非常需要注意:
scripts自定义的脚本:
start
: 默认启动脚本
start:dev
: 开启代码变化监视的启动脚本
start:debug
:开启代码debug调试的启动脚本
start:prod
:运行最终打包过后的代码
jest配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { "scripts" : { "prebuild" : "rimraf dist" , "build" : "nest build" , "format" : "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"" , "start" : "nest start" , "start:dev" : "nest start --watch" , "start:debug" : "nest start --debug --watch" , "start:prod" : "node dist/main" , "lint" : "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" , "test" : "jest" , "test:watch" : "jest --watch" , "test:cov" : "jest --coverage" , "test:debug" : "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" , "test:e2e" : "jest --config ./test/jest-e2e.json" } , "jest" : { "moduleFileExtensions" : [ "js" , "json" , "ts" ] , "rootDir" : "src" , "testRegex" : ".*\\.spec\\.ts$" , "transform" : { "^.+\\.(t|j)s$" : "ts-jest" } , "collectCoverageFrom" : [ "**/*.(t|j)s" ] , "coverageDirectory" : "../coverage" , "testEnvironment" : "node" } }
主程序文件(入口文件)main.ts
在项目的src
目录中,有整个项目的主程序文件main.ts
:
1 2 3 4 5 6 7 8 import { NestFactory } from '@nestjs/core' ;import { AppModule } from './app.module' ;async function bootstrap ( ) { const app = await NestFactory .create (AppModule ); await app.listen (3000 ); } bootstrap ();
这主程序文件中:
使用NestFactory
初始化了一个Nest实例app
定义服务监听3000
端口
使用bootstrap()
启动了该服务
我们可以看到该文件引入了一个模块
即./app.module
,可以从这个文件找到一些整个应用的蛛丝马迹。
1 2 3 4 5 6 7 8 9 10 import { Module } from '@nestjs/common' ;import { AppController } from './app.controller' ;import { AppService } from './app.service' ;@Module ({ imports : [], controllers : [AppController ], providers : [AppService ], }) export class AppModule {}
这里使用到了TypeScript
中的Decorator
,引入了一个AppService
和AppController
,如果熟悉Angular的小伙伴,看到这里就惊讶了。卧槽,这不是Augular吗?是的,官方给出了如下的解释:
Nest provides an out-of-the-box application architecture which allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications. The architecture is heavily inspired by Angular.
这是nestjs
的哲学:
Nest提供了一个开箱即用的应用架构,允许开发人员和团队创建高度可测试、可扩展、松散耦合和易于维护的应用。该架构深受Angular的启发。
了解到这一点来说之后,方便学习过Angular的同学快速的入手,那么对于没有学过Angular的同学,可以从以下两个角度来思考学习。
对于毫无Angular
基础的同学来说:
应用是基础:先学会如何使用,写一些基础的接口,然后再去思考内在的逻辑,免得搞混了;
学习先进框架的概念:官方有一手的介绍,所以一点不用慌,而且这些概念源于Google,都是经过了验证的;
平时开发多问几个为什么:比如,为什么nestjs的入口是main.ts
?nestjs
是用什么打包的?怎么没有看到webpack的配置?
对于学过Angular
的同学来说:
大致浏览基础示例代码:在清楚核心的概念之后,这样有利于快速入手;
做一些实战项目:工具的学习不能浮于表面,一定要应用于自己的工作中来,对于小项目,可以参考官方的awesome示例页 ;
下面来回答几个问题:
为什么nestjs的入口是main.ts
? 下面是我个人的思考路径:
package.json中的main属性,有没有?——没有
官方的配置文件中nest-cli.json
,有没有?——没有
scripts中的运行脚本,有没有?——有,但是没有指明,只有nest start
运行脚本使用的CLI,CLI的原文件中,有没有?——这里,就需要调试node_modules
了
按照上面的路径,可以自己建一个Nodejs的调试项目进行运行与调试!官方提供了--debug
脚本,所以可以直接来创建,以VSCode
为例:创建.vscode/launch.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "version" : "0.2.0" , "configurations" : [ { "name" : "Launch via NPM" , "request" : "launch" , "runtimeArgs" : [ "run-script" , "start:debug" ] , "runtimeExecutable" : "npm" , "skipFiles" : [ "<node_internals>/**" ] , "type" : "pwa-node" } ] }
直接使用VSCode
的调试工具进行调试,这时候,新的问题来了,断点打在哪里?!学习过我们慕课网《大前端》 的同学应该了解到CLI工具的工作原理,有以下几个组成部分:
Commander —— 处理传递的参数
Action —— 具体的函数
下面打开node_modules
,找到@nestjs/cli
映入我们眼帘的是:
这不巧了?怎么有两个文件,很打眼呢?commands
与actions
,我们来查看一下:
第一个:
第二个:
分别点开:
start.command.js
中主要是解析命令参数,但是有一个关键方法:this.action.handle(inputs, options)
最终会来执行start.action.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 handle (inputs, options ) { return __awaiter (this , void 0 , void 0 , function * () { try { const configFileName = options.find ((option ) => option.name === 'config' ) .value ; const configuration = yield this .loader .load (configFileName); const appName = inputs.find ((input ) => input.name === 'app' ) .value ; const pathToTsconfig = get_value_or_default_1.getValueOrDefault (configuration, 'compilerOptions.tsConfigPath' , appName, 'path' , options); const binaryToRunOption = options.find ((option ) => option.name === 'exec' ); const debugModeOption = options.find ((option ) => option.name === 'debug' ); const watchModeOption = options.find ((option ) => option.name === 'watch' ); const isWatchEnabled = !!(watchModeOption && watchModeOption.value ); const watchAssetsModeOption = options.find ((option ) => option.name === 'watchAssets' ); const isWatchAssetsEnabled = !!(watchAssetsModeOption && watchAssetsModeOption.value ); const debugFlag = debugModeOption && debugModeOption.value ; const binaryToRun = binaryToRunOption && binaryToRunOption.value ; const { options : tsOptions } = this .tsConfigProvider .getByConfigFilename (pathToTsconfig); const outDir = tsOptions.outDir || defaults_1.defaultOutDir ; const onSuccess = this .createOnSuccessHook (configuration, appName, debugFlag, outDir, binaryToRun); yield this .runBuild (inputs, options, isWatchEnabled, isWatchAssetsEnabled, !!debugFlag, onSuccess); } catch (err) { if (err instanceof Error ) { console .log (`\n${ui_1.ERROR_PREFIX} ${err.message} \n` ); } else { console .error (`\n${chalk.red(err)} \n` ); } } }); }
这时候,我们就可以打印一个configuration
了或者加个断点,使用npm start
来跑一下,得到的结果无非如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 🚀 ~ file : start.action .js ~ line 29 ~ StartAction ~ return__awaiter ~ configuration { language : 'ts' , sourceRoot : 'src' , collection : '@nestjs/schematics' , entryFile : 'main' , projects : {}, monorepo : false , compilerOptions : { tsConfigPath : 'tsconfig.build.json' , webpack : true , webpackConfigPath : 'webpack.config.js' , plugins : [], assets : [] }, generateOptions : {} }
已经让我们看到了entryFile
的字样,至此,我们找到了入口文件。还可以顺这这条思路,继续来找:
继续往下找:
nestjs
是用什么打包的?有了上面调试的技巧,来回答这个问题变得很简单,我们来看看scripts
中的打包命令"build": "nest build",
,所以我们找到对应的command
,可以找到这么一个文件build.action.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 runBuild (inputs, options, watchMode, watchAssetsMode, isDebugEnabled = false , onSuccess ) { return __awaiter (this , void 0 , void 0 , function * () { const configFileName = options.find ((option ) => option.name === 'config' ) .value ; const configuration = yield this .loader .load (configFileName); const appName = inputs.find ((input ) => input.name === 'app' ) .value ; const pathToTsconfig = get_value_or_default_1.getValueOrDefault (configuration, 'compilerOptions.tsConfigPath' , appName, 'path' , options); const { options : tsOptions } = this .tsConfigProvider .getByConfigFilename (pathToTsconfig); const outDir = tsOptions.outDir || defaults_1.defaultOutDir ; const isWebpackEnabled = get_value_or_default_1.getValueOrDefault (configuration, 'compilerOptions.webpack' , appName, 'webpack' , options); yield this .workspaceUtils .deleteOutDirIfEnabled (configuration, appName, outDir); this .assetsManager .copyAssets (configuration, appName, outDir, watchAssetsMode); if (isWebpackEnabled) { const webpackPath = get_value_or_default_1.getValueOrDefault (configuration, 'compilerOptions.webpackConfigPath' , appName, 'webpackPath' , options); const webpackConfigFactoryOrConfig = this .getWebpackConfigFactoryByPath (webpackPath, configuration.compilerOptions .webpackConfigPath ); return this .webpackCompiler .run (configuration, webpackConfigFactoryOrConfig, pathToTsconfig, appName, isDebugEnabled, watchMode, this .assetsManager , onSuccess); } if (watchMode) { const tsCompilerOptions = {}; const isPreserveWatchOutputEnabled = options.find ((option ) => option.name === 'preserveWatchOutput' && option.value === true ); if (isPreserveWatchOutputEnabled) { tsCompilerOptions.preserveWatchOutput = true ; } this .watchCompiler .run (configuration, pathToTsconfig, appName, tsCompilerOptions, onSuccess); } else { this .compiler .run (configuration, pathToTsconfig, appName, onSuccess); this .assetsManager .closeWatchers (); } }); }
结论:
nestjs可以开启webpack打包
或者自己定义的打包,见下图
怎么没有看到webpack
的配置? nestjs
支持webpack
打包,有两种方法启用webpack
,然后,就可以在根目录中添加webpack.config.js
文件了。
官方的说明:
方法一:
针对于命令:nest build
将应用程序或工作区编译到输出文件夹中。
1 $ nest build <name> [options]Copy to clipboardErrorCopied
参数
选项:
选项
描述
--path [path]
tsconfig
文件的路径。别名: -p
--watch
在监视模式下运行(实时重载)别名-w
--webpack
使用 webpack
进行编译。
--webpackPath
配置 webpack
的路径。
方法二:
在根目录中的nest-cli.json
文件中配置compilerOptions
,把webpack
设置成true
:
1 2 3 4 5 6 7 { "collection" : "@nestjs/schematics" , "sourceRoot" : "src" , "compilerOptions" : { "webpack" : true } }
关于nest的nest-cli.json
配置文件及其使用方式可以参考 这里 。