Search code examples
typescriptwebpackweb-worker

Using WebWorkers in Typescript with Webpack and worker-loader without custom loader strings


I'm trying to get web workers playing nice with Typescript and Webpack's worker-loader. There's an example in their documentation of achieving this through a custom module declaration, however this relies on using the webpack worker-loader!./myWorker syntax.

I want to be able to load workers through a custom *.worker.js webpack use rule rather than explicitly listing the loader in the import string. Something about typescript doesn't seem to like defining or modifying a modules declaration if it's a plain relative import, and gets stuck on the fact that the worker file is not a module, even with a module declaration for it.

I have a worker that looks like

// test.worker.ts
/// <reference lib="webworker" />

const worker: DedicatedWorkerGlobalScope = self as any;

worker.onmessage = ({ data }) => {
  if (data instanceof Array) {
    worker.postMessage(data.join(' ') + '!');
  }
};

A declaration that looks like

// custom.d.ts
declare module '*.worker' {
  class TestWorker extends Worker {
    constructor();
  }

  export default TestWorker
}

And is used in my main app as

import TestWorker from './test.worker';

const testWorker = new TestWorker();
testWorker.onmessage = ({ data }: { data: string }) => {
  console.log(data);
};

testWorker.postMessage([
  'hello',
  'I',
  'am',
  'a',
  'web',
  'worker',
]);

The error output is

TypeScript error in /worker-test/src/index.tsx(9,24):File '/worker-test/src/test.worker.ts' is not a module.  TS2306

Changing the import to worker-loader!./test.worker seems to work and gets typescript to understand the custom declaration, however I'm really trying to avoid using custom loader strings as the intention is to integrate this into create-react-app which doesn't allow these.

Is there a way to get regular relative imports to recognise a custom module declaration?


Solution

  • You were almost there!

    Change the module declaration to include the file extension:

    // custom.d.ts
    declare module '*.worker.ts' {
      class TestWorker extends Worker {
        constructor();
      }
    
      export default TestWorker
    }
    

    And your import to also include the file extension:

    import TestWorker from './test.worker.ts';
    

    This did it for me - specifying the extension in the import stops TS from importing it as a module, and specifying the extension in the declaration stops TS from complaining since you're importing a .ts file by also specifying the extension.