Playground: bundle babylonjs-addons + polyfill ES2019/ES2022 gaps on Chakra#1700
Playground: bundle babylonjs-addons + polyfill ES2019/ES2022 gaps on Chakra#1700bkaradzic-microsoft wants to merge 6 commits into
Conversation
Chakra's built-in Error constructor predates ES2022. When called with
`new Error("msg", { cause: e })` it stringifies the options bag into
.message ("[object Object]") and never sets .cause. Babylon.js v6+
uses this signature in its shader-compile error rethrow, so on every
Chakra-based BN backend (the Win32 default) a real glslang/SPIRV-Cross
error surfaces in stdout as `Error: [object Object]` with no further
detail, hiding the actual diagnostic.
Add a small JS polyfill loaded before any other script that:
- Probes ES2022 support via `new Error("x", {cause: 42})` and returns
early on engines that already implement the spec (V8, modern JSC).
- Replaces Error and the six standard subclasses with a thin wrapper
that forwards the message string and attaches `cause` from the
options bag via Object.defineProperty (non-enumerable, matches V8
semantics).
- Uses Reflect.construct to preserve `new.target` so subclassing via
`class X extends Error { ... }` keeps the correct prototype chain.
Chakra predates ES2019 and only implements the older trimLeft / trimRight names. Babylon.js's ShaderProcessor and NodeMaterial code paths use the newer trimEnd name, so on Chakra-based BN backends those paths fail with `TypeError: Object doesn't support property or method 'trimEnd'`. Add a tiny shim loaded right after error_polyfill.js that aliases String.prototype.trimEnd -> trimRight and trimStart -> trimLeft when the ES2019 names are missing. Both pairs are standard aliases in the spec, so engines that already provide the modern names are not touched.
Babylon Native ships babylonjs, babylonjs-gui, babylonjs-loaders, babylonjs-materials, and babylonjs-serializers alongside its Playground app but not babylonjs-addons. The addons package hosts newer Babylon .js components (Atmosphere, LiquidRenderingSceneComponent, ...) under the global `ADDONS` namespace, so any Playground snippet that uses them throws `ReferenceError: 'ADDONS' is not defined`. Add the npm dependency, ship babylonjs.addons.js as a Playground resource via BABYLON_SCRIPTS, and load it right after babylon.max.js so the addons-side initialization sees a fully constructed BABYLON global.
There was a problem hiding this comment.
Pull request overview
This PR updates the Babylon Native Playground to improve compatibility with Chakra-based runtimes and to ship the babylonjs-addons bundle so Playground snippets using the ADDONS global can run.
Changes:
- Load early JS polyfills for ES2022
Error(message, options)and ES2019String.prototype.trimStart/trimEndbefore Babylon scripts. - Add
babylonjs-addonsto the shipped Playground scripts and load it afterbabylon.max.js. - Add the new polyfill scripts to Playground build packaging and add the npm dependency.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Apps/Playground/Shared/AppContext.cpp | Loads the new polyfill scripts first and adds babylonjs.addons.js to the script load order. |
| Apps/Playground/Scripts/string_polyfill.js | Adds Chakra-targeted trimStart/trimEnd alias polyfill. |
| Apps/Playground/Scripts/error_polyfill.js | Adds Chakra-targeted Error(message, options) / cause polyfill for Error and standard subclasses. |
| Apps/Playground/CMakeLists.txt | Packages the new polyfill scripts and adds babylonjs.addons.js to copied/embedded script resources. |
| Apps/package.json | Adds babylonjs-addons dependency. |
| Apps/package-lock.json | Records the resolved babylonjs-addons dependency (currently introducing an additional Babylon.js version). |
Files not reviewed (1)
- Apps/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)
Apps/package.json:18
- Using
^9.3.4forbabylonjs-addonsallows it to resolve to a newer 9.x than the rest of the Babylon packages (and the bundledbabylon.max.js). To avoid ABI/API mismatches and duplicate installs, pinbabylonjs-addonsto the same exact version asbabylonjs(or update all Babylon packages together).
"dependencies": {
"babylonjs": "^9.3.4",
"babylonjs-addons": "^9.3.4",
"babylonjs-gltf2interface": "^9.3.4",
"babylonjs-gui": "^9.3.4",
"babylonjs-loaders": "^9.3.4",
"babylonjs-materials": "^9.3.4",
"babylonjs-serializers": "^9.3.4",
Apps/package-lock.json:2619
- This nested
node_modules/babylonjs-addons/node_modules/babylonjsentry indicatesbabylonjs-addonspulled in its own Babylon.js version (9.7.0). If the Playground loadsbabylon.max.jsfrom the top-level install, this duplication is unnecessary at best and can become a runtime mismatch at worst; aligning package versions should eliminate this nested dependency.
bghgary
left a comment
There was a problem hiding this comment.
[Reviewed by Copilot on behalf of @bghgary]
The addons bundling is fine, but the Error and String polyfills should not be JS scripts loaded by the Playground app — that only fixes it for the Playground. Any other app embedding Babylon Native with Chakra would hit the same failures and have to independently discover and ship the same workaround. These should be proper C++ polyfills (like Console, TextDecoder, etc.) with an Initialize(Napi::Env) entry point so every consumer gets them automatically.
Addresses bghgary's review of PR BabylonJS#1700: The Error(message, options) and String.prototype.trimEnd/trimStart polyfills were originally loaded as JS scripts by the Playground app. That only fixed Chakra in the Playground - every other app embedding Babylon Native with Chakra would have had to independently discover and ship the same workaround. Move both into proper C++ polyfills under Polyfills/ with an Initialize(Napi::Env) entry point, matching the Window / Canvas / TextDecoder pattern: - Polyfills/ErrorCause/ (BABYLON_NATIVE_POLYFILL_ERRORCAUSE) - Polyfills/StringTrim/ (BABYLON_NATIVE_POLYFILL_STRINGTRIM) Each polyfill embeds the verbatim ES-compliant JS shim as a raw string and injects it via napi env.RunScript() inside Initialize(). The shims remain no-ops on engines that already implement the relevant spec (V8, modern JavaScriptCore). AppContext now calls Babylon::Polyfills::ErrorCause::Initialize(env) and Babylon::Polyfills::StringTrim::Initialize(env) inside the runtime Dispatch lambda, before any other JS loads, and the two JS files plus their ScriptLoader entries are removed. Also addresses bghgary's second comment: babylonjs-addons resolved to 9.7.0 in the lockfile while the other Babylon packages were pinned at 9.3.4, opening a drift window where addons-side code could reference APIs added after 9.3.4 that are not in the shipped babylon.max.js. Pin the lockfile entry to 9.3.4 to match the rest. Smoke-tested locally (Win32_x64_Sanitizers RelWithDebInfo): EXR Loader and tests 0-5 all pass under ASAN+UBSan, no TypeError on trimEnd and no Error: [object Object] surfaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI on PR BabylonJS#1700 surfaced two distinct problems: 1. JSI N-API variant has no Napi::Env::RunScript(...). It exposes script evaluation through a free function Napi::Eval(env, source, url) declared in <napi/env.h>, which only exists in the JSI variant. Use __has_include(<napi/env.h>) to pick the right entry point at compile time so the same source builds against both the shared and JSI N-API surfaces. The shim itself stays identical. 2. Android builds the Playground via Apps/Playground/Android/ BabylonNative/CMakeLists.txt, which has its own target_link_libraries list and is not aware of the top-level Apps/Playground/CMakeLists.txt additions. Add ErrorCause and StringTrim there too so AppContext.cpp can include the new public headers. Verified ErrorCause and StringTrim compile under JSI locally (build\\win32_jsi RelWithDebInfo, .lib files produced). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two small fixes to the patched Error/TypeError/... constructors so they're faithful drop-ins for the originals: 1. Redefine `Orig.prototype.constructor` to point at Patched (wrapped in try/catch in case the prototype is non-configurable). Before this, `(new Error()).constructor` resolved to `Orig` -- not the global `Error` (which is now Patched). `instanceof` already worked because the prototype chain is unchanged; this just fixes `.constructor` identity for callers that key off it (serializers, error reporters, anything doing `err.constructor === Error`). 2. `Object.setPrototypeOf(Patched, Orig)` so Patched inherits all of Orig's static methods/properties (`captureStackTrace`, `stackTraceLimit`, anything else V8 ships). Subsumes the previous one-off `Patched.captureStackTrace = Orig.captureStackTrace` copy. Verified against a Chakra-like simulation (Node, class-based Old extends): the patched constructor identity, prototype-chain `instanceof`, and `Reflect.construct` new.target preservation all hold. Addresses Copilot review feedback on PR BabylonJS#1700. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
[Reviewed by Copilot on behalf of @bghgary]
Apologies — my earlier review asked you to move these into Polyfills/ C++ modules; on reflection that was the wrong direction. They shouldn't exist at all (concerns inline). babylonjs-addons bundling is fine.
| @@ -0,0 +1,132 @@ | |||
| #include <Babylon/Polyfills/ErrorCause.h> | |||
There was a problem hiding this comment.
Drop (sorry — flipping my earlier ask). Either run Playground validation against an ES-compliant engine (Win32_x64_V8_D3D11 exists in CI), or bundle babel-standalone in the Playground to transpile snippets at load if we want to keep Chakra coverage. Chakra consumers use Babel themselves. If error surfacing through Node-API is degraded on Chakra, fix in JsRuntimeHost.
| @@ -0,0 +1,48 @@ | |||
| #include <Babylon/Polyfills/StringTrim.h> | |||
There was a problem hiding this comment.
Drop (sorry — same as ErrorCause). Switching Playground validation off Chakra makes this unnecessary.
Three small, self-contained Playground-side fixes:
1. ES2022
Error(message, options)polyfill for ChakraChakra's built-in
Errorconstructor predates ES2022 and treats its secondargument as part of the message:
new Error("hello", { cause: 42 })producesan
Errorwhose.messageis"[object Object]"and whose.causeisundefined. Babylon.js's shader-compile error rethrow uses this signature,so on every Chakra-based BN backend (the Win32 default) real
glslang/SPIRV-Cross diagnostics surface in stdout as
Error: [object Object]with no further detail, hiding the actual error.Apps/Playground/Scripts/error_polyfill.jsis loaded before any otherscript. It self-detects ES2022 support (no-op on V8 / modern JSC) and on
Chakra patches
Errorplus the six standard subclasses (TypeError,RangeError,SyntaxError,ReferenceError,URIError,EvalError)with a thin wrapper that forwards the message string and attaches
causeviaObject.defineProperty. UsesReflect.constructto preservenew.target, soclass X extends Error { ... }keeps the correctprototype chain.
Verified end-to-end on Chakra (
build/win32=NAPI_JAVASCRIPT_ENGINE=Chakra):On a known shader-compile failure scenario the captured error transforms from
BJS - Error: [object Object]toBJS - Error: SHADER ERROR\nERROR: 2 compilation errors. No code generated.i.e. the actual glslang diagnostic now reaches stdout.
2. ES2019
String.prototype.trimEndpolyfill for ChakraChakra predates ES2019 and only implements the older
trimLeft/trimRight.Babylon.js's
ShaderProcessorandNodeMaterialpaths calltrimEnd, soon Chakra those paths throw
TypeError: Object doesn't support property or method 'trimEnd'.Apps/Playground/Scripts/string_polyfill.jsaliasesString.prototype.trimEnd -> trimRightandtrimStart -> trimLeftwhen theES2019 names are missing. Both pairs are spec-standard aliases, so engines
that already provide the modern names are untouched.
Verified:
Merge Meshes with submeshesandBones and morphs computation ordernow validate; the'trimEnd'error is gone from the other threeknown affected tests (which now fail for unrelated reasons tracked elsewhere).
3. Bundle
babylonjs-addonsin PlaygroundBabylon Native ships
babylonjs,babylonjs-gui,babylonjs-loaders,babylonjs-materials, andbabylonjs-serializersalongside its Playgroundapp but not
babylonjs-addons. The addons package hosts newer Babylon.jscomponents (
Atmosphere,LiquidRenderingSceneComponent, ...) under theglobal
ADDONSnamespace, so any Playground snippet that uses them throwsReferenceError: 'ADDONS' is not defined.Add the npm dependency, ship
babylonjs.addons.jsas a Playground resourcevia
BABYLON_SCRIPTS, and load it right afterbabylon.max.jsso theaddons initialization sees a fully constructed
BABYLONglobal.Verified: all four Atmosphere snippets (
Sunset,Night,Night (Planet Origin),Space View) no longer throw theADDONSReferenceError. They still fail on a separateTexture layers are not supported in Babylon Native(3D-texture / NativeEngine gap, trackedseparately) but the
ADDONS-bundling fix is independently complete.Risk
behaviour (V8 / modern JSC), so the V8 / JSC / iOS / macOS / Android paths
are unchanged.
babylonjs-addonsonly adds one moreLoadScriptcall; the npm packageis in the same release line (
^9.3.4) as the other Babylon scripts thePlayground already ships.
is touched.
Smoke regression check
Five known-passing indices (
0,39,47,165,200) produce the sameexit code / pass count as
upstream/masterafter this branch.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com