• Stars
    star
    113
  • Rank 299,795 (Top 7 %)
  • Language
    TypeScript
  • Created over 4 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Example of setting up distributed Azure build for Nx workspace

Blazing Fast Distributed CI with Nx Workspaces

Nx is a set of extensible dev tools for monorepos. Monorepos provide a lot of advantages:

  • Everything at that current commit works together. Changes can be verified across all affected parts of the organization.
  • Easy to split code into composable modules
  • Easier dependency management
  • One toolchain setup
  • Code editors and IDEs are "workspace" aware
  • Consistent developer experience
  • ...

But they come with their own technical challenges. The more code you add into your repository, the slower the CI gets.

Example Workspace

This repo is an example Nx Workspace. It has two applications. Each app has 15 libraries, each of which consists of 30 components. The two applications also share code.

If you run nx dep-graph, you will see somethign like this:

CI Provider

This example will use Azure Pipelines, but a very similar setup will work with CircleCI, Jenkins, GitLab, etc..

To see CI runs click here.

Baseline

Most projects that don't use Nx end up building, testing, and linting every single library and application in the repository. The easiest way to implement it with Nx is to do something like this:

jobs:
  - job: ci
    timeoutInMinutes: 120
    pool:
      vmImage: 'ubuntu-latest'
    steps:
      - template: .azure-pipelines/steps/install-node-modules.yml
      - script: yarn nx run-many --target=test --all
      - script: yarn nx run-many --target=lint --all
      - script: yarn nx run-many --target=build --all --prod

This will retest, relint, rebuild every project. Doing this for this repository takes about 45 minutes (note that most enterprise monorepos are significantly larger, so in those cases we are talking about many hours.)

The easiest way to make your CI faster is to do less work, and Nx is great at that.

Building Only What is Affected

Nx knows what is affected by your PR, so it doesn't have to test/build/lint everything. Say the PR only touches ng-lib9. If you run nx affected:dep-graph, you will see something like this:

If you update azure-pipelines.yml to use nx affected instead of nx run-many:

jobs:
  - job: ci
    timeoutInMinutes: 120
    pool:
      vmImage: 'ubuntu-latest'
    steps:
      - template: .azure-pipelines/steps/install-node-modules.yml
      - script: yarn nx affected --target=test --base=origin/master
      - script: yarn nx affected --target=lint --base=origin/master
      - script: yarn nx affected --target=build --base=origin/master --prod

the CI time will go down from 45 minutes to 8 minutes.

This is a good result. It helps to lower the average CI time, but doesn't help with the worstcase scenario. Some PR are going to affect a large portion of the repo.

You could make it faster by running the commands in parallel:

jobs:
  - job: ci
    timeoutInMinutes: 120
    pool:
      vmImage: 'ubuntu-latest'
    variables:
      IS_PR: $[ eq(variables['Build.Reason'], 'PullRequest') ]
    steps:
      - template: .azure-pipelines/steps/install-node-modules.yml
      - script: yarn nx affected --target=test --base=origin/master --parallel
      - script: yarn nx affected --target=lint --base=origin/master --parallel
      - script: yarn nx affected --target=build --base=origin/master --prod --parallel

This helps but it still has a ceiling. At some point, this won't be enough. A single agent is simply insufficent. You need to distrubte CI across a grid of machines.

Distributed CI

To distribute you need to split your job into multiple jobs.


              / lint1
initial_setup - lint2
              - lint3
              - test1
              ....
              \ build3

Initial Setup

The initial_setup job figures out what is affected and what needs to run on what agent.

jobs:
  - job: initial_setup
    pool:
      vmImage: 'ubuntu-latest'
    variables:
      IS_PR: $[ eq(variables['Build.Reason'], 'PullRequest') ]
    steps:
      - template: .azure-pipelines/steps/install-node-modules.yml
      - powershell: echo "##vso[task.setvariable variable=COMMANDS;isOutput=true]$(node ./tools/scripts/calculate-commands.js $(IS_PR))"
        name: setCommands
      - script: echo $(setCommands.COMMANDS)
        name: echoCommands

Where calculate-commands.js looks like this:

