Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions apps/examples/src/App.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
Expand All @@ -31,25 +32,30 @@ export default function App() {
<img src={logo} className="App-logo" alt="logo" />
<ul>
<li>
<Link style={{ color: 'white' }} to="/sorting">
<Link style={{ color: "white" }} to="/sorting">
Sorting Demo
</Link>
</li>
<li>
<Link style={{ color: 'white' }} to="/csv">
<Link style={{ color: "white" }} to="/csv">
Csv Demo
</Link>
</li>
<li>
<Link style={{ color: 'white' }} to="/external">
<Link style={{ color: "white" }} to="/external">
External Scripts Demo
</Link>
</li>
<li>
<Link style={{ color: 'white' }} to="/transferable">
<Link style={{ color: "white" }} to="/transferable">
Transferable Demo
</Link>
</li>
<li>
<Link style={{ color: "white" }} to="/localdeps">
LocalDeps Demo
</Link>
</li>
</ul>
</header>
<hr />
Expand All @@ -68,10 +74,13 @@ export default function App() {
<Route path="/transferable">
<TransferablePage />
</Route>
<Route path="/localdeps">
<LocalDepsPage />
</Route>
</Switch>
</div>
</Router>
<Toaster />
</>
)
);
}
77 changes: 77 additions & 0 deletions apps/examples/src/pages/LocalDeps/index.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<section className="App-section">
{/* Input field for dynamic value */}
<div style={{ display: "flex", flexDirection: "column" }}>
<pre>{"const pow = (a) => a * a;"}</pre>
<input
type="number"
value={inputValue}
onChange={handleInputChange}
placeholder="Enter a number"
className="App-input"
/>
<button
type="button"
disabled={localDepsWorkerStatus === WORKER_STATUS.RUNNING}
className="App-button"
onClick={() => onWorkerSortClick()}
>
{localDepsWorkerStatus === WORKER_STATUS.RUNNING
? `Loading...`
: `LocalDeps useWorker()`}
</button>
{localDepsWorkerStatus === WORKER_STATUS.RUNNING ? (
<button
type="button"
className="App-button"
onClick={() => killWorker()}
>
Kill Worker
</button>
) : null}
</div>
</section>
<section className="App-section">
<span style={{ color: "white" }}>
Open DevTools console to see the results.
</span>
</section>
</div>
);
}

export default App;
23 changes: 12 additions & 11 deletions packages/useWorker/src/lib/createWorkerBlobUrl.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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[], */
Comment on lines 20 to +24

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createWorkerBlobUrl signature now includes a localDeps parameter, but the JSDoc above still only documents fn and deps, which can confuse consumers and future maintainers about how to pass local dependencies. Please update the JSDoc to include the localDeps parameter (its expected type and behavior) so the documentation stays consistent with the function signature and the new localDependencies option exposed via useWorker.

Copilot uses AI. Check for mistakes.
) => {
// 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;
30 changes: 23 additions & 7 deletions packages/useWorker/src/lib/remoteDepsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.<String>}} deps array of string
* @param {Array.<String>} deps array of string
* @param {Array.<Function>} 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;
Comment on lines +20 to +25

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When building the depsFunctionString, this code assumes that every localDeps entry is a named function and blindly emits const ${name} = ${str} for non-function declarations. If a consumer passes an anonymous arrow/function expression (e.g. localDependencies: [() => 1]), fn.name will be an empty string and the generated worker script will contain invalid syntax like const = () => 1, causing the worker creation to fail with a syntax error. Consider either enforcing named functions (and throwing/logging when fn.name is empty) or generating a safe unique identifier when fn.name is falsy, so the generated worker code is always valid JavaScript.

Suggested change
.map((fn) => {
const str = fn.toString();
if (str.trim().startsWith("function")) {
return str;
} else {
const name = fn.name;
.map((fn, index) => {
const str = fn.toString();
if (str.trim().startsWith("function")) {
return str;
} else {
const name =
fn.name && fn.name.trim().length > 0 ? fn.name : `_localDep_${index}`;

Copilot uses AI. Check for mistakes.
return `const ${name} = ${str}`;
}
})
.join(";");
const importString = `importScripts(${depsString});`;
return `${
depsString.trim() === "" ? "" : importString
} ${depsFunctionString}`;
};

export default remoteDepsParser
export default remoteDepsParser;
Loading
Loading