I’m trying to implement file importing into my electron app using web workers to not block my main process. I already did everything they wrote in electron-vite docs (spoiler alert its not much) but app is freezing when calling worker. https://electron-vite.org/guide/dev#worker-threads
When I try to import files whole app is freezing but only visually. Copying itself works however after finish sometimes app unfreezes after some time and sometimes I need to restart it. I also tried to use 2nd method listed in docs to use workers in electron-vite app but then its not starting at all yelling at me that importWorker does not have default export, same error I got when trying to implement child process for this. Here is whole code for the app under branch workers
https://github.com/UnderMan4/abManager/tree/workers.
This is how my code looks like now:
// main/index.ts
import { importFiles } from "./import";
// Other imports
function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minHeight: 600,
minWidth: 1100,
show: false,
autoHideMenuBar: false,
...(process.platform === "linux" ? { icon } : {}),
webPreferences: {
preload: path.join(__dirname, "../preload/index.js"),
sandbox: false,
nodeIntegrationInWorker: true,
},
});
[...]
apiHandling();
[...]
});
[...]
const apiHandling = () => {
// more communication handling
ipcMain.on("import-files", (_, data) => {
importFiles(data);
});
};
[...]
// main/import.ts
import { ipcMain } from "electron";
import { Worker } from "worker_threads";
import importWorker from "./workers/importWorker?nodeWorker";
const runningImports = new Map<string, { data: ImportData; worker: Worker }>();
// This method will allow my to start importing (copying into library folder). By design every call should create new worker, do all copying in background and send progress to renderer using ipcMain.emit()
export const importFiles = (data: ImportData) => {
if (runningImports.has(data.id)) return;
const worker = importWorker({
workerData: data,
});
runningImports.set(data.id, { data, worker });
worker.on("message", (message: ImportWorkerMessage) => {
const { type, ...data } = message;
ipcMain.emit("import-message", type, data);
if (type === "done") {
worker.terminate();
}
});
worker.on("error", (err) => {
console.error(err);
});
worker.on("exit", (code) => {
runningImports.delete(data.id);
});
};
// main/workers/importWorker.ts
// The worker itself
import fs from "fs";
import p from "path";
import { parentPort, workerData } from "worker_threads";
if (!parentPort) {
throw new Error("No parentPort found");
}
const { paths, options, id } = workerData as ImportData;
const fileQueue = [...paths];
const processFiles = () => {
if (fileQueue.length === 0) {
parentPort!.postMessage({ type: "done", id });
return;
}
//Here I'm copying every file in paths array to desktop (for now)
const path = fileQueue.shift()!;
const fileName = p.basename(path);
const readStream = fs.createReadStream(path);
const writeStream = fs.createWriteStream(
p.join("C:\Users\filip\Desktop", fileName)
);
parentPort!.postMessage({
type: "fileStart",
id,
fileName,
});
readStream.on("data", (chunk) => {
parentPort!.postMessage({
type: "progress",
id,
fileName,
chunkLength: chunk.length,
});
});
readStream.on("end", () => {
parentPort!.postMessage({
type: "fileDone",
id,
fileName,
});
processFiles();
});
readStream.pipe(writeStream);
writeStream.on("error", (error) => {
parentPort!.postMessage({
type: "error",
id,
fileName,
error,
});
});
};
processFiles();
// defining communication methods for renderer in preload/index.ts
import: {
importFiles: (path: string[], options: unknown) => {
ipcRenderer.sendSync("import-files", path, options);
},
onMessage: (callback: (event: unknown, data: unknown) => void) => {
ipcRenderer.addListener("import-message", callback);
return () => ipcRenderer.removeListener("import-message", callback);
},
},
UnderMan4 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1