fix: probe isWellFormed per call so Hermes uses the shim - #55
fix: probe isWellFormed per call so Hermes uses the shim#55gomesalexandre wants to merge 1 commit into
Conversation
_isWellFormed resolved the native String.prototype.isWellFormed probe once at module load, inside a /* @__PURE__ */ IIFE. A bundler that minifies against V8 (Metro targeting Hermes) constant-folds that probe to the native branch and bakes it in; on Hermes, where isWellFormed is absent, utf8.decode then calls a missing method and throws. Probe the runtime argument instead. typeof str.isWellFormed cannot be constant-folded (str is not known at build time), so each engine takes the branch that works - native on V8, the encodeURI shim on Hermes. The cost is a cheap typeof per call rather than one at load. Adds a regression test that imports with isWellFormed present, removes it, and checks decode still accepts well-formed and rejects malformed strings. It fails on the resolve-once version and passes here. closes paulmillr#54
|
Can you measure perf before/after? |
|
both native and shim so 4 cases |
Measured with the repo's
No measurable difference in any of the 4 cases: the per-call If you'd rather keep the resolve-once shape without the build-time fold, a lazy variant that resolves at the first runtime call and caches benches the same as resolve-once (native ~535 ns) and is still fold-safe: let _iswf: ((s: string) => boolean) | undefined;
const _isWellFormed = (str: string): boolean => {
if (_iswf === undefined)
_iswf = typeof (str as any).isWellFormed === 'function'
? (s: string) => (s as any).isWellFormed()
: _isWellFormedShim;
return _iswf(str);
};Happy to switch to that if you prefer it over the per-call probe. benchmark script (
|
closes #54
what
utf8.decodethrowsstr.isWellFormed is not a functionon Hermes (React Native). The native-vs-shim check forString.prototype.isWellFormedwas resolved once at module load:When Metro minifies the bundle it evaluates that probe against its own V8 (where
isWellFormedexists), constant-folds the ternary to the native branch, and bakes in(str) => str.isWellFormed(). At Hermes runtime the method is absent, so the strict-UTF-8 well-formedness gate throws instead of falling back to the shim.how
Probe the runtime argument at call time:
typeof str.isWellFormeddepends on a value the bundler does not know at build time, so it cannot be constant-folded. Each engine takes the branch that actually works - native on V8, theencodeURIshim on Hermes. The only cost is a cheaptypeofper call rather than one at load.test
Adds
utf8 env: well-formed check survives String.prototype.isWellFormed removal after load- it imports whileisWellFormedexists, removes it (emulating the V8-minified / Hermes-runtime split), and checksutf8.decodestill accepts a well-formed string and rejects a lone surrogate. It fails on the resolve-once version and passes with this fix.