12

I'm trying to move the whole execution of an Angular 2 app to a web worker, but I've find out that all examples available right now are using System.js, and I'm trying to do so with a WebPack based project (built with angular-cli).

Has anyone done this or have an idea on how to do this with WebPack?

Below is my main.ts bootstrap file:

import './polyfills.ts';


import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';


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

import {bootstrapWorkerUi} from '@angular/platform-webworker';
bootstrapWorkerUi('/app/loader.js');

My loader.js looks like this:

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

platformWorkerAppDynamic().bootstrapModule(AppModule);

And my app.module.tsis declaring @NgModulethis way:

import { NgModule } from '@angular/core';
import {WorkerAppModule} from '@angular/platform-webworker';


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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    WorkerAppModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
 export class AppModule { }

However, when I run it, I get the error Uncaught SyntaxError: Unexpected token import in loader.js:1.

The issue seems related with webpack and some lack of imports of @angular libs in the webworker side, as suggested by Tamas Gegedus and MathMate, so the next step is to modify the webpack config file in order to provide all the required libs to the webworker. I'll work on that.

----------- UPDATE -----------

Since angular-cli do not provide access to webpack.config.js file, I've included mine, making little changes in code so it can work. I created the second entry point at webpack config file and I'm getting the error VM33:106 Uncaught ReferenceError: window is not defined.

Webpack config file looks like this:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var helpers = require('./config/helpers');


module.exports = {
  entry: {
    'app': ['./src/polyfills.ts', './src/vendor.ts', './src/main.ts'],
    'webworker': ['./src/workerLoader.ts']
  },

  resolve: {
    extensions: ['', '.ts', '.js']
  },

  output: {
    path: helpers.root('dist'),
    publicPath: 'http://localhost:8080/',
    filename: '[name].js'
  },

  devtool: 'cheap-module-eval-source-map',

  module: {
    loaders: [
      {
        test: /\.ts$/,
        loaders: ['awesome-typescript-loader?tsconfig=./src/tsconfig.json', 'angular2-template-loader']
      },
      {
        test: /\.html$/,
        loader: 'html'
      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        loader: 'file?name=assets/[name].[hash].[ext]'
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw'
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      excludeChunks: ['webworker']
    }),

    new ExtractTextPlugin('[name].css')
  ]
};

where polyfills.ts and vendor.ts include polyfills and angular libs respectively.

I've created 2 entry points, that generate 2 outputs (app.js and webworker.js).

With the HtmlWebpackPlugin, the first one is included in the index.html file, but not the second one.

When index.html is included, main.ts (contained in app.js) is called and tries to bootstrap the webworker using the second output of webpack (webworker.js):

import {bootstrapWorkerUi} from '@angular/platform-webworker';
bootstrapWorkerUi('../webworker.js');

webworker.js has been generated by webpack using workerLoader.ts entry, that is like previous loader.js, but including some imports.

WorkerLoader.ts file looks like this:

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

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

platformWorkerAppDynamic().bootstrapModule(AppModule);

I know that the web worker is been generated, as shown in the following image, but as shown in the image, I get the error VM33:106 Uncaught ReferenceError: window is not defined, and nothing is displayed except the Loading message from index.html. web worker instance

On the other side, I think that the webpack config is OK because if running the app with webpack in NO webworker mode, it works fine.

Right now I think that this issue is related with webworker bootstrap, but I'm quite stucked here.

Any help would be appreciated ;)

Enrique Oriol
  • 1,730
  • 1
  • 13
  • 24
  • That means you did not reference the webpack compiled bundle in your html, but the original es6 files. – Tamas Hegedus Oct 19 '16 at 11:29
  • The app worked fine when set up as a single thread (with typical bootstrap and already using WebPack), the problem appeared when introducing the changes I reflected in the code in order to run it inside a webworker. – Enrique Oriol Oct 19 '16 at 22:07
  • I managed to build a project and reproduce your issue: so far, I understood there was a reference to ie-shim, which adds something to the window object thus breaking the runtime. – user2363245 Nov 16 '16 at 11:24
  • However, I'm still getting the issue cause of some css-loaders - keep investigating – user2363245 Nov 16 '16 at 11:25
  • I was able to get rid of "window is undefined" by not using webpack-dev-server as well, but now I'm stuck with "No provider for PlatformLocation", even if @angular/common CommonModule is present. – user2363245 Nov 16 '16 at 12:22
  • OK, now working. Used angular/angular2-seed as baseline and https://github.com/angular/angular/blob/master/modules/playground/src/web_workers/router/index.ts to configure main and loader. I'll post the repo URL as soon as I can upload it – user2363245 Nov 17 '16 at 00:50
  • Awesome! need to see how you did it, please. struggling with this. – user3087827 Nov 22 '16 at 15:48
  • Same, this will be super helpful. Would love to see the repo and code! – Primm Nov 23 '16 at 00:47
  • I was very close to the solution. Check my answer below ;) – Enrique Oriol Nov 24 '16 at 17:22
  • @user2363245 how did you manage to get around "no provider for PlatformLocation" ? Thanks – NewtonCode Dec 17 '16 at 17:52

2 Answers2

9

I finally solved the issue :)

NOTE: if you have generated the project using Angular CLI 1.0 or higher, you can now use their own webpack config file, and that simplifies the whole process. Check this answer in that case.

After running the code with my custom webpack config, I realized that the error VM33:106 Uncaught ReferenceError: window is not defined was caused by webpack-dev-server.

I solved it by:

