14

Can I import a library installed with npm into a web worker?

I need to use the moment.js library into a web worker.

It is installed via npm into the node_modules/moment directory

I already have tried with this at the top of the worker.js file:

importScripts('/node_modules/moment/moment.js');

But I get

GET http://192.168.2.1:8100/node_modules/moment/moment.js 404 (Not Found)
kidroca
  • 3,480
  • 2
  • 27
  • 44
Hanzo
  • 1,839
  • 4
  • 30
  • 51

3 Answers3

10

Yes, it's possible, chances are you're already using a popular bundler like webpack or parcel, but even if you're not it's still possible, though probably not directly from node_modules

With Parcel

main.js
// relative path to the worker from current file
const worker = new Worker('../utils/myWorker.js');
myWorker.js
// use import like you would in any other file
import moment from 'moment';

console.log(`From worker: worker started at ${moment().format('HH:mm:ss')}`);

With Webpack (as entry)

main.js
// relative to the expected public path (have in mind any filename transforms like hashing)
const worker = new Worker('myWorker.bundle.js');
myWorker.js
// use import like you would in any other file
import moment from 'moment';

console.log(`From worker: worker started at ${moment().format('HH:mm:ss')}`);

webpack.config.js

{
  entry: {
    main: './src/app/main.js'
    worker: './src/utils/myWorker.js',
  },
  output: {
    path: `${ROOT_PATH}/public`,
    filename: '[name].bundle.js',
  }
}

With Webpack (worker-loader)

Install the loader

main.js
// relative path to the worker from current file
import Worker from '../utils/myWorker.worker.js';

const worker = new Worker();
myWorker.worker.js
// use import like you would in any other file
import moment from 'moment';

console.log(`From worker: worker started at ${moment().format('HH:mm:ss')}`);

webpack.config.js

{
  module: {
    rules: [
      {
        test: /\.worker\.js$/,
        use: { loader: 'worker-loader' }
      }
    ]
  }
}

With CRA (Create React App) (worker-loader) inline loader

Using an inline loader

main.js
/* eslint-disable import/no-webpack-loader-syntax */
import Worker from "worker-loader!./myWorker.worker.js";

const worker = new Worker();
myWorker.worker.js
// use import like you would in any other file
import moment from 'moment';

console.log(`From worker: worker started at ${moment().format('HH:mm:ss')}`);

You can always import the library from a CDN

myWorker.js
importScripts('//cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js');

console.log(`From worker: worker started at ${moment().format('HH:mm:ss')}`);

Other bundlers

  • Web workers cannot importScripts from parent folders for security reasons
  • The node_modules folder is usually at the root of the project so you can't access it with importScripts
  • The bundler needs to be configured so that the content can be aliased or copied to a location the worker can access

AngularCLI, Ionic, CRA

For projects using webpack as a bundler the 2 webpack solutions can be adapted as long as you can access the webpack config and customize it. If you can't access the config (CRA) you can use the inline loader from the "With CRA" example

The "Webpack (as entry)" example was actually borrowed from Angular CLI generated app with Web Workers

  • It explains how to modify the setup to bootstrap angular using web workers
  • It have been referenced as a webworker solution for Ionic projects too

Note on TypeScript

{
  "extends": "../generic-tsconfig.json",
  "compilerOptions": {
    "lib": ["esnext", "webworker"],
  }
}
kidroca
  • 3,480
  • 2
  • 27
  • 44
  • Tried both for webpack and neither worked. For the entry version, the bundle file was never produced and I couldn't do anything with the worker. With the `web-worker`, it says `Attempted import error: './myWorker.worker.js' does not contain a default export (imported as 'Worker').` – libby Oct 27 '21 at 00:44
  • Maybe you mean `worker-loader` there's no `web-worker`? From the error message it seems there might be a problem with your `worker-loader` configuration in `webpack.config.js` - the imported file `./myWorker.worker.js` is treated as a regular `js` import and it's not loaded through the `worker-loader` hence the "no default export" error. You might try the inline loader (it should take the highest precedence) - `import Worker from "worker-loader!./myWorker.worker.js";` - if this works you have a problem in the webpack configuration for the loader. https://www.npmjs.com/package/worker-loader – kidroca Oct 27 '21 at 09:08
  • I tried this and got: `Line 5:1: Unexpected '!' in 'worker-loader!./myWorker.worker.js'. Do not use import syntax to configure webpack loaders import/no-webpack-loader-syntax` – libby Oct 27 '21 at 14:29
  • 1
    Then, I put this line at the top of my file: `/* eslint import/no-webpack-loader-syntax: off */` And it seems to be **working**. – libby Oct 27 '21 at 14:37
2

As of 12 August 2020. I have been successful using npm modules with CloudFlare Workers by doing these steps.

Create new project:

wrangler generate your-project
cd your-project

Set 'wrangler.toml' to use webpack:

name = "your-project"
type = "webpack"
account_id = "your-account-id"
workers_dev = true
route = ""
zone_id = ""

Import your npm modules into the index.js file: eg.

const qr = require('qr-image')

In terminal / cmd:

wrangler publish

It automatically creates the worker packaged using webpack. Now it should just work.

Hope this helps someone, was looking on google for ages and then just decided to try the tutorials and found this out!

cuznerdexter
  • 586
  • 5
  • 21
1

For webpack 5 and onwards, you can use web workers and import libraries without using worker-loader or any bundler in simple way as mentioned here

I managed to run my web worker and import libraries in same way.

In the file you want to import web worker

// here ./deep-thought.js is the path to the web worker

const worker = new Worker(new URL('./deep-thought.js', import.meta.url));

worker.postMessage({
   question: 'The Answer to the Ultimate Question of Life, The Universe, and Everything.',
});
worker.onmessage = ({ data: { answer } }) => {
   console.log(answer);
};

In the web worker file,

// just to show it supports importing files into web worker in generic way

import JSONstream from 'JSONStream';
    
self.onmessage = function (e) {
    // worker code inside
    // just to show it supports importing and using files into web worker in generic way
   const jsonParser = JSONstream.parse();
}
SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34