const execSync = require('child_process').execSync;
const isMaster = process.argv[2] === 'False';
const baseSha = isMaster ? 'origin/master~1' : 'origin/master';

// prints an object with keys {lint1: [...], lint2: [...], lint3: [...], test1: [...], .... build3: [...]}
console.log(
  JSON.stringify({
    ...commands('lint'),
    ...commands('test'),
    ...commands('build')
  })
);

function commands(target) {
  const array = JSON.parse(
    execSync(`npx nx print-affected --base=${baseSha} --target=${target}`)
      .toString()
      .trim()
  ).tasks.map(t => t.target.project);

  array.sort(() => 0.5 - Math.random());
  const third = Math.floor(array.length / 3);
  const a1 = array.slice(0, third);
  const a2 = array.slice(third, third * 2);
  const a3 = array.slice(third * 2);
  return {
    [target + '1']: a1,
    [target + '2']: a2,
    [target + '3']: a3
  };
}

Let's step through it:

The following defines the base sha Nx uses to execute affected commands.

const isMaster = process.argv[2] === 'False';
const baseSha = isMaster ? 'origin/master~1' : 'origin/master';

If it is a PR, Nx sees what has changed compared to origin/master. If it's master, Nx sees what has changed compared to the previous commit (this can be made more robust by remembering the last successful master run, which can be done by labeling the commit).

The following prints information about affected project that have the needed target. print-affected doesn't run any targets, just prints information about them.

execSync(`npx nx print-affected --base=${baseSha} --target=${target}`)
  .toString()
  .trim();

The rest of the commands splits the list of projects into three grouprs or bins.

Other Jobs

Other jobs use the information crated by initial_step to execute the needed tasks.

- job: lint1
  dependsOn: initial_setup # this tells lin1 to wait for initial_setup to complete
  condition: |
    and(
      succeeded(),
      not(contains(
        dependencies.initial_setup.outputs['setCommands.COMMANDS'],
        '"lint1":[]'
      ))
    )
  pool:
    vmImage: 'ubuntu-latest'
  variables:
    COMMANDS: $[ dependencies.initial_setup.outputs['setCommands.COMMANDS'] ]
  steps:
    - template: .azure-pipelines/steps/install-node-modules.yml
    - script: node ./tools/scripts/run-many.js '$(COMMANDS)' lint1 lint

where run-many.js:

const execSync = require('child_process').execSync;

const commands = JSON.parse(process.argv[2]);
const projects = commands[process.argv[3]];
const target = process.argv[4];
execSync(
  `npx nx run-many --target=${target} --projects=${projects.join(
    ','
  )} --parallel`,
  {
    stdio: [0, 1, 2]
  }
);

Artifacts

This example doesn't do anything with the artifacts created by the build, but often you will need to upload/deploy them. There are several ways to handle it.

  1. You can create a job per application and then copy the output to the staging area, and then once tests complete unstage the files in a separate job and then deploy them.
  2. You can use the outputs property from running npx nx print-affected --target=build to stash and unstash files without having a job per app.
{
  "tasks": [
    {
      "id": "react-app:build",
      "overrides": {},
      "target": {
        "project": "react-app",
        "target": "build"
      },
      "command": "npm run nx -- build react-app",
      "outputs": [
        "dist/apps/react-app"
      ]
    },
    {
      "id": "ng-app:build",
      "overrides": {},
      "target": {
        "project": "ng-app",
        "target": "build"
      },
      "command": "npm run nx -- build ng-app",
      "outputs": [
        "dist/apps/ng-app"
      ]
    }
  ],
  "dependencyGraph": {
    ...
  }
}

Improvements

With these changes, rebuild/retesting/relinting everything takes only 7 minutes. The average CI time is even faster. The best part of this is that you can add more agents to your pool when needed, so the worst-case scenaro CI time will always be under 15 minutes regardless of how big the repo is.

Can We Do Better?

This example uses a fixed agent graph. This setup works without any problems for all CI providers. It also scales well for repo of almost any size. So before doing anything more sophisticated, I'd try this approach. Some CI providers (e.g., Jenkins) allow scaling the number of agents dynamically. The print-affected and run-many commands can be used to implement those setups as well.

