javascriptreactjsweb-workersheetjs

How to import functions that I created at web worker?


I am new to Web Worker, and right now what I am trying to do is import my function that I have created under the workerCode in Web Worker, but neither "import" nor "importScript" work; only the XLSX library is working fine, but the rest is not.

Could you help me figure out how to do this?

export const consolidateWorkerV2 = () => {
 const workerCode = `
 /* Load standalone script from CDN */
 import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.1/package/xlsx.mjs";
 import getDataRange from "../shared/util/GetDataRange";
 import modifyWorksheet from "../shared/util/modifyWorksheet.js";
 import headerStyling from "../shared/util/headerStyling";

 self.addEventListener('message', async (e) => {
  try {
    const files = e.data.files;
    const newWorkbook = XLSX.utils.book_new();
    console.log("message received from main thread");
    for (const [index, item] of files.entries()) {
      const file = item.file;
      const filename = item.file.name
      const data = await item.file.arrayBuffer();
      const workbook = XLSX.read(data);
      
      for (const sheetName of workbook.SheetNames) {
        if (newWorkbook.SheetNames.includes(journalType)) {
          const duplicateSheetName = workbook.SheetNames[0];
          const newWorksheet = newWorkbook.Sheets[journalType];
          const dupWorksheet = getDataRange(workbook.Sheets[duplicateSheetName]);
          const OPTIONS = { raw: false, defval: "", };
          const parsedDupWorksheet = XLSX.utils.sheet_to_json(dupWorksheet, OPTIONS);
          const updatedWorksheet = modifyWorksheet(dupWorksheet, parsedDupWorksheet, duplicateSheetName, journalType, reportDate);
          const newData = updatedWorksheet.map((row) => Object.values(row));
          XLSX.utils.sheet_add_aoa(newWorksheet, newData, { origin: -1, skipHeader: true });
        } else {
          // if no existing sheet name, then add new sheet
          for (const sheetName of workbook.SheetNames) {
            const worksheet = workbook.Sheets[sheetName];
            const worksheetData = getDataRange(worksheet);
            const parsedWorksheet = XLSX.utils.sheet_to_json(worksheet, { raw: false, defval: "", });
            const updatedWorksheet = modifyWorksheet(worksheetData, parsedWorksheet, sheetName, journalType, reportDate);
            const styledSheet = headerStyling(updatedWorksheet);
            XLSX.utils.book_append_sheet(newWorkbook, styledSheet, journalType);
          }
        }
      }
    }
    
    const consolidateFilename = namingConvention("Consolidated_Final_Report", date);
    const excelBuffer = XLSX.write(newWorkbook, { bookType: "xlsx", type: "array", });
    const blob = new File([excelBuffer], fileName + ".xlsx");
    postMessage({ blob: blob });   
  } catch(e) {
    /* Pass the error message back */
    postMessage({ error: String(e.message || e).bold() });
  }
}, false);
`;

  const blob = new Blob([workerCode], { type: "text/javascript" });
  const worker = new Worker(URL.createObjectURL(blob), { type: "module" });
  return worker;
};

Solution

  • I see two issues:

    1. You must include the full filename of the module you import via import or importScripts when there's no bundler involved; the native web platform doesn't try to guess at file extensions. So you'd need:

      import getDataRange from "../shared/util/GetDataRange.js";
      // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^
      

      ...assuming that's the full path of the resource.

    2. I can't find a specific citation for it, but I believe when you load a worker via a blob, you can't use relative imports. Four possible mututally-exclusive solutions for that:

      1. Don't use a Blob. Put the worker code in its own file and use the URL to that file:

        const worker = new Worker("./path/to/worker.js", { type: "module" });
        

        This is the simple and easy answer.

      2. Alternatively, use absolute imports as you did with the XLSX library if you only need to run this code on a single site. For example:

        import getDataRange from "https://example.com/shared/util/GetDataRange.js";
        

        Or similar.

      3. Or, modify the workerCode to replace the relative paths with absolute ones before creating the Blob:

        let workerCode = /*...*/;
        const shared = new URL("../shared", location.href).toString();
        workerCode = workerCode.replace(/from "\.\.\/shared/g, `from ${shared}`);
        // ...build the worker
        
      4. Or use self.origin to get the origin within the worker (a quick test suggests this works on Chromium, Firefox, and Safari) and use dynamic import.

        const getDataRange = (await import(new URL("../shared/util/GetDataRange", self.origin).toString())).default;
        

        I suspect any of the other three solutions are better than this one, but again, it did seem to work on the major browsers.