Setting Up Nx Libraries

Complete guide to setting up and configuring NX libraries in our monorepo

Setting Up NX Libraries

This guide explains how to properly configure NX libraries in our monorepo, including the build system fixes we've implemented to resolve ng-packagr compatibility issues.

🚨 Common Build Issues & Solutions

Problem: ng-packagr ES Module Errors

Error Message:

Cannot destructure property 'pos' of 'file.referencedFiles[index]' as it is undefined.
require() of ES Module /path/to/find-cache-directory/index.js not supported.

Root Cause: The @nx/angular:ng-packagr-lite executor has compatibility issues with newer Node.js versions and complex dependency chains.

Solution: Replace ng-packagr with direct TypeScript compilation using nx:run-commands.

📋 Standard Library Configuration

1. Project Structure

Every library MUST have this structure with models directory:

libs/your-library/
├── src/
│   ├── lib/
│   │   ├── models/              # ← REQUIRED: All types go here
│   │   │   ├── index.ts         # ← Export all models
│   │   │   └── your-types.ts    # ← Type definitions
│   │   ├── components/
│   │   ├── services/
│   │   └── index.ts             # ← Must export models first
│   └── test-setup.ts
├── project.json
├── package.json
├── tsconfig.json
├── tsconfig.lib.json
├── tsconfig.lib.prod.json
└── ng-package.json

🚨 CRITICAL: No Local Types Policy

NEVER define types/interfaces in service or component files!

✅ Correct: All types in models/ directory ❌ Wrong: Local type definitions in services/components

2. project.json Configuration

✅ Recommended Build Configuration

{
  "name": "your-library",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "libs/your-library/src",
  "prefix": "lib",
  "projectType": "library",
  "tags": [],
  "targets": {
    "build": {
      "executor": "nx:run-commands",
      "options": {
        "commands": [
          "echo 'Building your-library...'",
          "tsc --project libs/your-library/tsconfig.lib.json --outDir dist/libs/your-library",
          "cp -r libs/your-library/src/lib/styles dist/libs/your-library/ 2>/dev/null || true",
          "cp libs/your-library/package.json dist/libs/your-library/ 2>/dev/null || true",
          "echo 'Your-library build completed'"
        ],
        "parallel": false
      },
      "outputs": [
        "{workspaceRoot}/dist/libs/your-library"
      ],
      "configurations": {
        "production": {
          "commands": [
            "echo 'Building your-library (production)...'",
            "tsc --project libs/your-library/tsconfig.lib.prod.json --outDir dist/libs/your-library",
            "cp -r libs/your-library/src/lib/styles dist/libs/your-library/ 2>/dev/null || true",
            "cp libs/your-library/package.json dist/libs/your-library/ 2>/dev/null || true",
            "echo 'Your-library production build completed'"
          ]
        },
        "development": {
          "commands": [
            "echo 'Building your-library (development)...'",
            "tsc --project libs/your-library/tsconfig.lib.json --outDir dist/libs/your-library",
            "cp -r libs/your-library/src/lib/styles dist/libs/your-library/ 2>/dev/null || true",
            "cp libs/your-library/package.json dist/libs/your-library/ 2>/dev/null || true",
            "echo 'Your-library development build completed'"
          ]
        }
      },
      "defaultConfiguration": "production",
      "parallelism": true,
      "cache": true
    },
    "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
      "options": {
        "jestConfig": "libs/your-library/jest.config.ts"
      }
    },
    "lint": {
      "executor": "@nx/eslint:lint"
    }
  }
}

❌ Avoid This Configuration

{
  "build": {
    "executor": "@nx/angular:ng-packagr-lite",  // ❌ Causes ES module errors
    "options": {
      "project": "libs/your-library/ng-package.json"  // ❌ Complex ng-packagr config
    }
  }
}

🔧 Build Command Breakdown

What Each Command Does

  1. echo 'Building your-library...'

    • Shows progress in build logs
    • Helps with debugging and monitoring
  2. tsc --project libs/your-library/tsconfig.lib.json --outDir dist/libs/your-library

    • Compiles TypeScript to JavaScript
    • Generates .d.ts declaration files
    • Creates source maps (.js.map)
    • Uses the library's TypeScript configuration
  3. cp -r libs/your-library/src/lib/styles dist/libs/your-library/ 2>/dev/null || true

    • Copies CSS/SCSS files to build output
    • 2>/dev/null suppresses error messages
    • || true ensures the build continues even if no styles exist
  4. cp libs/your-library/package.json dist/libs/your-library/ 2>/dev/null || true

    • Copies package.json for npm publishing
    • Required for proper library distribution
  5. echo 'Your-library build completed'

    • Confirms successful completion
    • Helps identify which library finished building

Configuration Differences

Setting Development Production
TypeScript Config tsconfig.lib.json tsconfig.lib.prod.json
Optimization Basic Full optimization
Source Maps Yes Yes (for debugging)
Minification No Yes

📁 Required Files

1. tsconfig.lib.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "types": []
  },
  "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"],
  "include": ["src/**/*.ts"]
}

2. tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "moduleResolution": "bundler",
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "module": "es2022"
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  },
  "files": [],
  "include": [],
  "references": [
    { "path": "./tsconfig.lib.json" },
    { "path": "./tsconfig.spec.json" }
  ]
}

3. ng-package.json

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/libs/your-library",
  "lib": {
    "entryFile": "src/index.ts"
  }
}

Note: We use src/index.ts as the standard entry point for all libraries.

4. package.json

{
  "name": "@your-library",
  "version": "0.0.1",
  "private": true,
  "peerDependencies": {
    "@angular/common": "^20.0.0",
    "@angular/core": "^20.0.0"
  }
}

🚀 Build Commands

Standard Build Commands

# Build specific library
nx run your-library:build

# Build with production configuration
nx run your-library:build:production

# Build with development configuration
nx run your-library:build:development

# Build all libraries
yarn build:libs

Custom Build Script (Alternative)

# Use the custom build script for problematic libraries
node tools/scripts/build-angular-library.js your-library

🔍 Troubleshooting

Common Issues

  1. TypeScript Compilation Errors

    • Check tsconfig.lib.json extends path
    • Ensure base tsconfig exists at ../../tsconfig.base.json
    • Verify all imports are resolvable
  2. Missing Dependencies

    • Check package.json peer dependencies
    • Ensure all imported libraries are built first
    • Use direct import paths instead of aliases if needed
  3. Build Output Issues

    • Verify output path in outputs array
    • Check that dist/libs/your-library directory is created
    • Ensure all required files are copied

Debug Commands

# Build with verbose output
nx run your-library:build --verbose

# Skip cache for fresh build
nx run your-library:build --skip-nx-cache

# Check build output
ls -la dist/libs/your-library/

📚 Examples

Working Libraries

Build Script Pattern

All working libraries follow this pattern:

  1. Use nx:run-commands executor
  2. Direct TypeScript compilation
  3. Asset copying with error handling
  4. Proper output configuration
  5. Development and production configurations

🎯 Best Practices

  1. Always use the recommended build configuration to avoid ng-packagr issues
  2. Test both development and production builds before committing
  3. Use descriptive echo messages for better build monitoring
  4. Handle missing assets gracefully with 2>/dev/null || true
  5. Keep TypeScript configurations simple and consistent
  6. Document any special build requirements in library README

This configuration ensures reliable, fast builds that work consistently across different Node.js versions and environments.

Updated Analysis: TypeScript 5.8.x Compatibility Issue

After upgrading to Node.js 24 and ng-packagr 20.3.0, we discovered that the "Cannot destructure property 'pos'" error is actually a TypeScript 5.8.x compatibility issue, not specifically an ng-packagr problem. The error occurs in the TypeScript compiler when processing complex dependency graphs with referenced files.

Root Cause

Why nx:run-commands Works

The nx:run-commands approach bypasses this TypeScript issue because:

  1. Direct Compilation: Uses tsc directly without ng-packagr's complex file processing
  2. Simpler Pipeline: Avoids the problematic file reference resolution
  3. Better Control: More transparent and debuggable build process

Future Considerations

The nx:run-commands approach remains the most reliable solution for this workspace until the TypeScript compatibility issue is resolved upstream.