Search code examples
javascriptmoduleweb-worker

use a same module exporting to both main thread js and a web worker


*** NEW VERSION *** It was difficult to track down the problem. Now I have a better idea:

direct imports cannot reflects variables which are set after DOMContentLoaded, and I don't know how to tackle this, excepting by adding data to the web workers, and not through imports. Even window, known by the main.js module, becomes unreachable by next occurrences of the web worker (which fetches different vector layers) :

Error: ReferenceError: window is not defined

for instance, I was using window.document.baseURI and I must provide the full URL now.

Also I have a web worker that has two steps, the second step using user provided data (a hit point on the map). Therefore module import cannot import functions created by closures: closures must be initialized again.

Any idea ?

*** Initial VERSION text of the question (- typo) ***

Presently main.js uses a startTiming() from global.js, and webworker.js also uses startTiming() via importScripts('./global.js'). Some other parts of code are in separate script files (eg. mapmaker.js). Presently, everything is working, but this is not "modular" and a bit ugly.

<body>
    <script src="src/mapmaker.js"></script>
    <script src="src/global.js"></script>
    <script src="src/main.js"></script>
</body>

With main.js

async function(){
    const timlog = startTiming();
    const timing = (mess) => "MAIN" + timlog(mess);
    // ...
}

global.js (simple script, not a module)

function startTiming(){ /* ... performance.now initial */ }

webworker.js called with new Worker("webworker.js")

importScripts('./global.js')
const timlog = startTiming();

I am trying to translates everything the import-export way: but I fail.

New main.js

import {f} from './global.js'
//instead of nothing
async function(){
//...
const context = f();
// but `export {context};` is not possible
}

New global.js (script with type=module)

export const NAME = "name";
export function f(){ /* ... */ }

New webworker.js called with new Worker("webworker.js", {type:'module'})

import {f} from './global.js'
//instead of importScripts('./global.js')
const samecontext = f();
// but `samecontext` can't be the same as `context`

I got an error (HTML failure) what is not very explicit. These modifications are probably not enough. I was suspecting that async must be top level, and not in a module.


Solution

  • It is not necessarily difficult to code a module that exports functions and constants to both the main thread and web workers, BUT here is a summary of the indirect difficulties that can arise during this process, and a few solutions.

    1. All scripts be modules.

    As soon as a script must add an import or export instruction, it must be translated as a module. And the quick conclusion is that, directly or transitively, all scripts must either export and/or import something. Because they all contribute to the web page in one way or another.

    1. Confine the window dependent code.

    The module which launches the main thread (here named main.js) necessarily has a DOM interface, and it is better to avoid web workers importing directly from it (avoids ReferenceError: window is not defined). You would better add intermediate modules (named tierWW1.js, tierWW2...) from which each WWi would import, and in which there is no DOM dependency. And to isolate window dependent code, or document.

    // main.js
    import { morefunction } from "moremodule.js";
    export { initiatedInMain };
    const initiatedInMain = somevalue;
    ... (window independent section)...
    if(typeof window !== undefined) {...(window dependent)... }
    
    // tierWW1.js : lone intermediate with WW1
    import { initiatedInMain, otherfunction } from "main.js";
    export { initiatedInMain, specifTierWW };
    function specificTierWW(){...}
    
    // web worker WW1: ww1.js
    import { initiatedInMain, specifTierWW } from "tierWW1.js";
    self.onmessage = function(event){ ... }
    
    1. Online Script Management:

    If for lazy convenience you have embedded a function in an HTML element, for example

    .sometag. onmouseover='return popover();'> marksign </.sometag.
    

    then you should have this function in the module that knows about the window environment (or import it there first), and force it to be at the top "window" by the instruction:

    import {popover} from "othermod.js";
    window.popover = popover;
    

    Or, don't be lazy, remove the inline script, and replace it by coding addEventListener() on the element that you need to recover first by proper identification.

    1. Global variable updated thru async function call.

    Only the state of the variable (object, or function closure reflecting a context) obtained before external updates is exported. Hence a two-steps web-worker has only access to the initial context. You must find a workaround to signify the changes, in particular modifying the way to communicate them.

    Here are some of the hurdles I met when starting, last week, to post my initial question. Probably not exhaustive.

    1. Translation effort and ROI:

    It turns out that if you have a website using javascript code divided into a large number of scripts and if you plan to translate them into modules, be prepared for a "hot" period before you start the process. The reward? the pride of having succeeded.

    And finally it works!