Skip to content

Latest commit

 

History

History
174 lines (131 loc) · 7.57 KB

README.md

File metadata and controls

174 lines (131 loc) · 7.57 KB

What the heck is monorepo?

In VCS, a monorepo is:

  • A software-development strategy.
  • Where the code for a number of projects is stored in the same repository.

What does Nx for me?

  • A powerful open-source build system.
  • Provides tools and techniques for:
  • Run tasks in parallel based on the dependencies between them.
  • Caches tasks.
  • Works the same way with any JS or non-JS project.

Create a workspace in Nx

  • Workspace is a monorepo in which we are gonna have our codebase.
  • npx create-nx-workspace --pm pnpm
    • Will ask you a bunch of question about what kind of workspace do you need.

Devkit

  • A set of utilities for building Nx plugins.

Run a task

  • Run multiple tasks: npx nx --run-many targetName1 targetName2

  • Each task has what we call it "executor".

  • Nx picks up tasks from:

    1. package.json scripts.
    2. Defined inside project.json/nx.json.

    And their config:

    • It infer/deduce them from configuration files (e.g. jest.config.ts).
  • Run tasks affected by your PR: npx nx affected -t test.

nx.json

  • targetDefaults
    Here we assume Same Target Name, Same Executor.
    Multiple targets (tasks like build, test) with the same name across different projects, they all use the same executor.

    What did we get out of this?

    • DRY principle.
    • Ease of maintenance and development.
    • Easer time to reason about each project's configuration.
    Folder structure Before operation libs/lib1/project.json nx.json After operation libs/lib1/project.json
    Directory structure
    {
    "name": "lib1",
    "$schema": "../../node_modules/nx/schemas/project-schema.json",
    "sourceRoot": "libs/lib1/src",
    "projectType": "library",
    "targets": {
    "build": {
    "executor": "@nx/js:tsc",
    "outputs": ["{options.outputPath}"],
    "options": {
    "outputPath": "dist/libs/lib1",
    "main": "libs/lib1/src/index.ts",
    "tsConfig": "libs/lib1/tsconfig.lib.json",
    "assets": ["libs/lib1/*.md", "libs/lib1/src/images/*"]
    }
    },
    "lint": {
    "executor": "@nx/eslint:lint",
    "outputs": ["{options.outputFile}"],
    "options": {
    "lintFilePatterns": ["libs/lib1/**/*.ts"]
    }
    },
    "test": {
    "executor": "@nx/jest:jest",
    "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
    "options": {
    "jestConfig": "libs/lib1/jest.config.ts",
    "passWithNoTests": true
    },
    "configurations": {
    "ci": {
    "ci": true,
    "codeCoverage": true
    }
    }
    }
    },
    "tags": []
    }
    {
    "targetDefaults": {
    "build": {
    "executor": "@nx/js:tsc",
    "outputs": ["{options.outputPath}"],
    "options": {
    "outputPath": "dist/{projectRoot}",
    "main": "{projectRoot}/src/index.ts",
    "tsConfig": "{projectRoot}/tsconfig.lib.json",
    "assets": ["{projectRoot}/*.md"]
    }
    },
    "lint": {
    "executor": "@nx/eslint:lint",
    "outputs": ["{options.outputFile}"],
    "options": {
    "lintFilePatterns": ["{projectRoot}/**/*.ts"]
    }
    },
    "test": {
    "executor": "@nx/jest:jest",
    "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
    "options": {
    "jestConfig": "{projectRoot}/jest.config.ts",
    "passWithNoTests": true
    },
    "configurations": {
    "ci": {
    "ci": true,
    "codeCoverage": true
    }
    }
    }
    }
    }
    {
    "name": "lib1",
    "$schema": "../../node_modules/nx/schemas/project-schema.json",
    "sourceRoot": "libs/lib1/src",
    "projectType": "library",
    "targets": {
    "build": {
    "options": {
    "assets": ["libs/lib1/*.md", "libs/lib1/src/images/*"]
    }
    },
    "lint": {},
    "test": {}
    },
    "tags": []
    }
    • Two build targets with different executors:

      Do NOT use build as key inside the targetDefaults. But instead use use executor name as key:

      nx.json project.json
      {
      // ...
      "targetDefaults": {
      "@nx/jest:jest": {
      "cache": true,
      "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.cjs"],
      "options": {
      "passWithNoTests": true
      },
      "configurations": {
      "ci": {
      "ci": true,
      "codeCoverage": true
      }
      }
      }
      }
      // ...
      }
      {
      // ...
      "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
      "options": {
      "jestConfig": "libs/server/ai/jest.config.ts"
      }
      }
      // ...
      }
    • If you have to make exceptions for most of the projects in your repository, then that setting probably should not be a default.

project.json

Important

Learn more about project.json here.

Caching

  • Battle-tested computation caching system.

  • Code is never rebuilt twice.

  • Can be don in either:

    libs/lib1/project.json nx.json
    {
    "targets": {
    "test": {
    "cache": true
    }
    }
    }
    {
    "targetDefaults": {
    "build": {
    "cache": true
    }
    }
    }
  • The operation that you wanna cache need to be pure, no side effect is allowed. E.g. you cannot cache your e2e tests since something might have changed on your backend.

  • inputs: defines what gets included as part of the calculated hash (e.g. files, environment variables, etc.).

    Exclude all *.md files from the cache so that whenever we change the any markdown file, it does not invalidate the build cache.

    libs/lib1/project.json nx.json
    {
    "targetDefaults": {
    "build": {
    "inputs": ["{projectRoot}/**/*", "!{projectRoot}/**/*.md"],
    "outputs": ["{workspaceRoot}/dist/{projectName}"]
    }
    }
    }
    {
    "targetDefaults": {
    "build": {
    "inputs": ["{projectRoot}/**/*", "!{projectRoot}/**/*.md"],
    "outputs": ["{workspaceRoot}/dist/{projectName}"]
    }
    }
    }
  • outputs: defines where the result of a task is placed after task execution (e.g. where is our coverage report).

    Define output locations if they differ from the usual dist or build directory since Nx by default recognizes those.

Note

Learn about what are projectName, and they other place holders in glossary.

  • Nx plugin automatically configure caching based on your configuration files and also the executor.

    Nx plugins can automatically infer tasks and configure caching based on your underlying tooling configuration files.

Important notes about Nx

  1. Make sure to install the @nx/whatever version that matches the version of nx in your repository. If the version numbers get out of sync, you can encounter some difficult to debug errors. You can fix Nx version mismatches with this recipe.
  2. Nx plugins lift the burden of things like scaffolding a new app (e.g. NestJS, NextJS), testing, building, etc
  3. You can use nx graph and nx show projects to see what is going on in your monorepo.

Upgrade dependencies -- nx migrate latest

This command is NOT gonna upgrade your devDeps and deps to the latest version. It is only upgrading nx and its belongings. So we can use other approaches to upgrade our dependencies:

npm outdated
npm update

But most of the times npm update will NOT do the trick. So in those cases we need to rely on tools such as npm-check-updates

npm install -g npm-check-updates
ncu

if do not need migrations.json (scenarios such as applying same migration in other branches) remove it.