Nx can help you dramatically reduce the lines of configuration code that you need to maintain.
Lets say you have three libraries in your repository - lib1
, lib2
and lib3
. The folder structure looks like this:
Directoryrepo/
Directorylibs/
Directorylib1/
- tsconfig.lib.json
- project.json
Directorylib2/
- tsconfig.lib.json
- project.json
Directorylib3/
- tsconfig.lib.json
- project.json
- nx.json
Initial Configuration Settings
Section titled “Initial Configuration Settings”All three libraries have a similar project configuration. Here is what their project.json
files look like:
{ "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": []}
{ "name": "lib2", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/lib2/src", "projectType": "library", "targets": { "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/libs/lib2", "main": "libs/lib2/src/index.ts", "tsConfig": "libs/lib2/tsconfig.lib.json", "assets": ["libs/lib2/*.md"] } }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["libs/lib2/**/*.ts"] } }, "test": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "libs/lib2/jest.config.ts", "passWithNoTests": true, "testTimeout": 10000 }, "configurations": { "ci": { "ci": true, "codeCoverage": true } } } }, "tags": []}
{ "name": "lib3", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/lib3/src", "projectType": "library", "targets": { "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/libs/lib3", "main": "libs/lib3/src/index.ts", "tsConfig": "libs/lib3/tsconfig.lib.json", "assets": ["libs/lib3/*.md"] } }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["libs/lib3/**/*.ts"] } }, "test": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "libs/lib3/jest.config.ts", "passWithNoTests": true }, "configurations": { "ci": { "ci": true, "codeCoverage": true } } } }, "tags": []}
If you scan through these three files, they look very similar. The only differences aside from the project paths are that lib1
has different assets defined for the build
target and lib2
has a testTimeout
set for the test
target.
Reduce Configuration with targetDefaults
Section titled “Reduce Configuration with targetDefaults”Let's use the targetDefaults
property in nx.json
to reduce some of this duplicate configuration code.
{ "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 } } } }}
Now the project.json
files can be reduced to this:
{ "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": []}
{ "name": "lib2", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/lib2/src", "projectType": "library", "targets": { "build": {}, "lint": {}, "test": { "options": { "testTimeout": 10000 } } }, "tags": []}
{ "name": "lib3", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/lib3/src", "projectType": "library", "targets": { "build": {}, "lint": {}, "test": {} }, "tags": []}
Ramifications
Section titled “Ramifications”This change adds 33 lines of code to nx.json
and removes 84 lines of code from the project.json
files. That's a net reduction of 51 lines of code. And you'll get more benefits from this strategy the more projects you have in your repo.
Reducing lines of code is nice, but just like using the DRY principle in code, there are other benefits:
- You can easily change the default settings for the whole repository in one location.
- When looking at a single project, it is clear how it differs from the defaults.