Summary

  1. Rebuilding/retesting/relinting everyting on every code change doesn't scale. In this example it takes 45 minutes.
  2. Nx lets you rebuild only what is affected, which drastically improves the average CI time, but it doesn't address the worst-case scenario.
  3. Nx helps you run multiple targets in parallel on the same machine.
  4. Nx provides print-affected and run-many which make implemented distributed CI simple. In this example the time went down from 45 minutes to only 7

More Repositories

1

nx

Smart Monorepos · Fast CI
TypeScript
22,163
star
2

nx-console

Nx Console is the user interface for Nx & Lerna.
TypeScript
1,273
star
3

nx-examples

Example repo for Nx workspace
TypeScript
843
star
4

precise-commits

✨ Painlessly apply Prettier by only formatting lines you have modified anyway!
TypeScript
470
star
5

webpack-plugin-critical

Webpack wrapper for @addyosmani's critical library.
TypeScript
295
star
6

monorepo.tools

Your defacto guide on monorepos, and in depth feature comparisons of tooling solutions.
TypeScript
271
star
7

nx-recipes

Common recipes to productively use Nx with various technologies and in different setups. Made with ❤️ by the Nx Team
TypeScript
213
star
8

nx-workshop

Companion labs for the workshop: "Develop at Scale with Nx Monorepos"
TypeScript
171
star
9

nx-set-shas

✨ A Github Action which sets the base and head SHAs required for `nx affected` commands in CI
TypeScript
141
star
10

nx-labs

A collection of Nx plugins
TypeScript
128
star
11

nx-react-native

86
star
12

react-module-federation

Example showing how to use Nx's new module federation support to speed up the builds of React apps
TypeScript
69
star
13

nx-incremental-large-repo

TypeScript
65
star
14

nx-jenkins-build

Example of setting up distributed Jenkins build for Nx workspace
TypeScript
63
star
15

ci

TypeScript
47
star
16

nx-react-workshop

TypeScript
43
star
17

last-successful-commit-action

GitHub action for identifying the last successful commit for a given workflow and branch.
JavaScript
43
star
18

node-microservices

TypeScript
42
star
19

nx-angular-and-react

TypeScript
42
star
20

blog-post-nx-production

TypeScript
36
star
21

nx-apollo-react-example

TypeScript
34
star
22

gatsby

Nx plugin for Gatsby
TypeScript
32
star
23

nx-bazel-example

Python
31
star
24

nx-distributed-cache-example

Nx workspace connected to Nx Cloud's distributed cache
TypeScript
31
star
25

ng-module-federation

Example showing to use new Nx's module federation support to speed up the builds of Angular apps
TypeScript
31
star
26

example-nx-fullstack

TypeScript
29
star
27

nx-example-multirepo

28
star
28

react-nx-example

Example for the blog post
TypeScript
27
star
29

nx-apollo-angular-example

TypeScript
26
star
30

ngrx-workshop-app

NgRx Workshop Example Application based on Shopping Cart from Angular.io docs
TypeScript
25
star
31

ngrx-example

Application Showing How to Use NgRx
TypeScript
22
star
32

add-nx

JavaScript
22
star
33

ts-aoc-starter

TypeScript
22
star
34

nx-cloud-helm

Nx Cloud Helm Charts
Smarty
21
star
35

playbook-angular-v6-elements-example

TypeScript
21
star
36

board-game-hoard

TypeScript
20
star
37

workshop-nx-labs

Lab exercises for the Nx Enterprise workshop(s)
20
star
38

workshop-nx-starter

Starter repo for the Nx Enterprise workshop(s).
TypeScript
20
star
39

nx-orb

✨ A CircleCI Orb which includes helpful commands for running Nx commands in the CI
JavaScript
19
star
40

nx-expo

Official Expo plugin for Nx (alpha)
19
star
41

ngrx_race_condition

TypeScript
18
star
42

angular-reactive-forms-course

TypeScript
18
star
43

bazel-demo-app

Bazel Demo Application for Blog Post
TypeScript
18
star
44

angular-vscode

Useful VSCode plugins for Angular Development
TypeScript
17
star
45

nx-apollo-example

