Angular-builders: Custom Webpack config for platform-webworker or worker-loader

Created on 8 Aug 2018  Â·  33Comments  Â·  Source: just-jeb/angular-builders

Hello,

I have to run some angular code inside a separated thread. With ng eject being currently disabled, is it possible to use angular-cli-builders in order modify the webpack configuration to use platform-webworker or worker-loader ?

Example: webpack config proposed for worker-loader:
// webpack.config.js { module: { rules: [ { test: /\.worker\.js$/, use: { loader: 'worker-loader' } } ] } }

wontfix

All 33 comments

You should add module.exports = at the beginning of the file. The rest seems all right to me and should work. Doesn't it?

On Wed, Aug 8, 2018, 22:29 Ajyress notifications@github.com wrote:

Hello,

I have to run some angular code inside a separated thread. With ng eject
being currently disabled, is it possible to use angular-cli-builders in
order modify the webpack configuration to use platform-webworker
https://blog.angularindepth.com/angular-with-web-workers-step-by-step-dc11d5872135
or worker-loader https://github.com/webpack-contrib/worker-loader ?

Example: webpack config proposed for worker-loader:
// webpack.config.js { module: { rules: [ { test: /.worker.js$/, use: {
loader: 'worker-loader' } } ] } }

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/meltedspark/angular-cli-builders/issues/13, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AJXjb62mYjxiNqtaW-0GS69PqppoARVtks5uOzwrgaJpZM4V0hwi
.

Thank you for the help, it compiled as expected but in the end I don't think the worker-loader package is the good way to go for angular.

I'm trying to implement platform-webworker following this tutorial. Here is my new extra-webpack.config.js for angular-cli-builders :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const { BaseHrefWebpackPlugin } = require('base-href-webpack-plugin');

module.exports = {
    "entry": {
      "webworker": [
        "./src/workerLoader.ts"
      ]
    },
    "output": {
      "path": path.join(process.cwd(), "dist"),
      "filename": "[name].bundle.js",
      "chunkFilename": "[id].chunk.js"
    },
    "module": { },
    "plugins": [
      new HtmlWebpackPlugin({
        "excludeChunks": [
          "webworker"
        ]
      }),
      new BaseHrefWebpackPlugin({}),
      new AotPlugin({
        "tsConfigPath": 'tsconfig.json',
        "mainPath": "main.ts",
        "entryModule": "app/app.module#AppModule",
      })
    ],
    "optimization": {
        "splitChunks": {
            "name": "inline",
            "minChunks": 1,
            "chunks": "initial"
        }
    }
  };

When running _ng build_ I get the error "An @ngtools/webpack plugin already exist for this compilation"

Are you using replaceDuplicatePlugins option?

I indeed forgot the option replaceDuplicatePlugins = true. Now I don't have any error when running ng build or ng serve, it compiles successfully, thanks a lot!

I still have an error I don't know how to resolve on the browser console, but I don't think it's related to angular-cli-builders

core.js:3697 Uncaught Error: StaticInjectorError(Platform: core)[NgZone]: 
  In this configuration Angular requires Zone.js
    at new NgZone (core.js:3697)
    at createNgZone (platform-webworker.js:1013)
    at resolveToken (core.js:1296)
    at tryResolveToken (core.js:1244)
    at StaticInjector.push../node_modules/@angular/core/fesm5/core.js.StaticInjector.get (core.js:1141)
    at initializeGenericWorkerRenderer (platform-webworker.js:977)
    at platform-webworker.js:999
    at core.js:4200
    at Array.forEach (<anonymous>)
    at createPlatform (core.js:4200)
NgZone @ core.js:3697
createNgZone @ platform-webworker.js:1013
resolveToken @ core.js:1296
tryResolveToken @ core.js:1244
push../node_modules/@angular/core/fesm5/core.js.StaticInjector.get @ core.js:1141
initializeGenericWorkerRenderer @ platform-webworker.js:977
(anonymous) @ platform-webworker.js:999
(anonymous) @ core.js:4200
createPlatform @ core.js:4200
(anonymous) @ core.js:4221
(anonymous) @ core.js:4217
bootstrapWorkerUi @ platform-webworker.js:1782
./src/main.ts @ main.ts:12
__webpack_require__ @ bootstrap:76
0 @ main.ts:15
__webpack_require__ @ bootstrap:76
checkDeferredModules @ bootstrap:43
webpackJsonpCallback @ bootstrap:30
(anonymous) @ main.chunk.js:1
platform-webworker.js:1019 GET http://localhost:4200/webworker.bundle.js 404 (Not Found)