1 - doing a standalone webpack build

$: webpack --watch #this creates the bundes at myProject/dist

and

2 - running with another devserver tool like simplehttpserver

$: npm install simplehttpserver -g
$: simplehttpserver -p 8080 dist/ #as my webpack bundle is included in index.html using that port by default

And voilà, the error is gone and it simply works!

 

Changes applied to angular-cli project: Webpack config

Let me summarize the webpack changes regarding the original Angular-CLI project.

As angular-cli do not provide access to webpack.config.js file and customizing webpack is key to use webworkers, I've included mine, making little changes in code.

Webpack config file looks like this:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var helpers = require('./config/helpers');


module.exports = {
  entry: {
    'app': ['./src/polyfills.ts', './src/vendor.ts', './src/main.ts'],
    'webworker': ['./src/workerLoader.ts']
  },

  resolve: {
    extensions: ['', '.ts', '.js']
  },

  output: {
    path: helpers.root('dist'),
    publicPath: 'http://localhost:8080/',
    filename: '[name].js'
  },

  devtool: 'cheap-module-eval-source-map',

  module: {
    loaders: [
      {
        test: /\.ts$/,
        loaders: ['awesome-typescript-loader?tsconfig=./src/tsconfig.json', 'angular2-template-loader']
      },
      {
        test: /\.html$/,
        loader: 'html'
      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        loader: 'file?name=assets/[name].[hash].[ext]'
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw'
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      excludeChunks: ['webworker']
    }),

    new ExtractTextPlugin('[name].css')
  ]
};

where polyfills.ts and vendor.ts include polyfills and angular libs respectively.

I've created 2 entry points, that generate 2 outputs (app.js and webworker.js).

With the HtmlWebpackPlugin, the first one is included in the index.html file, but not the second one.

When index.html is included, main.ts (contained in app.js) is called and bootstraps the webworker like this:

import {bootstrapWorkerUi} from '@angular/platform-webworker';
bootstrapWorkerUi('../webworker.js');

webworker.js is the code webpack generated using workerLoader.ts entry.

WorkerLoader.ts file looks like this:

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

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

platformWorkerAppDynamic().bootstrapModule(AppModule);

And AppModule looks like this:

import { NgModule } from '@angular/core';
import {WorkerAppModule} from '@angular/platform-webworker';


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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    WorkerAppModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
 export class AppModule { }

Code example

To make easier to understand the differences, I've created a project showing how to convert a regular angular 2 project in a web workers based one. Check this repo: https://github.com/kaikcreator/webWorkerFactorialExample

You'll see in master branch the "single thread" code, and in webWorkers branch, the changes to run code using web workers.

Community
  • 1
  • 1
Enrique Oriol
  • 1,730
  • 1
  • 13
  • 24
  • 1
    i tried your solution and still have the 'window not defined' error in the worker. The problem seems to be that in the webworker.js bundles created , webpack have references to window object which ties itself to UI thread. Could you help me with this? – NewtonCode Dec 17 '16 at 17:43
  • Are you using webpack dev server to run the app? – Enrique Oriol Dec 17 '16 at 17:47
  • I resolved that issue when moving away from webpack-dev-server. Maybe you are including en webworker.js something that shouldn't be there. I've updated the response with the URL to an example repo that uses angular 2 webworkers with webpack. Probably you can find there your response. – Enrique Oriol Dec 18 '16 at 21:24
  • @Ced This should run much faster on mobile: https://medium.com/@areai51/the-4-stages-of-perf-tuning-for-your-angular2-app-922ce5c1b294#.tkquhoy3h – Tomer Almog Jan 20 '17 at 22:42
  • Angular CLI allows ejecting the Webpack configuration by calling `ng eject` starting from 1.0.0-beta.38: https://github.com/angular/angular-cli/issues/2047 – christianliebel Feb 20 '17 at 22:26
  • 1
    @chliebel I've created a new SO with the answer for the case of using CLI 1.0+ so you can take advantage of the webpack config file created from `ng eject`. Check it here: http://stackoverflow.com/questions/43276044/angular-cli-generated-app-with-web-workers/43276045#43276045 – Enrique Oriol Apr 07 '17 at 10:47
  • How would you manage external plugins that get loaded by a component that search access to the DOM on init? If all is in a webworker, so are external plugins required in the components – DutchKevv Jun 21 '17 at 23:29
  • If the plugins are built with Angular and are well designed, they won't access the DOM directly but will be render agnostic, so no problem on that case. Otherwise...I don't think you can use them :( – Enrique Oriol Jun 22 '17 at 08:46
1

As Tamas Hegedus has already mentioned in comment, most likely your webpack bundle is still referring es6 file for loader.js.

You need to create separate bundle with entry point for loader script.

If you share your webpack config then it would be easier to tell where the things might be going wrong.

Also your loader script needs to have required polyfills. You can add

import 'core-js/es7/reflect'; import 'zone.js/dist/zone';

MathMate
  • 11
  • 1
  • 1
  • Ok, I already thought the issue was on that direction, but your comment makes it much more clear. The problem is that angular-cli uses webpack internally, but without any webpack.config.js file. I'll figure out how to use it with a config file and how to define the new entry point in order to leave here the solution. – Enrique Oriol Oct 24 '16 at 11:56
  • I tried to use the second entry point for loader script, but now I'm facing the error "VM33:106 Uncaught ReferenceError: window is not defined" in the web worker. I've updated the question to share all the details. – Enrique Oriol Nov 04 '16 at 11:11