andywer / threads.js

🧵 Make web workers & worker threads as simple as a function call.
https://threads.js.org/
MIT License
3.06k stars 163 forks source link

Configuration for create-react-app with Typescript? #341

Open jimklo opened 3 years ago

jimklo commented 3 years ago

Trying to get a simple sample of threads.js working within a create-react-app that was started using the typescript enabled template.

I'm using craco to avoid ejecting, so my craco.config.js contains the following:

const ThreadsPlugin = require("threads-plugin");

module.exports = {
    module: {
        // Supposedly I don't need to enable the rules since it's already enabled in react-scripts.
        // rules: [
        //     {
        //         test: /\.ts$/,
        //         loader: "ts-loader",
        //         options: {
        //             compilerOptions: {
        //                 module: "esnext"
        //             }
        //         }
        //     }
        // ],
        plugins: [
            new ThreadsPlugin()
        ]
    }
}

I've created a simple worker as counter.ts:

import {expose} from "threads/worker";

let currentCount = 0;

const counter = {
    getCount() {
            return currentCount;
    },
    increment() {
        return ++currentCount;
    },
    decrement() {
        return --currentCount;
    }
}

expose(counter);

And in my App.tsx I'm creating a Pool and queuing a worker function:

import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';
import {Pool, spawn, Worker} from "threads";

function App() {

  const pool = Pool(() => spawn(new Worker("./counter")),8);
  const [count, setCount] = useState(0);

  const handleIncrement = () => {
      pool.queue(async counter => {
         const newCount = await counter.increment();
         setCount(newCount);
      });
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React

        </a>
        Count: {count}
        <button onClick={handleIncrement}>Increment</button>
      </header>
    </div>
  );
}

export default App;

Basically what what happens is that I just keep getting an error in the browser console: Uncaught SyntaxError: Unexpected token '<' :3000/counter:1

It would seem to me that the counter.ts file isn't passing through Babel/Webpack.

Can anyone advise on how I might add threads.js into this project?

Thanks

andywer commented 3 years ago

Hey @jimklo. Looks as if it is trying to fetch the worker bundle from http://localhost:3000/counter and I guess it does not exist (at least not at that path). The Unexpected token '<' error can indicate that an HTML page is served (probably a 404 page) instead of the JS file.

I cannot really tell you why that happens from here, though. Make sure the threads-plugin did not show any warnings during the webpack build. Maybe your Babel config is set to transpile ES modules to CommonJS?

jimklo commented 3 years ago

@andywer Yep, that's exactly what's going on; 404 error that's serving HTML. It's unclear to me how the loader is supposed to work within a create-react-app framework since it uses react-scripts to magically configure Webpack. I use craco to override and extend react-scripts - as my app uses CesiumJS (where the React friendly library needs craco-react).

The big problem that I see is that the path passed to threads.js``Worker's constructor is that the path doesn't get processed during transpilation, but only at runtime; so the path to be loaded must reside in the public folder within a CRA; making a Typescript worker seemingly not possible. (note, I just noticed the optional flag fromSource, and wonder if that might be a solution... more below).

I don't see any errors from the threads-plugin, but I'm not even sure how to tell. I'm not even quite sure I understand it's purpose; unless it's to handle that transpilation of the path's passed to the constructor I noted above. I'll admit I'm new to using Typescript, so I don't fully understand how Babel gets configured in this case. Here's the tsconfig.json that I'm using:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react"
  },
  "include": [
    "src"
  ]
}

FWIW: I'm able to use a straight up Worker within a TS enable CRA if I do something like this:

// webworker.ts
export default class WebWorker extends Worker {
    constructor(worker: any) {
        const code = worker.toString();
        const blob = new Blob([`(${code})()`])
        super(URL.createObjectURL(blob));
    }
}

// helloworker.ts
export default () => {
    self.onmessage = (e: any) => {
         console.log(`Hello, {e.data.name}`);
         // @ts-ignore
         postMessage({topic: "hello", target: "world"});
    }
}

// app.ts
import WebWorker from "./webworker";
import * as helloWorker from "./helloworker";

const myworker = new WebWorker(helloWorker);

myworker.addListener('message', evt => {
     console.log(`got message: {evt.data.topic} and {evt.data.target}`);
});

myworker.postMessage({name: "John"});

Digging into the threads.js Worker and spawn it looks like I might be able to make it work using:

import * as helloWorker from "./helloworker";

/* ... */
spawn(new Worker(helloWorker.toString(), { fromSource: true }));

However i've not tried something like this yet...

Given the popularity of CRA... it would seem some kind of guidance on getting threads.js configured would be useful to many. Basically my hope was to be able to work with the Pool features, rather than trying to roll my own.

Thanks!

andywer commented 3 years ago

Good points! I am not too sure about today's inner workings of react-scripts, though. Not sure if they still set up Babel – maybe there the ES modules are transpiled before they hit webpack?

Yeah, an example of CRA + threads.js would make a lot of sense.

MMauro94 commented 3 years ago

@jimklo I'm in the same exact boat (React + TypeScript + craco). Did you manage to make it work?

danieledler commented 3 years ago

Hi @jimklo, @andywer and @MMauro94, I just made a minimal example with CRA + TypeScript + CRACO + threads.js to make it work: https://github.com/danieledler/cra-typescript-threadsjs-worker.

andywer commented 3 years ago

Great stuff, @danieledler!

How about we just add a section about CRA to the docs and link to your demo repo?