Skip to content

fix(packages/js): add node js support via worker_threads - #433

Closed
Prachi-Gupta2808 wants to merge 16 commits into
Ryan-Millard:devfrom
Prachi-Gupta2808:fix/node-js-support
Closed

fix(packages/js): add node js support via worker_threads#433
Prachi-Gupta2808 wants to merge 16 commits into
Ryan-Millard:devfrom
Prachi-Gupta2808:fix/node-js-support

Conversation

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor

What was changed & why

The img2num JS package used the browser-only Web Workers API, causing a ReferenceError: Worker is not defined crash in any Node.js environment. This PR adds Node.js support by detecting the runtime environment and using worker_threads instead of Web Workers when running in Node.js.

Fixes: #394

Changes

  • packages/js/wasmClient.js : added environment detection; uses worker_threads Worker in Node.js, existing Web Worker in browser ( Didn't change anything in this flow)
  • packages/js/wasmWorker.js : extracted message handler into handleMessage(); uses parentPort for messaging in Node.js, existing self.onmessage/self.postMessage in browser (Didn't change anything for browser here either)
  • bindings/js/CMakeLists.txt : added node to ENVIRONMENT flag so the compiled WASM supports Node.js runtime
  • example-apps/console-js : new example app demonstrating the library working in a Node.js environment

Testing & Verification

Tested inside the Docker container (main image as mentioned by @Ryan-Millard ):

  1. Reproduced the original Worker is not defined error
  2. Applied the fix
  3. Built the WASM with the updated ENVIRONMENT flag
  4. Ran node index.js in example-apps/console-js , library loaded and processed successfully in Node.js

Browser path is completely untouched so existing functionality is unaffected.

Additional Resources

Note: I already mentioned the webGPU one stuff.

Thank you!

Prachi-Gupta2808 and others added 4 commits June 9, 2026 17:10
@github-actions github-actions Bot added js/ts example-app Updates to code related to demonstration (example) applications documentation labels Jun 11, 2026
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca83fcd4-257b-4b7a-9fee-763871786c0f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds Node.js runtime support to the img2num WASM package via environment detection and worker_threads, implements async module initialization to defer execution until the worker is ready, demonstrates the capability with a new console example app, and releases a redesigned marketing homepage with live GitHub repository stats and complete styling.

Changes

Node.js Runtime Support and Documentation

Layer / File(s) Summary
Environment-Aware Worker Initialization
bindings/js/CMakeLists.txt, packages/js/wasmClient.js, packages/js/wasmWorker.js
Emscripten build adds node to environment targets. Worker initialization detects Node.js runtime and creates workers via worker_threads with path resolution, otherwise uses browser import.meta.url. Message posting and handling abstracted to each environment via postMsg and parentPort or self.
Async Module Loading
packages/js/safeWasmWrappers.js
Module now uses top-level await initWasmWorker() to defer exported function execution until worker initialization completes.
Node.js Console Example
example-apps/console-js/index.js, example-apps/console-js/package.json, pnpm-workspace.yaml
New example app demonstrates Node.js usage: creates minimal test image, converts to SVG via imageToSvg, logs output, and writes to file. Package configured as ESM with workspace dependency on img2num. Workspace updated to include the example.
Marketing Homepage and Documentation
docs/src/pages/index.jsx, docs/src/pages/index.module.css, docs/src/components/Hedgehog.jsx, docs/docusaurus.config.js, docs/src/pages/index.md
New homepage page with RasterToSvgDemo, HeroSection (fetches GitHub stars/forks on mount), BindingsSection, FeaturesSection, CtaBand, and LinksSection. Complete CSS module with light/dark theme variables and responsive layout. Hedgehog component now accepts parameterized size prop. Docusaurus config adds gtag fallback plugin. Legacy Markdown homepage removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Ryan-Millard/Img2Num#335: WASM entrypoint wrapper (imgToSVG) in packages/js/safeWasmWrappers.js directly connects to this PR's async worker initialization pattern.
  • Ryan-Millard/Img2Num#250: Initial JS/WASM package setup that introduced initWasmWorker and worker messaging; this PR converts those to async and environment-aware.

Suggested labels

js/ts, example-app, wasm, docs, tooling

Suggested reviewers

  • Ryan-Millard
  • Krasner

Poem

🐰 A rabbit hops through Node.js lands,
Where workers once were born from browser sands—
Now threads and ports make homepages gleam,
With stats that fetch and components that dream,
The console chirps, the docs now shine,
Node and web together, truly fine! 🌟

🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive Changes to docs files (Docusaurus config, Hedgehog component, homepage module, CSS styling) and documentation page updates are peripheral enhancements not directly required by issue #394 but reasonable additions to document the new functionality. The documentation and homepage changes appear to be enhancements beyond the core fix; clarify whether these were explicitly requested as part of issue #394 or if they represent scope creep.
✅ Passed checks (6 passed)
Check name Status Explanation
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation (Worker is not defined error in Node.js), the solution (using worker_threads), the specific files modified, and testing verification. It clearly describes all aspects of the changes.
Linked Issues check ✅ Passed The PR successfully addresses issue #394 by adding Node.js support via worker_threads detection, modifying wasmClient.js and wasmWorker.js for environment-aware worker initialization, updating CMakeLists.txt to support Node.js WASM compilation, and providing a Node.js example app demonstrating the fix.
No Ai Slop Pr Description ✅ Passed PR description is specific and detailed: identifies the exact problem (Web Workers in Node.js), explains the solution (worker_threads detection), lists concrete file changes with technical details,...
No Strangely-Named Root Markdown Files ✅ Passed The PR contains only standard, acceptable markdown files at repository root: CODE_OF_CONDUCT.md, CONTRIBUTING.md, and README.md. No unconventional markdown files were added.
Coderabbit Config Needs Update ✅ Passed PR introduces only existing file types (JS, JSX, CSS, MD, JSON, YAML, CMakeLists.txt) already covered in .coderabbit.yaml; no new languages, linter configs, or CI tooling files added.
Title check ✅ Passed The title follows Conventional Commit format with 'fix' prefix and clearly describes the main change: adding Node.js support via worker_threads to resolve the Worker API limitation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/pages/index.jsx`:
- Around line 47-59: The client-side useEffect currently calls
fetch("https://api.github.com/repos/Ryan-Millard/Img2Num") which risks hitting
GitHub's unauthenticated rate limit; change this to avoid direct browser calls
by either (A) moving the GitHub API call into a build-time step (e.g.,
Docusaurus data fetch during site build) and populate setStats with embedded
static data, or (B) create a small serverless/backend endpoint that the
useEffect calls instead (e.g., /api/github-stats) which queries GitHub with an
authenticated token and short-TTL caching (5–15 minutes) before returning JSON;
update the component to call that endpoint from useEffect and keep setStats
logic the same.
- Around line 47-59: The useEffect that calls
fetch("https://api.github.com/repos/Ryan-Millard/Img2Num") should use an
AbortController to avoid calling setStats after the component unmounts: create
an AbortController inside the useEffect, pass controller.signal to fetch, check
the fetch response and only call setStats when the request was not aborted, and
in the effect cleanup call controller.abort(); also handle the abort error in
the catch (ignore DOMException "AbortError") so it doesn't surface.

In `@example-apps/console-js/index.js`:
- Around line 1-13: Add concise, user-facing comments to this example: explain
the imports (writeFileSync and imageToSvg), document the pixels
Uint8ClampedArray as an RGBA 1x1 white pixel and why values are
[255,255,255,255], annotate width and height variables, comment the imageToSvg({
pixels, width, height }) call (including that it returns { svg } and uses
await), and note the writeFileSync('output.svg', svg) line saves the SVG to
disk; also keep the console.log lines as brief explanations of each step for
newcomers.

In `@packages/js/wasmWorker.js`:
- Around line 223-234: The Node path assumes parentPort exists; add a defensive
null check around parentPort before using it: after importing parentPort in the
isNode branch, verify parentPort is truthy and only then set postMsg = (data) =>
parentPort.postMessage(data) and call parentPort.on('message', ...) to invoke
handleMessage; if parentPort is null, set postMsg to a safe fallback (noop or an
error-throwing function) and/or throw a clear Error indicating this module must
run in a worker, so postMsg, parentPort and handleMessage usage are guarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 359a1fa1-b9cb-4cff-ba83-aea795260c1f

📥 Commits

Reviewing files that changed from the base of the PR and between 2427d1d and 7a0bb18.

⛔ Files ignored due to path filters (3)
  • docs/static/img/homepage-demo.png is excluded by !**/*.png
  • docs/static/img/homepage-demo.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (12)
  • bindings/js/CMakeLists.txt
  • docs/docusaurus.config.js
  • docs/src/components/Hedgehog.jsx
  • docs/src/pages/index.jsx
  • docs/src/pages/index.md
  • docs/src/pages/index.module.css
  • example-apps/console-js/index.js
  • example-apps/console-js/package.json
  • packages/js/safeWasmWrappers.js
  • packages/js/wasmClient.js
  • packages/js/wasmWorker.js
  • pnpm-workspace.yaml
💤 Files with no reviewable changes (1)
  • docs/src/pages/index.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*.txt

📄 CodeRabbit inference engine (.editorconfig)

**/*.txt: Do not enforce maximum line length for text files
Use 2-space indentation for text files

Files:

  • bindings/js/CMakeLists.txt
bindings/**

⚙️ CodeRabbit configuration file

bindings/**: Language bindings for the Img2Num library. Ensure the public API surface matches
the core C/C++ headers and that error propagation is handled correctly for each
binding language. Also ensure the relevant docstrings are present to enable
auto-generation of documentation.

Files:

  • bindings/js/CMakeLists.txt
**

⚙️ CodeRabbit configuration file

**: # Contributing to Img2Num

Want to contribute to Img2Num? There are a few things you need to know.

We wrote a contribution guide to help you get started.

A few important points:

  • Add tests with your PR — new features and bug fixes must include tests where appropriate. PRs without tests are unlikely to be approved.
  • Follow the repository's coding style rules.
  • Use the issue and PR templates when filing issues or submitting code. Your PR will be rejected if you don't.

If you're unsure what to change, open a discussion and someone will assist you.

Questions?

If you have questions or need help:

Thank you for improving Img2Num! 🎨🚀

**: BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: false

**: root = true

-------------------------

Global defaults

-------------------------

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120

-------------------------

C++ / C headers

-------------------------

[*.{hpp,cpp,c,h}]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120

-------------------------

CMake

-------------------------

[CMakeLists.txt]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120

[*.cmake]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120

-------------------------

Ja...

Files:

  • bindings/js/CMakeLists.txt
  • pnpm-workspace.yaml
  • example-apps/console-js/package.json
  • docs/src/components/Hedgehog.jsx
  • example-apps/console-js/index.js
  • packages/js/safeWasmWrappers.js
  • docs/docusaurus.config.js
  • packages/js/wasmClient.js
  • docs/src/pages/index.jsx
  • packages/js/wasmWorker.js
  • docs/src/pages/index.module.css
**/*.json

📄 CodeRabbit inference engine (.editorconfig)

Do not trim trailing whitespace in JSON files

Files:

  • example-apps/console-js/package.json
example-apps/**

⚙️ CodeRabbit configuration file

example-apps/**: - Example applications. These are for demonstration; keep them minimal and ensure
they correctly reflect the public API. Flag any use of internal/private APIs.

  • IMPORTANT: These example apps must be a good reflection of how to use Img2Num's
    library, which means that they need good comments and must have clean code. This
    is meant to be external and is designed for others to use to get started.

Files:

  • example-apps/console-js/package.json
  • example-apps/console-js/index.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.editorconfig)

**/*.{js,ts,jsx,tsx}: Use 2-space indentation for JavaScript and TypeScript files
Maintain 200 character maximum line length for JavaScript/TypeScript files

Files:

  • docs/src/components/Hedgehog.jsx
  • example-apps/console-js/index.js
  • packages/js/safeWasmWrappers.js
  • docs/docusaurus.config.js
  • packages/js/wasmClient.js
  • docs/src/pages/index.jsx
  • packages/js/wasmWorker.js
docs/**/*.{js,jsx,ts,tsx,mdx}

⚙️ CodeRabbit configuration file

docs/**/*.{js,jsx,ts,tsx,mdx}: This is the Docusaurus site source. Check for MDX compatibility, correct
plugin usage, and valid imports. Review docusaurus.config.js changes carefully
for broken routing or plugin config regressions.

Files:

  • docs/src/components/Hedgehog.jsx
  • docs/docusaurus.config.js
  • docs/src/pages/index.jsx
**/*.css

📄 CodeRabbit inference engine (.editorconfig)

Use 2-space indentation for CSS files

Files:

  • docs/src/pages/index.module.css
🧠 Learnings (1)
📚 Learning: 2026-03-14T17:55:21.677Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 278
File: example-apps/react-js/src/components/GlassModal.module.css:35-43
Timestamp: 2026-03-14T17:55:21.677Z
Learning: CSS Module files (*.module.css) should use camelCase identifiers for class names and keyframe names (e.g., fadeIn, scaleIn). Regular (vanilla) CSS files should use kebab-case. This convention helps distinguish module-scoped identifiers from global styles. Apply this rule to all CSS Module files across the repository (and similarly structured projects); for non-module CSS, continue using kebab-case.

Applied to files:

  • docs/src/pages/index.module.css
🔇 Additional comments (15)
docs/src/components/Hedgehog.jsx (1)

42-42: LGTM!

Also applies to: 64-65

docs/docusaurus.config.js (1)

99-111: LGTM!

docs/src/pages/index.jsx (1)

10-38: LGTM!

Also applies to: 61-120, 122-175, 177-225, 227-238, 241-308, 310-323

docs/src/pages/index.module.css (1)

1-595: LGTM!

bindings/js/CMakeLists.txt (1)

25-25: LGTM!

packages/js/wasmClient.js (4)

63-66: LGTM!


68-69: LGTM!


81-98: LGTM!


71-79: Node.js Worker ESM mode is already set via packages/js/package.json

  • packages/js/wasmWorker.js is under packages/js/, where packages/js/package.json sets "type": "module", so new Worker(.../wasmWorker.js) already loads it as ESM; { type: 'module' } is redundant here.

      		> Likely an incorrect or invalid review comment.
    
packages/js/wasmWorker.js (2)

157-159: LGTM!


161-221: LGTM!

packages/js/safeWasmWrappers.js (2)

19-22: LGTM!


24-241: LGTM!

example-apps/console-js/package.json (1)

1-11: LGTM!

pnpm-workspace.yaml (1)

4-4: LGTM!

Comment thread docs/src/pages/index.jsx
Comment thread example-apps/console-js/index.js Outdated
Comment on lines +1 to +13
import { writeFileSync } from 'fs';
import { imageToSvg } from 'img2num';

// 1x1 white pixel PNG for testing
const pixels = new Uint8ClampedArray([255, 255, 255, 255]);
const width = 1;
const height = 1;

console.log('Running img2num in Node.js...');
const { svg } = await imageToSvg({ pixels, width, height });
console.log('SVG output:', svg);
writeFileSync('output.svg', svg);
console.log('Saved to output.svg'); No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add educational comments to the example application.

Example apps are external-facing and designed to help others get started. Adding comments explaining each step would improve the learning experience for users new to the library.

📚 Suggested improvements
+/**
+ * Example: Using img2num in Node.js
+ *
+ * This example demonstrates how to use the img2num library in a Node.js
+ * environment to convert a raster image to SVG format.
+ */
 import { writeFileSync } from 'fs';
 import { imageToSvg } from 'img2num';

-// 1x1 white pixel PNG for testing
+// Create a minimal test image: 1x1 white pixel
+// In real usage, you would load an actual image file
 const pixels = new Uint8ClampedArray([255, 255, 255, 255]);
 const width = 1;
 const height = 1;

 console.log('Running img2num in Node.js...');
+
+// Convert the raster image to SVG
+// imageToSvg applies bilateral filtering, K-means clustering, and contour detection
 const { svg } = await imageToSvg({ pixels, width, height });
+
 console.log('SVG output:', svg);
+
+// Write the result to a file
 writeFileSync('output.svg', svg);
 console.log('Saved to output.svg');

Based on coding guidelines: example apps "must have good comments and must have clean code. This is meant to be external and is designed for others to use to get started."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { writeFileSync } from 'fs';
import { imageToSvg } from 'img2num';
// 1x1 white pixel PNG for testing
const pixels = new Uint8ClampedArray([255, 255, 255, 255]);
const width = 1;
const height = 1;
console.log('Running img2num in Node.js...');
const { svg } = await imageToSvg({ pixels, width, height });
console.log('SVG output:', svg);
writeFileSync('output.svg', svg);
console.log('Saved to output.svg');
/**
* Example: Using img2num in Node.js
*
* This example demonstrates how to use the img2num library in a Node.js
* environment to convert a raster image to SVG format.
*/
import { writeFileSync } from 'fs';
import { imageToSvg } from 'img2num';
// Create a minimal test image: 1x1 white pixel
// In real usage, you would load an actual image file
const pixels = new Uint8ClampedArray([255, 255, 255, 255]);
const width = 1;
const height = 1;
console.log('Running img2num in Node.js...');
// Convert the raster image to SVG
// imageToSvg applies bilateral filtering, K-means clustering, and contour detection
const { svg } = await imageToSvg({ pixels, width, height });
console.log('SVG output:', svg);
// Write the result to a file
writeFileSync('output.svg', svg);
console.log('Saved to output.svg');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example-apps/console-js/index.js` around lines 1 - 13, Add concise,
user-facing comments to this example: explain the imports (writeFileSync and
imageToSvg), document the pixels Uint8ClampedArray as an RGBA 1x1 white pixel
and why values are [255,255,255,255], annotate width and height variables,
comment the imageToSvg({ pixels, width, height }) call (including that it
returns { svg } and uses await), and note the writeFileSync('output.svg', svg)
line saves the SVG to disk; also keep the console.log lines as brief
explanations of each step for newcomers.

Source: Coding guidelines

Comment thread packages/js/wasmWorker.js Outdated
Comment on lines +223 to +234
// -------- Environment-aware message binding --------
// Use worker_threads in Node.js, Web Worker API in browser
let postMsg;

if (isNode) {
const { parentPort } = await import('worker_threads');
postMsg = (data) => parentPort.postMessage(data);
parentPort.on('message', (data) => handleMessage(data));
} else {
postMsg = (data) => self.postMessage(data);
self.onmessage = ({ data }) => handleMessage(data);
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding a null check for parentPort in Node.js worker context.

In Node.js worker_threads, parentPort is null when code runs in the main thread (outside a worker). While this file is only loaded as a Worker, a defensive null check would prevent confusing errors if accidentally imported in the main thread.

🛡️ Proposed defensive check
 if (isNode) {
   const { parentPort } = await import('worker_threads');
+  if (!parentPort) {
+    throw new Error('wasmWorker.js must run in a worker thread, not the main thread');
+  }
   postMsg = (data) => parentPort.postMessage(data);
   parentPort.on('message', (data) => handleMessage(data));
 } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// -------- Environment-aware message binding --------
// Use worker_threads in Node.js, Web Worker API in browser
let postMsg;
if (isNode) {
const { parentPort } = await import('worker_threads');
postMsg = (data) => parentPort.postMessage(data);
parentPort.on('message', (data) => handleMessage(data));
} else {
postMsg = (data) => self.postMessage(data);
self.onmessage = ({ data }) => handleMessage(data);
}
// -------- Environment-aware message binding --------
// Use worker_threads in Node.js, Web Worker API in browser
let postMsg;
if (isNode) {
const { parentPort } = await import('worker_threads');
if (!parentPort) {
throw new Error('wasmWorker.js must run in a worker thread, not the main thread');
}
postMsg = (data) => parentPort.postMessage(data);
parentPort.on('message', (data) => handleMessage(data));
} else {
postMsg = (data) => self.postMessage(data);
self.onmessage = ({ data }) => handleMessage(data);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/js/wasmWorker.js` around lines 223 - 234, The Node path assumes
parentPort exists; add a defensive null check around parentPort before using it:
after importing parentPort in the isNode branch, verify parentPort is truthy and
only then set postMsg = (data) => parentPort.postMessage(data) and call
parentPort.on('message', ...) to invoke handleMessage; if parentPort is null,
set postMsg to a safe fallback (noop or an error-throwing function) and/or throw
a clear Error indicating this module must run in a worker, so postMsg,
parentPort and handleMessage usage are guarded.

@Ryan-Millard Ryan-Millard changed the title Fix: Added node js support fix(packages/js): add node js support via worker_threads Jun 11, 2026
@Ryan-Millard
Ryan-Millard changed the base branch from main to dev June 11, 2026 16:23

@Ryan-Millard Ryan-Millard left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @Prachi-Gupta2808. This is great stuff! Thank you.

I just have a few changes I'd like implemented here before this gets merged. This looks like a good fix.

Additionally, I'd also like @Krasner to review this because I'm having some trouble with my computers at the moment and can't run anything on them.

I look forward to merging this. Thanks again!🦔

Comment thread example-apps/console-js/index.js
Comment thread packages/js/safeWasmWrappers.js Outdated
Comment thread packages/js/wasmClient.js Outdated
Comment thread packages/js/wasmWorker.js Outdated
@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

@Ryan-Millard & @Krasner I did the rest changes, except one, I looked at how the Python example uses cv2 to read and convert the image to RGBA, and the React example uses the browser's Image and canvas APIs via imageToUint8ClampedArray. Neither of these approaches work in a Node.js CLI context since there's no browser APIs available. I'd need a Node.js package to decode the image to raw RGBA pixel data. I was thinking sharp or jimp

Which package would you guys be okay with?

@Krasner

Krasner commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@Ryan-Millard & @Krasner I did the rest changes, except one, I looked at how the Python example uses cv2 to read and convert the image to RGBA, and the React example uses the browser's Image and canvas APIs via imageToUint8ClampedArray. Neither of these approaches work in a Node.js CLI context since there's no browser APIs available. I'd need a Node.js package to decode the image to raw RGBA pixel data. I was thinking sharp or jimp

Which package would you guys be okay with?

sharp seems fine

@Ryan-Millard

Ryan-Millard commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Hi @Prachi-Gupta2808, @Krasner's suggestion sounds good (Node doesn't have it built-in). Please just make sure it gets the Uint8ClampedArray in RGBA... format.

It should conceptually be an array like this:

[
  255, // Red
  255, // Green
  255, // Blue
  255, // Alpha
  ...
];

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

Hi @Ryan-Millard & @Krasner , The pipeline seems to be hanging after the WebGPU fallback messages, even on a tiny 20x20 image. It prints the two WebGPU warning messages and then just hangs indefinitely without producing any output or error. Is the CPU fallback expected to work in Node.js or is there something else needed to get it running?

@Krasner

Krasner commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Hi @Ryan-Millard & @Krasner , The pipeline seems to be hanging after the WebGPU fallback messages, even on a tiny 20x20 image. It prints the two WebGPU warning messages and then just hangs indefinitely without producing any output or error. Is the CPU fallback expected to work in Node.js or is there something else needed to get it running?

Hi @Ryan-Millard & @Krasner , The pipeline seems to be hanging after the WebGPU fallback messages, even on a tiny 20x20 image. It prints the two WebGPU warning messages and then just hangs indefinitely without producing any output or error. Is the CPU fallback expected to work in Node.js or is there something else needed to get it running?

I can take a look later today. Can you show me how to run the node code? I assume it's the console-js example

@Krasner

Krasner commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

The cpu fallback is just standard wasm c++

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

I can take a look later today. Can you show me how to run the node code? I assume it's the console-js example

Hi! Yes it's the console-js example. To run it:

# From repo root inside Docker
pnpm install

# Navigate to the example app
cd example-apps/console-js

# Run with an image path
node index.js <image-path>

# Example
node index.js test.jpg

It prints the WebGPU fallback warnings and then hangs after that without producing output.

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

The cpu fallback is just standard wasm c++

Would it help if I add some console.log statements to trace where exactly it's getting stuck?

@Krasner

Krasner commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

The cpu fallback is just standard wasm c++

Would it help if I add some console.log statements to trace where exactly it's getting stuck?

That would be helpful

@Krasner

Krasner commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

There are also 3 merge conflicts on this PR. Can you address them?

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Prachi-Gupta2808.

Have you tried any of the other example apps on your computer to verify that it's working?

It could also be your hardware that's a bit slow (especially on a CPU). Please would you give me the details of your hardware so I can confirm what's going on.

@Krasner

Krasner commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

@Ryan-Millard it's just not showing up in the diff viewer:
image

@Ryan-Millard

Copy link
Copy Markdown
Owner

That's probably a good approach

I think it's the best solution because webgpu isn't really an optional peer dependency - it's a hard requirement for the Node.js library.

@Ryan-Millard it's just not showing up in the diff viewer: image

I was talking about what I broke. I'm not sure what I broke.😭

@Krasner

Krasner commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

That's probably a good approach

I think it's the best solution because webgpu isn't really an optional peer dependency - it's a hard requirement for the Node.js library.

@Ryan-Millard it's just not showing up in the diff viewer: image

I was talking about what I broke. I'm not sure what I broke.😭

Oh, you changed webgpu from peerDependency to dependency and also removed it from example-apps/console-js/package.json in this commit 93576f2

but you're right it would be better to have a pure node.js build with webgpu as a real dependency, and then a browser build (which is the react-app as we have now) without it

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi. Here's an update from my side:

Unfortunately, I didn't realise the large scope that the issue related to this actually has and it would be pretty unfair for me to expect @Prachi-Gupta2808 to get it all done alone because it's not a very newcomer-friendly issue and she isn't very familiar with the codebase.

As a result, I will probably need to work on this PR a bit to set up the bundling since @Krasner isn't very experienced with it (unless you would like to try it out - I don't mind). With that, I'll probably only be able to work on it on Wednesday (or later).

What needs to be done:

  1. Bundler setup (preferably Vite since I have used it a lot)
  2. Multi-environment builds for Node & browser support
    • Move all code files to a new folder: src
    • Make environment specific folders: src/node and src/browser
  3. Set up something like aliases in Vite (so @env resolves to either src/node or src/browser depending on the target environment)
    • This is similar to the concept of macros in C/C++, but it's slightly safer
  4. Tests that the library works properly in each environment
  5. Likely CI integrations (can be in a follow-up PR)

Additionally, the introduction of Node has made #391 all the more relevant because newcomers now have to battle the various build systems individually:

  1. pnpm
  2. uv
  3. cmake
  4. node
  5. img2num scripts (not really a build system, but still a bit weird)
  6. npm (occasionally because JavaScript is poorly standardized)

We need to definitely implement #391 sooner before its scope grows out of control.😓


Thank you both (@Prachi-Gupta2808 and @Krasner) for your work on this pull request!

@Ryan-Millard

Copy link
Copy Markdown
Owner

That's probably a good approach

I think it's the best solution because webgpu isn't really an optional peer dependency - it's a hard requirement for the Node.js library.

@Ryan-Millard it's just not showing up in the diff viewer: image

I was talking about what I broke. I'm not sure what I broke.😭

Oh, you changed webgpu from peerDependency to dependency and also removed it from example-apps/console-js/package.json in this commit 93576f2

but you're right it would be better to have a pure node.js build with webgpu as a real dependency, and then a browser build (which is the react-app as we have now) without it

Please would you explain how the opaque error is caused by webgpu not being a peer dependency (if you know). I don't see a reason for it and may have just missed the problematic code.

@Krasner

Krasner commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Hi. Here's an update from my side:

Unfortunately, I didn't realise the large scope that the issue related to this actually has and it would be pretty unfair for me to expect @Prachi-Gupta2808 to get it all done alone because it's not a very newcomer-friendly issue and she isn't very familiar with the codebase.

As a result, I will probably need to work on this PR a bit to set up the bundling since @Krasner isn't very experienced with it (unless you would like to try it out - I don't mind). With that, I'll probably only be able to work on it on Wednesday (or later).

What needs to be done:

  1. Bundler setup (preferably Vite since I have used it a lot)

  2. Multi-environment builds for Node & browser support

    • Move all code files to a new folder: src
    • Make environment specific folders: src/node and src/browser
  3. Set up something like aliases in Vite (so @env resolves to either src/node or src/browser depending on the target environment)

    • This is similar to the concept of macros in C/C++, but it's slightly safer
  4. Tests that the library works properly in each environment

  5. Likely CI integrations (can be in a follow-up PR)

Additionally, the introduction of Node has made #391 all the more relevant because newcomers now have to battle the various build systems individually:

  1. pnpm
  2. uv
  3. cmake
  4. node
  5. img2num scripts (not really a build system, but still a bit weird)
  6. npm (occasionally because JavaScript is poorly standardized)

We need to definitely implement #391 sooner before its scope grows out of control.😓

Thank you both (@Prachi-Gupta2808 and @Krasner) for your work on this pull request!

Check PR #439

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

Hi. Here's an update from my side:

Unfortunately, I didn't realise the large scope that the issue related to this actually has and it would be pretty unfair for me to expect @Prachi-Gupta2808 to get it all done alone because it's not a very newcomer-friendly issue and she isn't very familiar with the codebase.

As a result, I will probably need to work on this PR a bit to set up the bundling since @Krasner isn't very experienced with it (unless you would like to try it out - I don't mind). With that, I'll probably only be able to work on it on Wednesday (or later).

What needs to be done:

  1. Bundler setup (preferably Vite since I have used it a lot)

  2. Multi-environment builds for Node & browser support

    • Move all code files to a new folder: src
    • Make environment specific folders: src/node and src/browser
  3. Set up something like aliases in Vite (so @env resolves to either src/node or src/browser depending on the target environment)

    • This is similar to the concept of macros in C/C++, but it's slightly safer
  4. Tests that the library works properly in each environment

  5. Likely CI integrations (can be in a follow-up PR)

Additionally, the introduction of Node has made #391 all the more relevant because newcomers now have to battle the various build systems individually:

  1. pnpm
  2. uv
  3. cmake
  4. node
  5. img2num scripts (not really a build system, but still a bit weird)
  6. npm (occasionally because JavaScript is poorly standardized)

We need to definitely implement #391 sooner before its scope grows out of control.😓

Thank you both (@Prachi-Gupta2808 and @Krasner) for your work on this pull request!

I will look into other issues and would contribute in them if I find something interesting!
Thank you

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

In case there is something I can help you guys with, please let me know.

@Ryan-Millard

Copy link
Copy Markdown
Owner

In case there is something I can help you guys with, please let me know.

Thank you. I don't think that there is much more that you can do on this issue because it'll involve some complex things that I'll have to set up.

Thank you for the wonderful work you've done on it. I'll try to get this PR merged soon.

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hey @Prachi-Gupta2808, @Krasner and I have been really impressed with your work so far. This PR and the nice docs contributions are great. @Krasner and I are considering adding you as a maintainer (mainly for the docs/ and JS side for now).

Would you be interested in that?

@Prachi-Gupta2808

Prachi-Gupta2808 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Hiii @Ryan-Millard and @Krasner , I would love that! But as you guys already know, I am new to this so I might be a little too much of a "Oh I have a doubt" person for you both.
Alongside this, I don't exactly know alot about what it takes to be a maintainer. If you both be okay with me being this new and stuff, I'll definitely love to work on this as a maintainer.

One more thing, can you guys give me your linkedin or slack invite link maybe? I did try searching you guys. That way I can ask you both any doubt if I have, only if you both would be interested in helping me a little.

Thank youuu!

@Krasner

Krasner commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Hiii @Ryan-Millard and @Krasner , I would love that! But as you guys already know, I am new to this so I might be a little too much of a "Oh I have a doubt" person for you both. Alongside this, I don't exactly know alot about what it takes to be a maintainer. If you both be okay with me being this new and stuff, I'll definitely love to work on this as a maintainer.

One more thing, can you guys give me your linkedin or slack invite link maybe? I did try searching you guys. That way I can ask you both any doubt if I have, only if you both would be interested in helping me a little.

Thank youuu!

No problem, we all have different levels of experience in different areas. The project is growing so it's really nice to have some help.

We prefer to keep most, if not all, conversations on github - through issues, discussions, or PRs so that they are visible to all new comers. If you have any questions, issues or discussions would be a good place for them.

In fact this will be a good test to check if we can help new comers get up to speed. Let us know your experience contributing to this repo. Was it documented enough to understand how this repo functions? (We're working on updating the documentation)

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

Thank you so much, I'd love to! 🦔

The docs were pretty easy to follow especially coming in with some React experience. Setup-wise it was also smooth. The one thing I had to ask about was the WASM build step (emcmake cmake + cmake --build) I couldn't find it documented in the contributing guide and had to ask Ryan directly. That would be worth adding for contributors working on JS bindings.

Apart from that the codebase is well commented and easy to navigate. I'll spend today and tomorrow reading through more of the documentation properly ( I want to learn more about the project) and will share more detailed feedback after that!

@Krasner

Krasner commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

The one thing I had to ask about was the WASM build step (emcmake cmake + cmake --build) I couldn't find it documented in the contributing guide and had to ask Ryan directly

PR #439 should make that much clearer. We are introducing a Justfile (https://github.com/casey/just) to simplify and bundle commands

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Prachi-Gupta2808. I'm to hear that you're interested in joining us. Welcome to the team.🥳🦔

Outsider perspectives are important to us and we appreciate the insight about the WebAssembly setup. We definitely need to overhaul our documentation because it's been neglected for too long.

@Krasner maybe we should set this up in #439 with something like just init-img2num or something like that to ensure the project is setup correctly. I know people have struggled with the submodules in the past, so this could be a very good safety measure.

@Krasner

Krasner commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

@Krasner maybe we should set this up in #439 with something like just init-img2num or something like that to ensure the project is setup correctly. I know people have struggled with the submodules in the past, so this could be a very good safety measure.

just added it

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Krasner. I'm a bit stuck with this implementation because it doesn't seem like there is a clean way to implement it across the browser and node environments - the bundler is causing problems because it embeds the worker as a base64 string in the output file (for efficiency), which causes problems when calling the WebAssembly. On the Node side of things, it needs a relative path, which Vite breaks when it builds because it restructures everything.

Using the current approach, I cannot seem to get both environments to work simultaneously - getting node to work breaks the browser code and vice versa. I'm trying my best to have separate builds and isolate the node- and browser-specific imports so they aren't included in the final outputs for both (e.g., url in the browser isn't good because it is only node-compatible).

Overall, I think it may actually be a better idea to remove the workers entirely from the library because they aren't really necessary and cause additional work for us for no reason. By removing it, we would be transferring the responsibility onto the user, which could be a good and a bad thing at the same time:

  • It lets them choose if they want a thread-blocking function call and how to handle it if they don't want that
  • It may make it slightly difficult for beginner developers to use our library - I don't think this is much of a concern, though, because the people who use this library likely aren't new to the world of programming because at least knowing the difference between raster and SVG formats requires a basic enough understanding of programming to be able to make such decisions easily. To support those who struggle a bit, we could also put some examples in the documentation to explain the possibilities to avoid long, blocking function calls.

I'd like to know your thoughts on the matter. I think this would be a good idea because we don't handle threading and stuff like that for the other libraries - they are expected to deal with it themselves.

@Ryan-Millard

Copy link
Copy Markdown
Owner

Hi @Prachi-Gupta2808,

When you have some time, please take a look at this blog post: We're Expanding the Maintainer Team

After reading it, could you let us know which areas of the project you feel most comfortable working on? We'd like to use that information when assigning issues and planning future work.

Also, are you familiar with reviewing pull requests on GitHub? Reviewing code is an important part of being a maintainer. If you're not sure how the process works, this short video gives a good overview: https://www.youtube.com/watch?v=lSnbOtw4izI&t=136s

It would also be helpful if you could tell us a bit about your current experience and interests. For example:

  • JavaScript / TypeScript
  • React
  • Documentation and technical writing
  • C++
  • WebAssembly
  • Testing and CI
  • Git and GitHub workflows
  • Project management / issue triage

There's no expectation that you'll be experienced in all of these areas. Knowing where you're most confident will help us assign suitable tasks and update our CODEOWNERS file over time.

Finally, since you're new to maintaining open-source projects, please don't hesitate to ask questions (I don't find lots of questions annoying). We expect questions, and we'd much rather have discussions happen publicly in issues, discussions, and pull requests where other contributors can benefit from them too.

Welcome to the team!🦔🦔🦔🦔

@Prachi-Gupta2808

Copy link
Copy Markdown
Contributor Author

@Ryan-Millard , Thanks for the warm welcome!

No, I currently don't have any familiarity with reviewing PRs on github but would catch up to it, thank you for the resource.

I'll mostly be comfortable with:

  • JavaScript/ TypeScript ( I am currently learning typescript and have experience in JS )
  • React
  • C++

Outside of this I really want to work on Documentation and Technical writing because I am an author too and like it just makes me feel curious about trying it. I want to give it a shot. Apart from this I wish to learn testing and CI too but I really don't want to make img2num my "figuring it out project" with lots of new experiments, I think I can do it over time.

@Ryan-Millard

Copy link
Copy Markdown
Owner

No, I currently don't have any familiarity with reviewing PRs on github but would catch up to it, thank you for the resource.

If you ever need any help with it, let me know. I'd be happy to help you.

I'll mostly be comfortable with:

  • JavaScript/ TypeScript ( I am currently learning typescript and have experience in JS )
  • React
  • C++

That sounds good - those are our main languages (we mainly use JS for scripting). We don't currently use TypeScript in this project because the benefits of it aren't really felt in our use cases.

Outside of this I really want to work on Documentation and Technical writing because I am an author too and like it just makes me feel curious about trying it. I want to give it a shot.

Docusaurus is a very good one because a lot of projects use it for their documentation because it's simple to use. I really want to upgrade the documentation site because it's still using the default Docusaurus configuration, but I have no time for it - you're free to work on that if you'd like, but it's not an important feature.

Apart from this I wish to learn testing and CI too but I really don't want to make img2num my "figuring it out project" with lots of new experiments, I think I can do it over time.

I dislike how GitHub set this stuff up. It's very difficult to test most of the time. For simpler workflows, I usually test them out on a private repository that I don't care about so I don't have to worry about thousands of mistakes on this repository. In the end, nothing is truly testable and you just have to YOLO it sometimes.😅😅

@Krasner

Krasner commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@Ryan-Millard do you have a brach with your Node experiments that I can take a look at?

@Ryan-Millard

Copy link
Copy Markdown
Owner

@Ryan-Millard do you have a brach with your Node experiments that I can take a look at?

https://github.com/Ryan-Millard/Img2Num/tree/wip/pr-433/fix/node-js-support

@Ryan-Millard

Copy link
Copy Markdown
Owner

#449 is the succeeds this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build-system c/c++ Changes to C or C++ files core documentation example-app Updates to code related to demonstration (example) applications js/ts tooling wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(packages/js): img2num package does not support Node.js environments

3 participants