Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration with Create React App #5

Closed
craigcoles opened this issue Jan 11, 2018 · 16 comments
Closed

Integration with Create React App #5

craigcoles opened this issue Jan 11, 2018 · 16 comments

Comments

@craigcoles
Copy link

craigcoles commented Jan 11, 2018

I need to setup a worker in CRA. I have ejected CRA, so I can add the loader via Webpack configs.

However, I am not sure I am doing this correctly. I have been using the test/webpack.config.js as a helper. The only thing I noticed that was different, was the resolveLoader section in the webpack config. Have I missed something else?

Current config:

module.exports = {
  ...
  resolveLoader: {
    alias: {
      'workerize-loader': path.resolve(__dirname, 'workerize-loader')
    }
  },
  ...
};
@developit
Copy link
Owner

Hmm - you shouldn't need any resolution config at all to use this.

npm i -D workerize-loader
import worker from 'workerize-loader!./foo';

@craigcoles
Copy link
Author

I did try that, but CRA has rules setup:

Unexpected '!' in 'workerize-loader!Workers/Worker'. Do not use import syntax to configure webpack loaders

@developit
Copy link
Owner

developit commented Jan 12, 2018

Eek! That's a surprisingly draconian lint rule.. maybe try with require?

const worker = require('workerize-loader!Workers/Worker')

@ddebarros
Copy link

CRA uses eslint so just suppress that rule for the import line.

import worker from 'workerize-loader!./foo'; // eslint-disable-line import/no-webpack-loader-syntax

@developit
Copy link
Owner

Perfect! Thanks for the tip, closing this issue then.

@kentcdodds
Copy link

Related: facebook/create-react-app#7741

@wilcoxmd
Copy link

Has anyone gotten workerize-loader to work with create-react-app and typescript?

I'm using react-scripts version 3.2, so should have the PR related to facebook/create-react-app#7741 included in my build. However, I keep running into a typescript error: Cannot find module 'workerize-loader!./example.worker'.

I'm importing like so:

// in ./Component.tsx
import ExampleWorker from 'workerize-loader!./example.worker'; // eslint-disable-line import/no-webpack-loader-syntax

// ./example.worker.ts
interface IWorkerMessage {
  message: string;
  time: number;
}

export const sayHi = async ({ message, time }: IWorkerMessage) => {
  console.log('in worker function!, got message, ', message);
  const start = Date.now();
  let count: number = 0;
  while (Date.now() - start < time) {
    count++;
  }
  return count;
};

Anyone else seen this error / overcome it?

@stevenmcdonald
Copy link

@wilcoxmd
It's not pretty, but I was able to get it to work using a shim js file to load the worker and export it to the TS file:

// in ./ComponentWorkerShim.js
import ExampleWorker from 'workerize-loader!./example.worker'; // eslint-disable-line import/no-webpack-loader-syntax

export default ExampleWorker
// in ./Component.tsx
import ExampleWorker from './ComponentWorkerShim';

// ...

Then you have type issues to deal with when calling the worker, but at least it works.

@MikeSpitz
Copy link

MikeSpitz commented Dec 3, 2019

The above comment works great but I'm running into issues with jest when I use the shim. Without the shim jest works fine but typescript doesn't 😢

Edit
I was able to get this running without the shim by using require:

Component.tsx

const Worker = require( "workerize-loader!../../workers/workerFileName"); // eslint-disable-line import/no-webpack-loader-syntax

src/workers/workerFileName.js

export async function doStuff(data) {
    // Worker code here
}

package.json

jest {
     "moduleNameMapper": {
       "workerize-loader!../../workers/workerFileName":"<rootDir>/src/workers/workerFileName.mock.js"
    },
}

src/workers/workerFileName.mock.js

class WorkerFileName {

}

module.exports = WorkerFileName;

Now I have the following:

  • Workers work in create-react-app with typescript
  • Components that include the workerize-loader file no longer fail in jest

@wilcoxmd
Copy link

wilcoxmd commented Jan 3, 2020

I think I found another solution here as well, by using a hook for each worker I create. Using some of your tips above about the jest config, and some tips from here, I have the following that works for me and allows typed usage:

src/workers/sampleWorker.worker.ts

export function foo(a: number, b: number) {
  return `Worker result: ${a + b}`;
}

src/workers/workerDeclarations.d.ts

declare module 'workerize-loader!*' {
  type AnyFunction = (...args: any[]) => any;
  type Async<F extends AnyFunction> = (
    ...args: Parameters<F>
  ) => Promise<ReturnType<F>>;

  type Workerized<T> = Worker &
    { [K in keyof T]: T[K] extends AnyFunction ? Async<T[K]> : never };

