Search code examples
reactjsurlnext.jsweb-worker

Creating relative URLs with `new URL()` behaves differently when first param is a variable. WHY?


I'm trying to implement web workers in NextJs, I've followed their example but It really bugs me that I cannot pass the worker relative URL as a variable to new URL(url, baseUrl).

The following snippet is where the worker gets called:

import { useEffect, useRef, useCallback } from 'react'

export default function Index() {
  const workerRef = useRef()
  useEffect(() => {
    const workerUrl = '../worker.js';

    console.log({
      URL: new URL('../worker.js', import.meta.url),
      meta: import.meta.url
    });
    console.log({
      URL: new URL(workerUrl, import.meta.url),
      meta: import.meta.url
    });

    workerRef.current = new Worker(new URL('../worker.js', import.meta.url))
    workerRef.current.onmessage = (evt) =>
      alert(`WebWorker Response => ${evt.data}`)
    return () => {
      workerRef.current.terminate()
    }
  }, [])

  const handleWork = useCallback(async () => {
    workerRef.current.postMessage(100000)
  }, [])

  return (
    <div>
      <p>Do work in a WebWorker!</p>
      <button onClick={handleWork}>Calculate PI</button>
    </div>
  )
}

This strangely logs:

{
  "URL":"/_next/static/media/worker.3c527896.js",
  "meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}
    
{
  "URL":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/worker.js",
  "meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}

How in the world is this any different:

    const workerUrl = '../worker.js';

    console.log({
      URL: new URL('../worker.js', import.meta.url),
      meta: import.meta.url
    });
    console.log({
      URL: new URL(workerUrl, import.meta.url),
      meta: import.meta.url
    });

The problem is that I cannot pass the URL as a prop, to some generic worker caller. I get the annoying error:

SecurityError: Failed to construct 'Worker': Script at 'file:///home/omar/CODE/NextJs/lullo/client/src/utils/WebWorkers/postErrorToServer.ts' cannot be accessed from origin 'http://localhost:3000'.

Solution

  • This is probably happening because in the first case:

    const workerUrl = '../worker.js';
    const url = new URL(workerUrl, import.meta.url);
    

    webpack sees the URL as dynamic and is unable to properly bundle the web worker at compile-time. Something similar happens if you define the worker as follows:

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

    This comment on a discussion in webpack's GitHub repo might help in your case. I don't think the worker URL can be truly dynamic, due to the above reason - webpack needs to know the url of the worker script at compile-time.