Have you specified the original config for the replaced plugins like it is mentioned in #14?

You are right, i didn't specify the original config for the replaced plugins!

On the links that you give in #14, I see that some plugin options are given by wco: WebpackConfigOptions. I found the interface declaration on build-options.ts but I still have to dive in a little bit to understand how it works :).

After some research, I managed to get rid of the previous error and some others.

I'm now stuck on the exact same error that someone asked of StackOverflow yesterday.

Could you create a minimal reproduction case so that I would be able to investigate?

Thanks again for proposing your help.

Here is a minimal reproduction of the error, with angular CLI 6.1.3:
ng new myWorkerApp
cd myWorkerApp
npm install
npm install --save @angular/platform-webworker @angular/platform-webworker-dynamic
npm i -D angular-cli-builders

Change src/app/app.module.ts with the following code:

import { WorkerAppModule, WORKER_APP_LOCATION_PROVIDERS } from '@angular/platform-webworker';
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common'

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    WorkerAppModule 
  ],
  providers: [
    {
      provide: APP_BASE_HREF,
      useValue: '/'
    },
    WORKER_APP_LOCATION_PROVIDERS
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Change src/main.ts with the following code:

import { enableProdMode } from '@angular/core';
import { bootstrapWorkerUi, WORKER_UI_LOCATION_PROVIDERS } from '@angular/platform-webworker';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

bootstrapWorkerUi('webworker.bundle.js', WORKER_UI_LOCATION_PROVIDERS);

Create src/workerLoader.ts file containing the following code:

import './polyfills.ts';
import '@angular/core';
import '@angular/common';

import { platformWorkerAppDynamic } from '@angular/platform-webworker-dynamic';
import { AppModule } from './app/app.module';

platformWorkerAppDynamic().bootstrapModule(AppModule);

Configure angular.json as following:

//... some stuff...
"build": {
          "builder": "angular-cli-builders:custom-webpack-browser",
          "options": {
            "customWebpackConfig": {
               "path": "./extra-webpack.config.js",
               "replaceDuplicatePlugins": true
            },
            //... some stuff...
"serve": {
          "builder": "angular-cli-builders:generic-dev-server",
          "options": {
            "browserTarget": "myWorkerApp:build"
          },
//... some stuff...

Create an extra-webpack.config.js file with the following code:

const path = require('path');

const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];

const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;


module.exports = {
    target : "web",
    "entry": {
      "main": [
        "./src/main.ts"
      ],
      "polyfills": [
        "./src/polyfills.ts"
      ],
      "styles": [
        "./src/styles.css"
      ],
      "webworker": [
        "./src/workerLoader.ts"
      ]
    },
    "output": {
      "path": path.join(process.cwd(), "dist"),
      "filename": "webworker.bundle.js",
      "chunkFilename": "[id].chunk.js"
    },
    "module": { },
    "plugins": [
      new HtmlWebpackPlugin({
        "template": "./src/index.html",
        "filename": "./index.html",
        "hash": false,
        "inject": true,
        "compile": true,
        "favicon": false,
        "minify": false,
        "cache": true,
        "showErrors": true,
        "chunks": "all",
        "xhtml": true,
        "excludeChunks": [
          "webworker"
        ],
        "title": "Webpack App",
        "chunksSortMode": function sort(left, right) {
          let leftIndex = entryPoints.indexOf(left.names[0]);
          let rightIndex = entryPoints.indexOf(right.names[0]);
          if (leftIndex > rightIndex) {
            return 1;
          }
          else if (leftIndex < rightIndex) {
            return -1;
          }
          else {
            return 0;
          }
        }
      }),
      new AotPlugin({
        "mainPath": "main.ts",
        "entryModule": 'src/app/app.module#AppModule',
        "hostReplacementPaths": {
          "environments/environment.ts": "environments/environment.ts"
        },
        "exclude": [],
        "tsConfigPath": "src/tsconfig.app.json",
        "skipCodeGeneration": true
      })
    ],
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            name: 'vendor',
            test: /\/node_modules\//,
            chunks: 'all',
            priority: 0,
            enforce: true,
          },
        },
      },
    },
  };

Run ng serve

I finally managed to get it work!

I had to add the following option to customWebpackConfig in angular.json:
mergeStrategies": {"optimization":"replace"}

Not related to the issue, i also had to replace webworker.bundle.js with [name].bundle.js in extra-webpack.config.js

Great, happy to hear that! Would you share your final configuration with us, please?

My final configuration:

angular.json:

//... some stuff...
"build": {
          "builder": "angular-cli-builders:custom-webpack-browser",
          "options": {
            "customWebpackConfig": {
               "path": "./extra-webpack.config.js",
               "replaceDuplicatePlugins": true,
               "mergeStrategies": {"optimization":"replace"}
            },
            //... some stuff...
"serve": {
          "builder": "angular-cli-builders:generic-dev-server",
          "options": {
            "browserTarget": "myWorkerApp:build"
          },
//... some stuff...

extra-webpack.config.js:

const path = require('path');

const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];

const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;


module.exports = {
    target : "web",
    "entry": {
      "main": [
        "./src/main.ts"
      ],
      "polyfills": [
        "./src/polyfills.ts"
      ],
      "styles": [
        "./src/styles.css"
      ],
      "webworker": [
        "./src/workerLoader.ts"
      ]
    },
    "output": {
      "path": path.join(process.cwd(), "dist"),
      "filename": "[name].bundle.js",
      "chunkFilename": "[id].chunk.js"
    },
    "module": { },
    "plugins": [
      new HtmlWebpackPlugin({
        "template": "./src/index.html",
        "filename": "./index.html",
        "hash": false,
        "inject": true,
        "compile": true,
        "favicon": false,
        "minify": false,
        "cache": true,
        "showErrors": true,
        "chunks": "all",
        "xhtml": true,
        "excludeChunks": [
          "webworker"
        ],
        "title": "Webpack App",
        "chunksSortMode": function sort(left, right) {
          let leftIndex = entryPoints.indexOf(left.names[0]);
          let rightIndex = entryPoints.indexOf(right.names[0]);
          if (leftIndex > rightIndex) {
            return 1;
          }
          else if (leftIndex < rightIndex) {
            return -1;
          }
          else {
            return 0;
          }
        }
      }),
      new AotPlugin({
        "mainPath": "main.ts",
        "entryModule": 'src/app/app.module#AppModule',
        "hostReplacementPaths": {
          "environments/environment.ts": "environments/environment.ts"
        },
        "exclude": [],
        "tsConfigPath": "src/tsconfig.app.json",
        "skipCodeGeneration": true
      })
    ],
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            name: 'vendor',
            test: /\/node_modules\//,
            chunks: 'all',
            priority: 0,
            enforce: true,
          },
        },
      },
    },
  };

I'm still new to wepback, I can't tell this configuration is optimal or not but it works

Hey I tried creating an Angular app running in a web worker following your configuration but I cannot get it working. All I'm getting is an empty page, the workerLoader.ts is never executed. I assume for some reason the worker isn't loaded properly, but I don't know why.

Did something change in the default plugin config that is overwritten?

I assume you're trying to apply the configuration @Ajyress provided?
If I recall correctly this issue was open before plugins merge support.
Plugins merge is supported now, so you might not need to specify the whole plugin configuration. Although if you do want to replace plugins and did specify replaceDuplicatePlugins flag it should work just as it worked before.

@meltedspark Yeah I was trying @Ajyress configuration. No luck with changing replaceDuplicatePlugins to false either. The worker does not seem to be loaded.

Would you mind providing a repository with minimal reproduction?

Hi,

The configuration changes quite a lot now that there is plugins merge support.
Also now you have to install both @angular-builders/custom-webpack and @angular-builders/dev-server.

Here is a reproduction with angular CLI 6.2.2:
ng new workerAppRepro
cd workerAppRepro
npm install
npm install --save @angular/platform-webworker @angular/platform-webworker-dynamic
npm i -D @angular-builders/custom-webpack
npm i -D @angular-builders/dev-server

Change src/app/app.module.ts with the following code:

import { WorkerAppModule, WORKER_APP_LOCATION_PROVIDERS } from '@angular/platform-webworker';
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common'

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    WorkerAppModule 
  ],
  providers: [
    {
      provide: APP_BASE_HREF,
      useValue: '/'
    },
    WORKER_APP_LOCATION_PROVIDERS
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Change src/main.ts with the following code:

import { enableProdMode } from '@angular/core';
import { bootstrapWorkerUi, WORKER_UI_LOCATION_PROVIDERS } from '@angular/platform-webworker';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

bootstrapWorkerUi('webworker.bundle.js', WORKER_UI_LOCATION_PROVIDERS);

Create src/workerLoader.ts file containing the following code:

import './polyfills.ts';
import '@angular/core';
import '@angular/common';

import { platformWorkerAppDynamic } from '@angular/platform-webworker-dynamic';
import { AppModule } from './app/app.module';

platformWorkerAppDynamic().bootstrapModule(AppModule);

Configure angular.json as following:

//... some stuff...
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
               "path": "./extra-webpack.config.js",
               "mergeStrategies": {
                 "entry": "replace",
                 "output": "replace",
                 "optimization":"replace"
                }
            },
 //... some stuff...
        "serve": {
          "builder": "@angular-builders/dev-server:generic",
          "options": {
            "browserTarget": "workerAppRepro:build"
          },
//... some stuff...

Create an extra-webpack.config.js file with the following code:

const path = require('path');
const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;


module.exports = {
    "entry": {
      "main": [
        "./src/main.ts"
      ],
      "polyfills": [
        "./src/polyfills.ts"
      ],
      "styles": [
        "./src/styles.css"
      ],
      "webworker": [
        "./src/workerLoader.ts"
      ]
    },
    "output": {
      "path": path.join(process.cwd(), "dist"),
      "filename": "[name].bundle.js",
      "chunkFilename": "[id].chunk.js"
    },
    "plugins": [
      new HtmlWebpackPlugin({
        "template": "./src/index.html",
        "filename": "./index.html",
        "excludeChunks": [
          "webworker"
        ],
        "chunksSortMode": function sort(left, right) {
          let leftIndex = entryPoints.indexOf(left.names[0]);
          let rightIndex = entryPoints.indexOf(right.names[0]);
          if (leftIndex > rightIndex) {
            return 1;
          }
          else if (leftIndex < rightIndex) {
            return -1;
          }
          else {
            return 0;
          }
        }
      }),
      new AotPlugin({
        "mainPath": "main.ts",
        "entryModule": 'src/app/app.module#AppModule',
        "exclude": [],
        "tsConfigPath": "src/tsconfig.app.json",
        "skipCodeGeneration": true
      })
    ],
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            name: 'vendor',
            test: /\/node_modules\//,
            chunks: 'all',
            priority: 0,
            enforce: true,
          },
        },
      },
    },
  };

@Ajyress so you're saying it's not working with the new config? Or does it?
Anyways, if you use replaceDuplicatePlugins option it should the same way it worked before (no plugins merge).
Can you confirm it doesn't work even with this option enabled in the new version, while it does work in the old version?
Thanks for cooperation!

it works for me with the new config I gave today.

It also works when I set replaceDuplicatePlugins to true with this exact same configuration.

In this situation, I think it is better not to use replaceDuplicatePlugins as I don't know the exact webpack config provided by angular. Merging the config seems a better option.

@Ajyress great, thank you very much.
@thillmann could you try the exact same configuration and confirm it works for you?

@Ajyress Thanks, I'll give it a try! @meltedspark Yeah will do later! Thanks!

I can confirm the configuration provided here works, applied it to my project just now. The only thing I had to enhance was absolutizing the path to the entryModule in the AotPlugin configuration:

new AotPlugin({
        "entryModule": path.join(__dirname, 'src/app/app.module#AppModule'),

otherwise I kept getting build errors, with complaints that the app/app.module can't be found. Other than that, it just works! Great job and many thanks @Ajyress!

Hmm, now it doesn't work anymore. Strange, as I can't understand the change in my environment that broke it. I can reproduce the error creating an app from scratch (the example from above with workerAppRepro), the build runs fine but I get an error while bootstrapping: Uncaught ReferenceError: window is not defined. I guess it's related to this, but am unable to work around the issue (redefining output.globalObject resolves the error, but the app is not loaded). Maybe some bumped dependency broke it? @Ajyress can you please verify if the steps still work from scratch for you?

How it goes? Is this angular-builders issue or it's just a matter of configuration? If it worked before and is broken in a newer version I should investigate, as there were no breaking changes since then.

It's not working for me any of this example with angular 7.
Steps:
ng new ng722

angular.json config

"build-webpack": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./extra-webpack.config.js",
              "replaceDuplicatePlugins": true,
              "mergeStrategies": {
                "entry": "append",
                "output": "replace",
                "optimization": "replace"
              }
            },
            "outputPath": "dist/ng722",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          }
        },
        "serve-webpack": {
          "builder": "@angular-builders/dev-server:generic",
          "options": {
            "browserTarget": "ng722:build-webpack"
          }
        },

webpack-extra.config.js

const path = require('path');
const entryPoints = ["inline","polyfills","sw-register","styles","vendor","main"];
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;

module.exports = {
  "entry": {
    "webworker": [
      "./src/workerLoader.ts"
    ]
  },
  "output": {
    "path": path.join(process.cwd(), "dist"),
    "filename": "[name].bundle.js",
    "chunkFilename": "[id].chunk.js",
    "globalObject": "this"
  },
  "plugins": [
    new HtmlWebpackPlugin({
      "template": "./src/index.html",
      "filename": "./index.html",
      "excludeChunks": [
        "webworker"
      ],
      "chunksSortMode": function sort(left, right) {
        let leftIndex = entryPoints.indexOf(left.names[0]);
        let rightIndex = entryPoints.indexOf(right.names[0]);
        if (leftIndex > rightIndex) {
          return 1;
        }
        else if (leftIndex < rightIndex) {
          return -1;
        }
        else {
          return 0;
        }
      }
    }),
    new AotPlugin({
      "mainPath": "main.ts",
      "entryModule": 'src/app/app.module#AppModule',
      "exclude": [],
      "tsConfigPath": "src/tsconfig.app.json",
      "skipCodeGeneration": true
    })
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          name: 'vendor',
          test: /\/node_modules\//,
          chunks: 'all',
          priority: 0,
          enforce: true,
        },
      },
    },
  },
};

workerLoader.ts

console.log('worker');
import './polyfills.ts';
import '@angular/core';
import '@angular/common';

import { platformWorkerAppDynamic } from '@angular/platform-webworker-dynamic';
import { AppModule } from './app/app.module';

platformWorkerAppDynamic().bootstrapModule(AppModule);

main.ts

import {enableProdMode} from '@angular/core';
import {bootstrapWorkerUi, WORKER_UI_LOCATION_PROVIDERS} from '@angular/platform-webworker';
import {environment} from './environments/environment';

if (environment.production) {
  enableProdMode();
}

console.log('main');
bootstrapWorkerUi('webworker.bundle.js', WORKER_UI_LOCATION_PROVIDERS);

package.json

"bworker": "ng run ng722:build-worker",
"sworker": "ng run ng722:serve-webpack"

yarn sworker

Open the browser, connect to localhost:4200, and open the console. The app prints:


expected behavior

worker

If I comment all lines of workerLoader.ts except console.log('worker'), then the app prints this log, but if I add only polyfills, no logs printed.

My conclusion: I think this is a webpack problem. Any ideas?

@Serginho I'd assume it's not even a webpack problem but some configuration issue. Since this problem is quite common I'd like to find a solution and add it to the documentation.
Let's try to figure it out together.
Would you mind creating a minimal reproduction repo so that it would be faster to get to the point?
Also, just to confirm - neither build or serve work for you, neither in prod or dev mode, is that right?

Probably someone of Angular CLI team can shed some light on this mystery (how the webworkers should be enabled for Angular 7+). @hansl, @filipesilva, @mgechev, guys, any suggestions?

I'm reopening this issue just to track it, labeling as not-an-issue.

@meltedspark Here is the proof of concept: https://github.com/Serginho/angular-platform-webworker

I can confirm it's not working for build and serve configurations.

The yarn sworker and yarn bworkerto serve and build.

Thanks for helping.

Angular CLI has no support for webworkers currently. https://github.com/angular/angular-cli/pull/12575 aims to support bundling workers, https://github.com/angular/angular-cli/issues/2305 tracks using workers in a CLI app, and https://github.com/angular/angular-cli/issues/2305 tracks running Angular in a web worker.

For anyone who's coming to this thread looking to run part of his application in WebWorker - it is officially supported since Angular 8: https://angular.io/guide/web-worker

Did you do some tests? @meltedspark angular 8 deprecates platform-worker, we dont have any guarantee if it will works properly and sngular cli 8 autobundle workers but it's not compatible with lazy loading.

I personally did not do any tests but this guy definitely did.

@meltedspark This guy bundles stuff in a workers. But it didn't test platform-webworker.

I pretty sure angular-cli won't work with lazy loading because you need two webpack targets amd angular-cli only supports one (target=web). To say webpack: hey compiles me this part with target=web and this other part with target=webworker you will have lots of changes in the CLI. That's the problem.

More info: https://webpack.js.org/concepts/targets/

Hi @meltedspark, I have a question with custom web worker in angular 8, could you provide some advices?
https://stackoverflow.com/questions/56785853/how-to-custom-web-worker-by-angular-builder-in-angular-8

Was this page helpful?
0 / 5 - 0 ratings