  function createInstance<T>(): Workerized<T>;
  export = createInstance;
}

src/hooks/useSampleWorker.ts

// eslint-disable-next-line import/no-webpack-loader-syntax
import createWorker from 'workerize-loader!../workers/sampleWorker.worker';
import * as SampleWorker from '../workers/sampleWorker.worker';

const sampleWorker = createWorker<typeof SampleWorker>();

export const useSampleWorker = () => sampleWorker;

Component.tsx

import {useSampleWorker} from '../hooks/useSampleWorker';

function Component(props) { 
    const sampleWorker = useSampleWorker();
    
    // ...
    sampleWorker.foo(1000, 1000).then(result => console.log(result)) // result is of type 'string'
}

then to make tests work....
src/workers/mocks/sampleWorker.mock.ts

import { foo } from '../sampleWorker.worker';

export default () => ({
  foo,
});

package.json

"jest": {
    "moduleNameMapper": {
      "workerize-loader!../workers/sampleWorker.worker": "<rootDir>/src/workers/mocks/sampleWorker.mock.ts"
    }
}

@kentcdodds
Copy link

Awesome. Instead of moduleNameMapper I'd probably just do jest.mock. Glad you got that working and thanks for coming back here to show us how you did it.

@wilcoxmd
Copy link

wilcoxmd commented Jan 3, 2020

@kentcdodds No problem! This one has bugged me for a bit because I've really wanted to use this package :)

I had a little trouble getting jest.mock to work for me. What were you picturing? If I try to use jest.mock in my tests I was getting more 'unresolved' module errors.

An alternative, more flexible option I did think of though is to better use the regex in the moduleNameMapper....

  "jest": {
    "moduleNameMapper": {
      "^workerize-loader!(.*)/workers(.*)": "<rootDir>/src/workers/__mocks__$2"
    }
  }

and to make the actual mock file more easy to work with...
in src/workers/__mocks__/sampleWorker.worker.ts

import * as SampleWorker from '../sampleWorker.worker';

export default () => SampleWorker

That way any worker you add into the /workers directory just needs a corresponding mock like the one above and you shouldn't really have to touch it again?

@aeroxy
Copy link

aeroxy commented May 15, 2020

Cannot find module 'workerize-loader!./workerize'.ts(2307)

TypeScript also doesn't like it.

@Download
Copy link

Download commented Sep 7, 2020

CRA uses eslint so just suppress that rule for the import line.

When I do, it works in development.
However when I then build the project, it fails without errors. It just doesn't generate anything.
Took me a few hours to figure out why.
Uncommenting the workerize loader import fixed the build right away.
So think twice before using this.

@lukesmurray
Copy link

lukesmurray commented Nov 10, 2020

src/workers/workerDeclarations.d.ts

declare module 'workerize-loader!*' {
type AnyFunction = (...args: any[]) => any;
type Async = (
...args: Parameters
) => Promise<ReturnType>;

type Workerized = Worker &
{ [K in keyof T]: T[K] extends AnyFunction ? Async<T[K]> : never };

function createInstance(): Workerized;
export = createInstance;
}

Using a flattened promise so async methods aren't double nested as Promise<Promise<return value>>

declare module "workerize-loader!*" {
  type FlattenedPromise<T> = unknown extends T
    ? Promise<T>
    : T extends Promise<infer U>
    ? T
    : Promise<T>;

  type AnyFunction = (...args: any[]) => any;
  type Async<F extends AnyFunction> = (
    ...args: Parameters<F>
  ) => FlattenedPromise<ReturnType<F>>;

  type Workerized<T> = Worker &
    { [K in keyof T]: T[K] extends AnyFunction ? Async<T[K]> : never };

  function createInstance<T>(): Workerized<T>;
  export = createInstance;
}

@rdgfuentes
Copy link

Fixed it following @wilcoxmd's solution described above and also applied the alternative option but with a slightly tweak in the moduleNameMapper regexp :

  "jest": {
    "moduleNameMapper": {
      "^workerize-loader(\\?.*)?!(.*)/([^/]*)$": "$2/__mocks__/$3"
    }
  }

with the purpose of loading mocks from a file with the same name that the worker but located in the ./__mocks__ folder where the worker file is (Eg: <workerFolder>/__mocks__/<workerFilename>) since we want to have the workers and its mocks next to the other files organized by features. Our files structure looks like this:

├── src/
│   └── features/
│       └── foo/
│           ├── FooBar.worker.ts 
│           └── __mocks__
│               └── FooBar.worker.ts
├── App.tsx
└── App.test.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests