0

I want to use a library in Angular. (The npm package is cubing for reference). This library can run both in the browser or in node and has some specific code to both. I want it to run in the browser, but Angular compilation doesn't work because it can't find worker_threads. I asked the library owner and he said the intended solution is to tell your build system that this import should be ignored because it's only relevant for the node variant of the code.

But I can't figure out how to tell Angular this. How do I tell it: Please ignore this import in this node module, we're never going to reach the code that uses it?

Error: Can't resolve 'worker_threads' in REDACTED/node_modules/cubing/dist/esm

If that's not possible, I guess I could consider doing a node_modules patch, but I dislike that idea, for obvious reasons. And I heard it's hard to get it to work in production environments.

For reference, this is the github project (switch to branch scrambles for the problem at hand): https://github.com/Lykos/cube_trainer.git

And here the most relevant files:

Package.json:

{
  "name": "cube-trainer",
  "private": true,
  "author": "Bernhard F. Brodowsky",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/Lykos/cube_trainer.git"
  },
  "engines": {
    "node": "16.x",
    "npm": "8.x"
  },
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --configuration production",
    "build_development": "ng build --configuration development",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "test_ci": "ng test --karma-config client/karma-ci.conf.js",
    "lint": "ng lint"
  },
  "dependencies": {
    "@angular/animations": "^13.0.2",
    "@angular/cdk": "^13.0.2",
    "@angular/common": "^13.0.2",
    "@angular/compiler": "^13.0.2",
    "@angular/core": "^13.0.2",
    "@angular/forms": "^13.0.2",
    "@angular/material": "^13.0.2",
    "@angular/platform-browser": "^13.0.2",
    "@angular/platform-browser-dynamic": "^13.0.2",
    "@angular/router": "^13.0.2",
    "@ngrx/component-store": "^13.0.2",
    "@ngrx/effects": "^13.0.2",
    "@ngrx/store": "^13.0.2",
    "@ngrx/store-devtools": "^13.0.2",
    "@rxweb/reactive-form-validators": "^2.1.6",
    "actioncable": "^5.2.6",
    "angular-token": "^7.0.1",
    "cubing": "^0.22.0",
    "file-saver": "^2.0.5",
    "ngx-cookie-service": "^13.0.1",
    "ngx-filesaver": "^12.0.0",
    "rxjs": "~7.4.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~13.0.1",
    "@angular-eslint/builder": "12.6.1",
    "@angular-eslint/eslint-plugin": "12.6.1",
    "@angular-eslint/eslint-plugin-template": "12.6.1",
    "@angular-eslint/schematics": "12.6.1",
    "@angular-eslint/template-parser": "12.6.1",
    "@angular/cli": "~13.0.1",
    "@angular/compiler-cli": "~13.0.0",
    "@ngrx/schematics": "^13.0.2",
    "@types/actioncable": "^5.2.7",
    "@types/chai": "^4.2.22",
    "@types/file-saver": "^2.0.4",
    "@types/jasmine": "~3.10.0",
    "@types/node": "^12.11.1",
    "@typescript-eslint/eslint-plugin": "4.28.2",
    "@typescript-eslint/parser": "4.28.2",
    "angular-http-server": "^1.10.0",
    "chai": "^4.3.4",
    "eslint": "^7.26.0",
    "jasmine-core": "~3.10.0",
    "karma": "~6.3.0",
    "karma-chai": "^0.1.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "~1.7.0",
    "karma-mocha": "^2.0.1",
    "mocha": "^9.1.3",
    "typescript": "~4.4.4"
  }
}

angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "cube-trainer": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "client/src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "./public",
            "index": "client/src/index.html",
            "main": "client/src/main.ts",
            "polyfills": "client/src/polyfills.ts",
            "tsConfig": "client/tsconfig.app.json",
            "assets": [
              "client/src/favicon.ico",
              "client/src/assets"
            ],
            "styles": [
              "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
              "client/src/styles.css"
            ],
            "scripts": [],
            "webWorkerTsConfig": "client/tsconfig.worker.json"
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "4mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "client/src/environments/environment.ts",
                  "with": "client/src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true,
              "outputHashing": "none"
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "proxyConfig": "client/src/proxy.conf.json"
          },
          "configurations": {
            "production": {
              "browserTarget": "cube-trainer:build:production"
            },
            "development": {
              "browserTarget": "cube-trainer:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "cube-trainer:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "client/src/test.ts",
            "polyfills": "client/src/polyfills.ts",
            "tsConfig": "client/tsconfig.spec.json",
            "karmaConfig": "client/karma.conf.js",
            "assets": [
              "client/src/favicon.ico",
              "client/src/assets"
            ],
            "styles": [
              "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
              "client/src/styles.css"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-eslint/builder:lint",
          "options": {
            "lintFilePatterns": [
              "client/src/**/*.ts",
              "client/src/**/*.html"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "cube-trainer",
  "cli": {
    "defaultCollection": "@ngrx/schematics"
  }
}

tsconfig.json

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "src/",
    "paths": {
      "@environment": ["environments/environment"],
      "@shared/*": ["app/shared/*"],
      "@utils/*": ["app/utils/*"],
      "@store/*": ["app/store/*"],
      "@effects/*": ["app/effects/*"],
      "@core/*": ["app/core/*"],
      "@training/*": ["app/training/*"]
    },
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2020",
      "dom"
    ],
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "strict": true,
    "skipLibCheck": true
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

tsconfig.app.json

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": []
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]
}
Lykos
  • 1,039
  • 6
  • 25
  • What builder/bundler are you using? Angular is the library – Luke Miles Jan 04 '22 at 18:28
  • 1
    I use npm, ng build and ng serve and pretty much the defaults of what Angular tutorials tell me to use. If any of my package.json or angular.json or tsconfig.json are important , let me know and I will add them. – Lykos Jan 04 '22 at 18:32
  • 1
    Ok, added all the files that I could think of that might be relevant and a link to Github. Let me know if anything else is useful. But as I understand it, the answer to your question is that I use the Angular buidl tools. – Lykos Jan 04 '22 at 19:12

1 Answers1

0

Looks like you might be able to exclude it in the tsconfig or in the angular.json, maybe try both?

Exclude files from build in Angular 2

// tsconfig
{
   "exclude": [
       "node_modules/thepath/tothedependency/orfile/using/worker_threads/I_guess",
   ]
}
// angular.json
...
          "configurations": {
            "production": {
              "assets": [
                {
                  "ignore": [
                    "**/test.json",
                    "**/test"
                  ],
                },
...

Something like that

You can also install a browser wrapper for worker_threads then configure angular build / webpack to substitute the old import for the new one.

If you do decide to patch it it might not be too bad. Forking on github and installing from your github fork is pretty easy. Or else saving a git patch file for the change and automatically applying that patch in your install step isn't crazy either.

This kind of thing is generally a pain in the ass, in esbuild and rollup and stuff too.

You can also modify your webpack config, which I think ng build is using under the hood, it's probably in /node_modules/angular/somejunk/config/webpack.config.json or something and add this:

Webpack ignore import

module.exports = {
  //...
  externals: {
    fetch: 'cross-fetch'
  }
};

But then your back to applying a patch or using a git branch

Luke Miles
  • 941
  • 9
  • 19
  • Hey, thanks a lot for your options. I didn't quite manage to get it to work yet though. I tried: * exclude in tsconfig doesn't work. Apparently that option is commonly misunderstood and isn't really excluding things in general, it's just removing those from the "includes" option. * What you suggested for angular.json causes parse errors. But my assets are only two files and this library isn't part of it, so I don't expect this to do much. * I tried patching using the patch-package package, but somehow ng serve seems to ignore the patches. * There is no webpack config in node_modules. – Lykos Jan 04 '22 at 20:45
  • 1
    Ah wow, but your hint with the webpack config did help me solve the issue! I stumbled upon the @angular-builders/custom-webpack in your referenced question and that worked! Thanks! – Lykos Jan 04 '22 at 20:55