diff --git a/apps/examples/src/App.jsx b/apps/examples/src/App.jsx
index 8936aec..04aaced 100644
--- a/apps/examples/src/App.jsx
+++ b/apps/examples/src/App.jsx
@@ -1,26 +1,27 @@
-import React from 'react'
-import { Toaster } from 'react-hot-toast'
-import { Link, Route, BrowserRouter as Router, Switch } from 'react-router-dom'
+import React from "react";
+import { Toaster } from "react-hot-toast";
+import { Link, Route, BrowserRouter as Router, Switch } from "react-router-dom";
-import CsvPage from './pages/Csv/index.jsx'
-import ExternalScriptsPage from './pages/ExternalScripts/index.jsx'
-import SortingPage from './pages/Sorting/index.jsx'
-import TransferablePage from './pages/Transferable'
-import logo from './react.png'
-import './style.css'
+import CsvPage from "./pages/Csv/index.jsx";
+import ExternalScriptsPage from "./pages/ExternalScripts/index.jsx";
+import SortingPage from "./pages/Sorting/index.jsx";
+import TransferablePage from "./pages/Transferable";
+import LocalDepsPage from "./pages/LocalDeps";
+import logo from "./react.png";
+import "./style.css";
-let turn = 0
+let turn = 0;
function infiniteLoop() {
- const lgoo = document.querySelector('.App-logo')
- turn += 8
- lgoo.style.transform = `rotate(${turn % 360}deg)`
+ const lgoo = document.querySelector(".App-logo");
+ turn += 8;
+ lgoo.style.transform = `rotate(${turn % 360}deg)`;
}
export default function App() {
React.useEffect(() => {
- const loopInterval = setInterval(infiniteLoop, 100)
- return () => clearInterval(loopInterval)
- }, [])
+ const loopInterval = setInterval(infiniteLoop, 100);
+ return () => clearInterval(loopInterval);
+ }, []);
return (
<>
@@ -31,25 +32,30 @@ export default function App() {
-
-
+
Sorting Demo
-
-
+
Csv Demo
-
-
+
External Scripts Demo
-
-
+
Transferable Demo
+ -
+
+ LocalDeps Demo
+
+
@@ -68,10 +74,13 @@ export default function App() {
+
+
+
>
- )
+ );
}
diff --git a/apps/examples/src/pages/LocalDeps/index.jsx b/apps/examples/src/pages/LocalDeps/index.jsx
new file mode 100644
index 0000000..ddb5666
--- /dev/null
+++ b/apps/examples/src/pages/LocalDeps/index.jsx
@@ -0,0 +1,77 @@
+import { WORKER_STATUS, useWorker } from "@koale/useworker";
+import React, { useState } from "react";
+import toast from "react-hot-toast";
+
+const pow = (a) => a * a;
+
+function App() {
+ const [inputValue, setInputValue] = useState(2); // State for input value
+ const [localDepsWorker, { status: localDepsWorkerStatus, kill: killWorker }] =
+ useWorker((numbers) => pow(numbers), {
+ autoTerminate: false,
+ localDependencies: [pow],
+ });
+
+ React.useEffect(() => {
+ console.log("WORKER:", localDepsWorkerStatus);
+ }, [localDepsWorkerStatus]);
+
+ const onWorkerSortClick = () => {
+ // Use the inputValue from state
+ localDepsWorker(Number(inputValue)).then((result) => {
+ console.log("localdeps useWorker()", result);
+ toast.success(
+ "Finished: localdeps using useWorker." + `The result is ${result}`
+ );
+ });
+ };
+
+ // Handle input change
+ const handleInputChange = (e) => {
+ setInputValue(e.target.value);
+ };
+
+ return (
+
+
+ {/* Input field for dynamic value */}
+
+
{"const pow = (a) => a * a;"}
+
+
+ {localDepsWorkerStatus === WORKER_STATUS.RUNNING ? (
+
+ ) : null}
+
+
+
+
+ Open DevTools console to see the results.
+
+
+
+ );
+}
+
+export default App;
diff --git a/packages/useWorker/src/lib/createWorkerBlobUrl.ts b/packages/useWorker/src/lib/createWorkerBlobUrl.ts
index 231ce44..5936958 100644
--- a/packages/useWorker/src/lib/createWorkerBlobUrl.ts
+++ b/packages/useWorker/src/lib/createWorkerBlobUrl.ts
@@ -1,7 +1,7 @@
// import isoworker from 'isoworker'
-import { TRANSFERABLE_TYPE } from '../useWorker'
-import jobRunner from './jobRunner'
-import remoteDepsParser from './remoteDepsParser'
+import { TRANSFERABLE_TYPE } from "../useWorker";
+import jobRunner from "./jobRunner";
+import remoteDepsParser from "./remoteDepsParser";
/**
* Converts the "fn" function into the syntax needed to be executed within a web worker
@@ -20,19 +20,20 @@ import remoteDepsParser from './remoteDepsParser'
const createWorkerBlobUrl = (
fn: Function,
deps: string[],
- transferable: TRANSFERABLE_TYPE /* localDeps: () => unknown[], */,
+ localDeps: Function[],
+ transferable: TRANSFERABLE_TYPE /* localDeps: () => unknown[], */
) => {
// const [context] = isoworker.createContext(localDeps)
const blobCode = `
- ${remoteDepsParser(deps)};
+ ${remoteDepsParser(deps, localDeps)};
onmessage=(${jobRunner})({
fn: (${fn}),
transferable: '${transferable}'
})
- `
- const blob = new Blob([blobCode], { type: 'text/javascript' })
- const url = URL.createObjectURL(blob)
- return url
-}
+ `;
+ const blob = new Blob([blobCode], { type: "text/javascript" });
+ const url = URL.createObjectURL(blob);
+ return url;
+};
-export default createWorkerBlobUrl
+export default createWorkerBlobUrl;
diff --git a/packages/useWorker/src/lib/remoteDepsParser.ts b/packages/useWorker/src/lib/remoteDepsParser.ts
index f0eefb8..5bfd1e3 100644
--- a/packages/useWorker/src/lib/remoteDepsParser.ts
+++ b/packages/useWorker/src/lib/remoteDepsParser.ts
@@ -3,18 +3,34 @@
* Concatenates the remote dependencies into a comma separated string.
* this string will then be passed as an argument to the "importScripts" function
*
- * @param {Array.}} deps array of string
+ * @param {Array.} deps array of string
+ * @param {Array.} localDeps array of function
* @returns {String} a string composed by the concatenation of the array
* elements "deps" and "importScripts".
*
* @example
* remoteDepsParser(['http://js.com/1.js', 'http://js.com/2.js']) // importScripts('http://js.com/1.js', 'http://js.com/2.js')
*/
-const remoteDepsParser = (deps: string[]) => {
- if (deps.length === 0) return ''
+const remoteDepsParser = (deps: string[], localDeps: Function[]) => {
+ if (deps.length === 0 && localDeps.length === 0) return "";
- const depsString = deps.map((dep) => `'${dep}'`).toString()
- return `importScripts(${depsString})`
-}
+ const depsString = deps.map((dep) => `'${dep}'`).toString();
+ const depsFunctionString = localDeps
+ .filter((dep) => typeof dep === "function")
+ .map((fn) => {
+ const str = fn.toString();
+ if (str.trim().startsWith("function")) {
+ return str;
+ } else {
+ const name = fn.name;
+ return `const ${name} = ${str}`;
+ }
+ })
+ .join(";");
+ const importString = `importScripts(${depsString});`;
+ return `${
+ depsString.trim() === "" ? "" : importString
+ } ${depsFunctionString}`;
+};
-export default remoteDepsParser
+export default remoteDepsParser;
diff --git a/packages/useWorker/src/useWorker.ts b/packages/useWorker/src/useWorker.ts
index 1a0bfae..955975a 100644
--- a/packages/useWorker/src/useWorker.ts
+++ b/packages/useWorker/src/useWorker.ts
@@ -1,34 +1,36 @@
-import React from 'react'
-import { useDeepCallback } from './hook/useDeepCallback'
-import { AbortError } from './lib/abortError'
-import createWorkerBlobUrl from './lib/createWorkerBlobUrl'
-import WORKER_STATUS from './lib/status'
+import React from "react";
+import { useDeepCallback } from "./hook/useDeepCallback";
+import { AbortError } from "./lib/abortError";
+import createWorkerBlobUrl from "./lib/createWorkerBlobUrl";
+import WORKER_STATUS from "./lib/status";
type WorkerController = {
- status: WORKER_STATUS
- kill: Function
-}
+ status: WORKER_STATUS;
+ kill: Function;
+};
export enum TRANSFERABLE_TYPE {
- AUTO = 'auto',
- NONE = 'none',
+ AUTO = "auto",
+ NONE = "none",
}
type Options = {
- timeout?: number
- remoteDependencies?: string[]
- autoTerminate?: boolean
- transferable?: TRANSFERABLE_TYPE
-}
-
-const PROMISE_RESOLVE = 'resolve'
-const PROMISE_REJECT = 'reject'
+ timeout?: number;
+ remoteDependencies?: string[];
+ localDependencies?: Function[];
+ autoTerminate?: boolean;
+ transferable?: TRANSFERABLE_TYPE;
+};
+
+const PROMISE_RESOLVE = "resolve";
+const PROMISE_REJECT = "reject";
const DEFAULT_OPTIONS: Options = {
timeout: undefined,
remoteDependencies: [],
+ localDependencies: [],
autoTerminate: true,
transferable: TRANSFERABLE_TYPE.AUTO,
-}
+};
/**
*
@@ -37,33 +39,35 @@ const DEFAULT_OPTIONS: Options = {
*/
export const useWorker = any>(
fn: T,
- options: Options = DEFAULT_OPTIONS,
+ options: Options = DEFAULT_OPTIONS
) => {
const [workerStatus, setWorkerStatus] = React.useState(
- WORKER_STATUS.PENDING,
- )
- const worker = React.useRef()
- const isRunning = React.useRef(false)
+ WORKER_STATUS.PENDING
+ );
+ const worker = React.useRef();
+ const isRunning = React.useRef(false);
const promise = React.useRef<{
- [PROMISE_REJECT]?: (result: ReturnType | ErrorEvent | AbortError) => void
- [PROMISE_RESOLVE]?: (result: ReturnType) => void
- }>({})
- const timeoutId = React.useRef()
+ [PROMISE_REJECT]?: (
+ result: ReturnType | ErrorEvent | AbortError
+ ) => void;
+ [PROMISE_RESOLVE]?: (result: ReturnType) => void;
+ }>({});
+ const timeoutId = React.useRef();
const killWorker = React.useCallback(() => {
if (worker.current?._url) {
if (promise.current) {
- promise.current[PROMISE_REJECT]?.(new AbortError())
+ promise.current[PROMISE_REJECT]?.(new AbortError());
}
- worker.current.terminate()
- URL.revokeObjectURL(worker.current._url)
- promise.current = {}
- worker.current = undefined
- isRunning.current = false
- window.clearTimeout(timeoutId.current)
+ worker.current.terminate();
+ URL.revokeObjectURL(worker.current._url);
+ promise.current = {};
+ worker.current = undefined;
+ isRunning.current = false;
+ window.clearTimeout(timeoutId.current);
}
- }, [])
+ }, []);
// biome-ignore lint/correctness/useExhaustiveDependencies:
const onWorkerEnd = React.useCallback(
@@ -71,128 +75,137 @@ export const useWorker = any>(
const terminate =
options.autoTerminate != null
? options.autoTerminate
- : DEFAULT_OPTIONS.autoTerminate
+ : DEFAULT_OPTIONS.autoTerminate;
if (terminate) {
- killWorker()
+ killWorker();
}
- setWorkerStatus(status)
+ setWorkerStatus(status);
},
- [options.autoTerminate, killWorker, setWorkerStatus],
- )
+ [options.autoTerminate, killWorker, setWorkerStatus]
+ );
const generateWorker = useDeepCallback(() => {
const {
remoteDependencies = DEFAULT_OPTIONS.remoteDependencies,
+ localDependencies = DEFAULT_OPTIONS.localDependencies,
timeout = DEFAULT_OPTIONS.timeout,
transferable = DEFAULT_OPTIONS.transferable,
- } = options
+ } = options;
- const blobUrl = createWorkerBlobUrl(fn, remoteDependencies!, transferable!)
- const newWorker: Worker & { _url?: string } = new Worker(blobUrl)
- newWorker._url = blobUrl
+ const blobUrl = createWorkerBlobUrl(
+ fn,
+ remoteDependencies!,
+ localDependencies!,
+ transferable!
+ );
+ const newWorker: Worker & { _url?: string } = new Worker(blobUrl);
+ newWorker._url = blobUrl;
newWorker.onmessage = (e: MessageEvent) => {
- const [status, result] = e.data as [WORKER_STATUS, ReturnType]
+ const [status, result] = e.data as [WORKER_STATUS, ReturnType];
switch (status) {
case WORKER_STATUS.SUCCESS:
- promise.current[PROMISE_RESOLVE]?.(result)
- onWorkerEnd(WORKER_STATUS.SUCCESS)
- break
+ promise.current[PROMISE_RESOLVE]?.(result);
+ onWorkerEnd(WORKER_STATUS.SUCCESS);
+ break;
default:
- promise.current[PROMISE_REJECT]?.(result)
- onWorkerEnd(WORKER_STATUS.ERROR)
- break
+ promise.current[PROMISE_REJECT]?.(result);
+ onWorkerEnd(WORKER_STATUS.ERROR);
+ break;
}
- }
+ };
newWorker.onerror = (e: ErrorEvent) => {
- promise.current[PROMISE_REJECT]?.(e)
- onWorkerEnd(WORKER_STATUS.ERROR)
- }
+ promise.current[PROMISE_REJECT]?.(e);
+ onWorkerEnd(WORKER_STATUS.ERROR);
+ };
if (timeout) {
timeoutId.current = window.setTimeout(() => {
- killWorker()
- setWorkerStatus(WORKER_STATUS.TIMEOUT_EXPIRED)
- }, timeout)
+ killWorker();
+ setWorkerStatus(WORKER_STATUS.TIMEOUT_EXPIRED);
+ }, timeout);
}
- return newWorker
- }, [fn, options, killWorker])
+ return newWorker;
+ }, [fn, options, killWorker]);
// biome-ignore lint/correctness/useExhaustiveDependencies:
const callWorker = React.useCallback(
(...workerArgs: Parameters) => {
- const { transferable = DEFAULT_OPTIONS.transferable } = options
+ const { transferable = DEFAULT_OPTIONS.transferable } = options;
return new Promise>((resolve, reject) => {
promise.current = {
[PROMISE_RESOLVE]: resolve,
[PROMISE_REJECT]: reject,
- }
+ };
const transferList: any[] =
transferable === TRANSFERABLE_TYPE.AUTO
? workerArgs.filter(
(val: any) =>
- ('ArrayBuffer' in window && val instanceof ArrayBuffer) ||
- ('MessagePort' in window && val instanceof MessagePort) ||
- ('ImageBitmap' in window && val instanceof ImageBitmap) ||
- ('OffscreenCanvas' in window &&
- val instanceof OffscreenCanvas),
+ ("ArrayBuffer" in window && val instanceof ArrayBuffer) ||
+ ("MessagePort" in window && val instanceof MessagePort) ||
+ ("ImageBitmap" in window && val instanceof ImageBitmap) ||
+ ("OffscreenCanvas" in window &&
+ val instanceof OffscreenCanvas)
)
- : []
+ : [];
- worker.current?.postMessage([[...workerArgs]], transferList)
+ worker.current?.postMessage([[...workerArgs]], transferList);
- setWorkerStatus(WORKER_STATUS.RUNNING)
- })
+ setWorkerStatus(WORKER_STATUS.RUNNING);
+ });
},
- [setWorkerStatus],
- )
+ [setWorkerStatus]
+ );
const workerHook = React.useCallback(
(...fnArgs: Parameters) => {
const terminate =
options.autoTerminate != null
? options.autoTerminate
- : DEFAULT_OPTIONS.autoTerminate
+ : DEFAULT_OPTIONS.autoTerminate;
if (isRunning.current) {
console.error(
- '[useWorker] You can only run one instance of the worker at a time, if you want to run more than one in parallel, create another instance with the hook useWorker(). Read more: https://github.com/alewin/useWorker',
- )
- return Promise.reject()
+ "[useWorker] You can only run one instance of the worker at a time, if you want to run more than one in parallel, create another instance with the hook useWorker(). Read more: https://github.com/alewin/useWorker"
+ );
+ return Promise.reject();
}
if (terminate || !worker.current) {
- worker.current = generateWorker()
+ worker.current = generateWorker();
}
- return callWorker(...fnArgs)
+ return callWorker(...fnArgs);
},
- [options.autoTerminate, generateWorker, callWorker],
- )
+ [options.autoTerminate, generateWorker, callWorker]
+ );
// biome-ignore lint/correctness/useExhaustiveDependencies:
const killWorkerController = React.useCallback(() => {
- killWorker()
- setWorkerStatus(WORKER_STATUS.KILLED)
- }, [killWorker, setWorkerStatus])
+ killWorker();
+ setWorkerStatus(WORKER_STATUS.KILLED);
+ }, [killWorker, setWorkerStatus]);
const workerController = {
status: workerStatus,
kill: killWorkerController,
- }
+ };
React.useEffect(() => {
- isRunning.current = workerStatus === WORKER_STATUS.RUNNING
- }, [workerStatus])
+ isRunning.current = workerStatus === WORKER_STATUS.RUNNING;
+ }, [workerStatus]);
React.useEffect(
() => () => {
- killWorker()
+ killWorker();
},
- [killWorker],
- )
-
- return [workerHook, workerController] as [typeof workerHook, WorkerController]
-}
+ [killWorker]
+ );
+
+ return [workerHook, workerController] as [
+ typeof workerHook,
+ WorkerController
+ ];
+};