TypeScript
15
star
46

nx-go-project-graph-plugin

ℹ️ Please take a look at the plugin https://github.com/nx-go/nx-go if you want to use Go and Nx together
Go
15
star
47

nx-tag-successful-ci-run

✨ A Github Action which tags the repo, intended to be used only when on the main branch of your repo at the end of an Nx powered CI run
13
star
48

full-stack-react-example-app

TypeScript
12
star
49

bazel-rollup

TypeScript
11
star
50

nxcloud-k8s-setup

Example of running Nx Cloud on K8s
10
star
51

cra-to-nx

Tool for converting CRA project to Nx workspace
TypeScript
9
star
52

advent-of-code-starter

TypeScript
9
star
53

nx-cypress-command-sharing

Example repo to showcase how to share Cypress commands within various e2e tests in a Nx monorepo
TypeScript
8
star
54

pokemon-supabase-example

TypeScript
8
star
55

nx-trpc-demo

TypeScript
7
star
56

nx-ecosystem-ci

TypeScript
7
star
57

upgrade-with-elements

TypeScript
7
star
58

nx-shops

TypeScript
6
star
59

nx-custom-layout

TypeScript
6
star
60

bazel-cli-build

JavaScript
6
star
61

nx-migrate-angularjs-example

An example repo demonstrating a migration of an AngularJS application into an Nx workspace
JavaScript
6
star
62

bazel-docker-example

Python
6
star
63

cra-to-nx-migration

TypeScript
6
star
64

tic-tac-toe-playwright

TypeScript
6
star
65

nx-devkit-schematics

An example library that uses Nx Devkit to author Angular Schematics
TypeScript
6
star
66

zack-live-stream

The code base being worked on by Zack's livestream
TypeScript
6
star
67

nxcloud-k8s

Nx Cloud Kubernetes Example
6
star
68

angular-react-node

TypeScript
6
star
69

nx-cloud-workflows

JavaScript
5
star
70

nx-plugin-community-example

TypeScript
5
star
71

trpc-livestream

TypeScript
5
star
72

stackblitz-nx-angular

StackBlitz template for an Nx monorepo with Angular
TypeScript
4
star
73

react-to-solid

TypeScript
4
star
74

large-ts-monorepo

TypeScript
4
star
75

simple-portal-example

TypeScript
4
star
76

workshop-ngconf-18-labs

Lab exercises for the ng-conf 2018 workshop
4
star
77

transfer-state

TypeScript
3
star
78

lazy-spinners

TypeScript
3
star
79

angular-console

3
star
80

nx-incremental

Example showing how to set up incremental builds with Nx
TypeScript
3
star
81

workshop-pwa-fishgoco

Demo project for PWA workshop
TypeScript
3
star
82

nx-example-workspace

TypeScript
3
star
83

count-contributors-sample

Sample Repo for Node CLI exercise
JavaScript
2
star
84

bug-repro-circular

TypeScript
2
star
85

nx-example-sharedlib

TypeScript
2
star
86

hybrid-sample

TypeScript
2
star
87

nrwl-coding-assignment

TypeScript
2
star
88

release-js-and-rust

Example of versioning and publishing JS and Rust code together using nx release
Rust
2
star
89

spring-boot-livestream

TypeScript
2
star
90

stackblitz-nx-react

StackBlitz template for an Nx monorepo with React
TypeScript
2
star
91

using-tsc-b-with-next

TypeScript
2
star
92

angular-module-federation-example

TypeScript
2
star
93

nx-coding-assignment

Coding assignment for JavaScript/TypeScript developers
TypeScript
2
star
94

workshop-upgrade-angular-project

Angular version of TuskDesk demo project for upgrade workshops
TypeScript
2
star
95

nrwl-coding-assignment-angular

TypeScript
2
star
96

named-outlets

TypeScript
2
star
97

workshop-upgrade-angularjs-starter

AngularJS version of TuskDesk demo project for workshops
JavaScript
2
star
98

gatsby-example

Example repository that shows Gatsby and Nx in action
TypeScript
2
star
99

nx-14-5-cypress-10-example

TypeScript
1
star
100

.github

Nrwl GitHub Org profile
1
star