How to use monorepo packages with nestjs using turborepo?
Asked Answered
B

3

7

Here's what a real simple monorepo with nestjs using turborepo looks like:

.
├── README.md
├── apps
│   └── nest
│       ├── README.md
│       ├── nest-cli.json
│       ├── package.json
│       ├── src
│       │   ├── app.controller.spec.ts
│       │   ├── app.controller.ts
│       │   ├── app.module.ts
│       │   ├── app.service.ts <---- importing class here
│       │   └── main.ts
│       ├── test
│       │   ├── app.e2e-spec.ts
│       │   └── jest-e2e.json
│       ├── tsconfig.build.json
│       └── tsconfig.json
├── package.json
├── packages
│   └── lib
│       ├── index.ts <------- exporting class here
│       ├── package.json
│       └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json

A class is defined in packages/lib/index.ts and is then imported in apps/nest/src/app.service.ts.

But doing so leads to the following error when trying to import this index.ts:

nest:dev: 
nest:dev: [1:40:25 AM] Found 0 errors. Watching for file changes.
nest:dev: 
nest:dev: /Users/hercule/Workspace/monorepo-nestjs-package/packages/lib/index.ts:3
nest:dev:   public hello() {
nest:dev:          ^^^^^
nest:dev: 
nest:dev: SyntaxError: Unexpected identifier
nest:dev:     at Object.compileFunction (node:vm:360:18)
nest:dev:     at wrapSafe (node:internal/modules/cjs/loader:1088:15)
nest:dev:     at Module._compile (node:internal/modules/cjs/loader:1123:27)
nest:dev:     at Object.Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
nest:dev:     at Module.load (node:internal/modules/cjs/loader:1037:32)
nest:dev:     at Function.Module._load (node:internal/modules/cjs/loader:878:12)
nest:dev:     at Module.require (node:internal/modules/cjs/loader:1061:19)
nest:dev:     at require (node:internal/modules/cjs/helpers:103:18)
nest:dev:     at Object.<anonymous> (/Users/hercule/Workspace/monorepo-nestjs-package/apps/nest/src/app.service.ts:2:1)
nest:dev:     at Module._compile (node:internal/modules/cjs/loader:1159:14)

Therefore, How do we import a typescript package into nestjs that will be compiled / parsed properly?

Note 1: I tried importing this lib (index.ts) to a nextjs app and angular app, it works without any issue. The problem only arises with nestjs

Note 2: Above's example can be reproduced using following repository: https://github.com/beneccli/monorepo-nestjs-package, once cloned, at root of the project, simply run pnpm i and then pnpm dev (or npm or yarn).

Briones answered 9/12, 2022 at 17:58 Comment(1)
did you find any solution yet for this issue? I'm facing the exact same issue!Shemikashemite
B
0

The solution is actually simple.

Issue was that the dependency was not transpiled from typescript to javascript.

To solve this it is required to tell how to build the dependency and where to find the result files. This is done using the package.json of the lib dependency:

// packages/lib/package.json
{
  "name": "lib",
  "version": "0.0.0",
  "main": "./dist/index", <----- NEW
  "types": "./dist/index", <----- NEW
  "scripts": {
    "build": "tsc --build --force tsconfig.json" <----- NEW
  },
  "devDependencies": {
    "typescript": "^4.5.2"
  }
}

I updated the previously given repository accordingly.

What is important here is to not forget to run pnpm build after pnpm install in order to get the proper built dependency. Then nestjs can be started in either dev or prod.

Briones answered 10/12, 2022 at 17:20 Comment(1)
but if you run nest app in dev it won't refresh after you update one of the packages correct?Vittorio
U
0

I clone a repo and see the following things in your packages/lib/package.json :

  • Your packages ( lib exactly ) does not have a build script so I added one.
  • Lib main does not point to the dist/index.js build so I modified the main too.

Final lib's package.json is here

{
  "name": "lib",
  "version": "0.0.0",
  "main": "./dist/index.js",
  "scripts": {
    "build": "tsc"
  },
  "types": "./index.ts",
  "devDependencies": {
    "typescript": "^4.5.2"
  }
}

After those are done, I run the script pnpm run build in the root.

Then I run pnpm dev, and your apps should start working on http://localhost:3000/ with the text Test: Hello World!

The thing behind that is you should have a build script to compile your Typescript code into JS, then have the main to point exactly the compiled index. So that when NestJs use your lib, it should have all the functions and classes in it.

Unwieldy answered 10/12, 2022 at 17:38 Comment(0)
M
0

The reason why consuming TypeScript source code exported from your internal packages works with your Next.js and Angular application packages but doesn't work with NestJS is that:

  • Next.js and Angular use bundlers configured to understand TypeScript source code

While

  • NestJS, by default, uses tsc for compilation and tsc doesn't expect TypeScript source code from node_modules (this is where your internal packages are symlinked and resolved to). But NestJS can be configured to use webpack with some setup

With Turborepo, you can have different compilation strategies for your internal packages. Learn more 🔗 https://turbo.build/repo/docs/core-concepts/internal-packages#compilation-strategies.

Next.js and Angular apps (though I'm not certain about Angular) can use just-in-time packages that export TypeScript source code because their respective bundlers can handle it. NestJS's default compiler, tsc, isn't set up for that.

You can resolve this by either pre-compiling your internal packages or switching to a Webpack-based setup in your NestJS app package.

1. Using compiled internal packages from the consuming NestJS app package (preferred method)

I prefer this method because it speeds up your build time significantly in a large codebase, as your package's build step will hit cache if its source code is unchanged.

step 1: Update your internal package's package.json

Add build and start:dev scripts to your internal package's package.json:

// packages/helloworld/package.json

{
    "name": "@packages/helloworld",
    "scripts": {
        "build": "tsc --build",
        "start:dev": "tsc --watch"
    },
    "exports": {
        "./*": {
            "types": "./src/*.ts",
            "default": "./dist/*.js"
        }
    }
}

step 2: Add a tsconfig.json to your internal package

// packages/helloworld/tsconfig.json

{
    "compilerOptions": {
        "outDir": "./dist",
        "rootDir": "./src",
        "declaration": true,
        "declarationMap": true,
        "sourceMap": true,
        "module": "CommonJS", 
        "target": "ES2020"
    }
}

step 3: Then, in your NestJS app package, import and use the compiled javascript code directly:

// apps/nestjs/src/app.module.ts

import { MyInternalPackage } from "@packages/helloworld/dir-under-src-dir";

2. Using just-in-time compilation for internal packages by switching your NestJS app’s builder to webpack and using the swc-loader

Alternatively, you can configure your NestJS app to use Webpack with swc-loader, which allows TypeScript code to be transpiled on the fly, similar to the Next.js and Angular apps.

Learn more about using webpack in NestJS here (https://docs.nestjs.com/recipes/swc#monorepo).

step 1: Setup an internal package that exposes a webpack config that you can install and extend in your nestjs application packages. Create a package.json for the new webpack config package:

// packages/webpack-config/package.json

{
    "name": "@packages/webpack-config",
    "files": [
        "webpack.config.js"
    ],
   "peerDependencies": {
        "@nestjs/cli": "^10.1.8"
   }
}

step 2: Install necessary dependencies

npm i -D swc-loader —workspace @packages/webpack-config

step 3: Create a webpage config in the internal package folder so your nest application packages can consume it

// packages/webpack-config/webpack.config.js

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

module.exports = {
  node: {
    // required for __dirname to properly resolve
    // Also required for `bull` to work, see https://github.com/OptimalBits/bull/issues/811
    __dirname: true,
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: (_) =>
          /node_modules/.test(_) && !/node_modules\/(@package)/.test(_),
        use: {
          loader: "swc-loader",
          options: swcDefaultConfig,
        },
      },
    ],
  },
};

step 4: Configure your NestJS application packages to use your internal Webpack config by installing and extending it.

Update your NestJS application package's nest-cli.json:

// apps/nestjs/nest-cli.json

{
    "compilerOptions": {
        "builder": "webpack"
    }
}

step 5: Create a webpack.config.js in your NestJs application package and extend the internal webpack package:

// apps/nestjs/webpack.config.js
module.exports = require("@packages/webpack-config/webpack.config");
Mozarab answered 8/10, 2024 at 14:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.