[Demo][Electron] Load prebuilt binaries at runtime#1546
Open
minggangw wants to merge 4 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the Electron demos’ ability to run using rclnodejs prebuilt binaries at runtime (instead of requiring electron-rebuild), and adds a couple of Electron-demo runtime UX/compatibility tweaks.
Changes:
- Extend the native addon loader to (a) mirror a matched prebuilt binary into
build/Releaseand (b) attempt Electron-targeted compilation settings when building from source under Electron. - Update Electron demo docs and Forge configs to stop using
electron-rebuildand to excluderclnodejsfrom Forge’s rebuild step. - Improve demo robustness by catching 3D scene init failures in the renderer and by enabling GPU/WebGL-related Electron flags in the TF2 demo.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/native_loader.js | Adds mirroring of matched prebuilds to build/Release and introduces Electron-aware source build environment. |
| demo/electron/turtle_tf2/renderer.js | Wraps 3D initialization in try/catch and updates the loading screen on failure. |
| demo/electron/turtle_tf2/README.md | Updates setup/troubleshooting guidance to rely on runtime prebuilt selection instead of manual rebuild. |
| demo/electron/turtle_tf2/package.json | Removes rebuild script and configures Forge rebuild to ignore rclnodejs. |
| demo/electron/turtle_tf2/main.js | Adds Chromium command-line switches to improve WebGL availability on constrained systems. |
| demo/electron/topics/README.md | Updates setup instructions to avoid electron-rebuild and rely on runtime prebuilt selection. |
| demo/electron/topics/package.json | Removes rebuild script and configures Forge rebuild to ignore rclnodejs. |
| demo/electron/manipulator/README.md | Updates setup guidance to source ROS 2 for prebuilt selection and removes rebuild instructions. |
| demo/electron/manipulator/package.json | Removes rebuild script and configures Forge rebuild to ignore rclnodejs. |
| demo/electron/car/README.md | Updates setup/troubleshooting to avoid manual rebuild and rely on prebuilt selection. |
| demo/electron/car/package.json | Removes rebuild script and configures Forge rebuild to ignore rclnodejs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+63
to
+71
| if (detectPrebuildRuntime() === 'electron' && process.versions.electron) { | ||
| // node-gyp targets Electron via --target (Electron version) + --dist-url | ||
| // (Electron headers); it reads these from npm_config_* env vars. | ||
| env.npm_config_target = process.versions.electron; | ||
| env.npm_config_dist_url = 'https://electronjs.org/headers'; | ||
| env.npm_config_arch = process.arch; | ||
| debug(`Building from source for Electron ${process.versions.electron}`); | ||
| } else { | ||
| debug('Building from source for Node.js'); |
Comment on lines
+37
to
+44
| // Copy a matched prebuilt binary into build/Release so that standard binary | ||
| // resolution (and packaged Electron apps where ROS_DISTRO may be unset at | ||
| // runtime) can still locate it. Non-destructive: never overwrites an existing | ||
| // build output, so local source builds are preserved. | ||
| function mirrorPrebuiltToBuildRelease(srcPath) { | ||
| try { | ||
| const destDir = path.join(__dirname, '..', 'build', 'Release'); | ||
| const destPath = path.join(destDir, 'rclnodejs.node'); |
Comment on lines
+70
to
+76
| const loadingText = loadingScreen.querySelector('div:last-child'); | ||
| if (loadingText) { | ||
| loadingText.textContent = | ||
| 'Failed to initialize 3D view (WebGL unavailable): ' + err.message; | ||
| loadingText.style.color = '#ff4444'; | ||
| } | ||
| } |
Comment on lines
+16
to
+21
| // Allow WebGL to work on systems without a usable GPU (e.g. WSL2, headless, | ||
| // or VMs) where Chromium blocklists hardware WebGL. Without this, the 3D | ||
| // renderer fails to create a WebGL context and the visualization cannot start. | ||
| app.commandLine.appendSwitch('ignore-gpu-blocklist'); | ||
| app.commandLine.appendSwitch('enable-unsafe-swiftshader'); | ||
|
|
Comment on lines
+41
to
+50
| function mirrorPrebuiltToBuildRelease(srcPath) { | ||
| try { | ||
| const destDir = path.join(__dirname, '..', 'build', 'Release'); | ||
| const destPath = path.join(destDir, 'rclnodejs.node'); | ||
| if (fs.existsSync(destPath)) { | ||
| return; | ||
| } | ||
| fs.mkdirSync(destDir, { recursive: true }); | ||
| fs.copyFileSync(srcPath, destPath); | ||
| debug(`Mirrored prebuilt binary to ${destPath}`); |
Comment on lines
116
to
129
| try { | ||
| const candidate = getTaggedPrebuildFilename({ | ||
| rosDistro, | ||
| ubuntuCodename, | ||
| arch, | ||
| runtime, | ||
| }); | ||
| const candidatePath = path.join(prebuildDir, candidate); | ||
|
|
||
| if (fs.existsSync(candidatePath)) { | ||
| debug(`Found ${runtime} prebuilt binary: ${candidate}`); | ||
| mirrorPrebuiltToBuildRelease(candidatePath); | ||
| return require(candidatePath); | ||
| } |
Comment on lines
+60
to
+80
| function buildFromSource() { | ||
| const env = { ...process.env }; | ||
|
|
||
| if (detectPrebuildRuntime() === 'electron' && process.versions.electron) { | ||
| // node-gyp targets Electron via --target (Electron version) + --dist-url | ||
| // (Electron headers); it reads these from npm_config_* env vars. | ||
| env.npm_config_target = process.versions.electron; | ||
| env.npm_config_dist_url = 'https://electronjs.org/headers'; | ||
| env.npm_config_arch = process.arch; | ||
| debug(`Building from source for Electron ${process.versions.electron}`); | ||
| } else { | ||
| debug('Building from source for Node.js'); | ||
| } | ||
|
|
||
| childProcess.execSync('npm run rebuild', { | ||
| stdio: 'inherit', | ||
| cwd: path.join(__dirname, '..'), | ||
| timeout: 300000, // 5 minute timeout | ||
| env, | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make the Electron demos load the prebuilt
rclnodejsnative binary at runtime instead of recompiling it duringelectron-forgepackaging, fix theturtle_tf2demo's stuck loading screen on GPU-less environments (WSL2/headless), shorten the demo READMEs, and harden the native-loader unit tests.Native loader (
lib/native_loader.js)mirrorPrebuiltToBuildRelease(): when an exact-match prebuilt binary is found, copy it intobuild/Release/rclnodejs.nodeso standardbindingsresolution (and packaged Electron apps whereROS_DISTROmay be unset at runtime) can locate it. Non-destructive — never overwrites an existing build output, preserving local source builds.buildFromSource()and make it Electron-aware: when running under Electron, target Electron's headers/ABI vianpm_config_target,npm_config_dist_url, andnpm_config_archso the compiled addon matches the runtime (the addon links libuv directly, so host runtime matters).npm run rebuildexecSynccalls (forced build + fallback build paths) with the sharedbuildFromSource()helper.Electron demos — load prebuilt at packaging time
demo/electron/{car,manipulator,topics,turtle_tf2}/package.json: Remove the"rebuild": "electron-rebuild"script; addforge.rebuildConfig.ignoreModules: ["rclnodejs"]soelectron-forgedoes not recompilerclnodejs, using the shipped prebuilt binary instead.turtle_tf2demo — WebGL / loading-screen fixesmain.js: appendignore-gpu-blocklistandenable-unsafe-swiftshadercommand-line switches so WebGL works on systems without a usable GPU (WSL2, headless, VMs) where Chromium blocklists hardware WebGL.renderer.js: register ROS2 status listeners before 3D scene setup so the loading screen always reflects initialization progress, and wrapinitializeScene()/setupEventListeners()/setupKeyboardControls()in a try/catch that surfaces a clear user-facing message ("Failed to initialize 3D view (WebGL unavailable): …") if scene init fails.Documentation
car,manipulator,topics,turtle_tf2) — net ~1100 lines removed, kept accurate against eachpackage.json(scripts, dependency versions, output directory names).Tests (
test/test-native-loader.js)existsSync.getCalls()(filtering for theprebuilds+rclnodejs.nodepath) instead of relying onexistsSync.lastCall, which now points at the newbuild/Releasemirror probe. Adds a guard assertion for a clearer failure message and drops the now-redundantrclnodejs.nodecheck.Fix: #1545