Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ android/keystores/debug.keystore
# generated by bob
lib/

# Polyfills build output
packages/polyfills/dist/

# React Native Codegen
ios/generated
android/generated
Expand Down
35 changes: 35 additions & 0 deletions cpp/Polyfills.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Auto-generated file - DO NOT EDIT
// Generated by: packages/polyfills/scripts/generate-header.js

#pragma once

namespace webworker {

constexpr const char* kPolyfillScript = R"POLYFILL(
var __BUNDLE_START_TIME__=globalThis.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=false,process=globalThis.process||{},__METRO_GLOBAL_PREFIX__='';process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||"production";
!(function(e){"use strict";e.__r=i,e[`${__METRO_GLOBAL_PREFIX__}__d`]=function(e,o,n){if(r.has(o))return;var i={dependencyMap:n,factory:e,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}};r.set(o,i)},e.__c=n,e.__registerSegment=function(e,t,o){s[e]=t,o&&o.forEach(t=>{r.has(t)||v.has(t)||v.set(t,e)})};var r=n(),t={},o={}.hasOwnProperty;function n(){return r=new Map}function i(e,t){if(null===e)throw new Error("Cannot find module");var o=e,n=r.get(o);return n&&n.isInitialized?n.publicModule.exports:d(o,n)}function a(e){var o=e,n=r.get(o);if(n&&n.importedDefault!==t)return n.importedDefault;var a=i(o),l=a&&a.__esModule?a.default:a;return r.get(o).importedDefault=l}function l(e){var n=e,a=r.get(n);if(a&&a.importedAll!==t)return a.importedAll;var l,u=i(n);if(u&&u.__esModule)l=u;else{if(l={},u)for(var d in u)o.call(u,d)&&(l[d]=u[d]);l.default=u}return r.get(n).importedAll=l}i.importDefault=a,i.importAll=l,i.context=function(){throw new Error("The experimental Metro feature `require.context` is not enabled in your project.")},i.resolveWeak=function(){throw new Error("require.resolveWeak cannot be called dynamically.")};var u=!1;function d(r,t){if(!u&&e.ErrorUtils){var o;u=!0;try{o=h(r,t)}catch(r){e.ErrorUtils.reportFatalError(r)}return u=!1,o}return h(r,t)}var f=16,c=65535;function p(e){return{segmentId:e>>>f,localId:e&c}}i.unpackModuleId=p,i.packModuleId=function(e){return(e.segmentId<<f)+e.localId};var s=[],v=new Map;function h(t,o){if(!o&&s.length>0){var n=v.get(t)??0,u=s[n];null!=u&&(u(t),o=r.get(t),v.delete(t))}var d=e.nativeRequire;if(!o&&d){var f=p(t),c=f.segmentId;d(f.localId,c),o=r.get(t)}if(!o)throw Error('Requiring unknown module "'+t+'".');if(o.hasError)throw o.error;o.isInitialized=!0;var h=o,g=h.factory,m=h.dependencyMap;try{var _=o.publicModule;return _.id=t,g(e,i,a,l,_,_.exports,m),o.factory=void 0,o.dependencyMap=void 0,_.exports}catch(e){throw o.hasError=!0,o.error=e,o.isInitialized=!1,o.publicModule.exports=void 0,e}}})('undefined'!=typeof globalThis?globalThis:'undefined'!=typeof global?global:'undefined'!=typeof window?window:this);
__d(function(g,r,i,a,m,e,d){r(d[0])},0,[1]);
__d(function(_g,r,i,a,m,e,d){"use strict";var o=r(d[0]),n="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==_g?_g:void 0;n&&(void 0===n.AbortController&&(n.AbortController=o.AbortController),void 0===n.AbortSignal&&(n.AbortSignal=o.AbortSignal))},1,[2]);
__d(function(g,r,i,a,m,_e,d){'use strict';var t=r(d[0]),e=t(r(d[1])),o=t(r(d[2])),n=t(r(d[3])),l=t(r(d[4])),u=t(r(d[5]));function c(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(c=function(){return!!t})()}Object.defineProperty(_e,'__esModule',{value:!0});var f=r(d[6]),b=(function(t){function f(){var t,o,u;throw(0,e.default)(this,f),t=this,o=f,o=(0,l.default)(o),(0,n.default)(t,c()?Reflect.construct(o,u||[],(0,l.default)(t).constructor):o.apply(t,u)),new TypeError("AbortSignal cannot be constructed directly")}return(0,u.default)(f,t),(0,o.default)(f,[{key:"aborted",get:function(){var t=p.get(this);if("boolean"!=typeof t)throw new TypeError("Expected 'this' to be an 'AbortSignal' object, but got "+(null===this?"null":typeof this));return t}}])})(f.EventTarget);f.defineEventAttribute(b.prototype,"abort");var p=new WeakMap;Object.defineProperties(b.prototype,{aborted:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag&&Object.defineProperty(b.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortSignal"});var y=(function(){return(0,o.default)(function t(){var o;(0,e.default)(this,t),s.set(this,(o=Object.create(b.prototype),f.EventTarget.call(o),p.set(o,!1),o))},[{key:"signal",get:function(){return v(this)}},{key:"abort",value:function(){var t;t=v(this),!1===p.get(t)&&(p.set(t,!0),t.dispatchEvent({type:"abort"}))}}])})(),s=new WeakMap;function v(t){var e=s.get(t);if(null==e)throw new TypeError("Expected 'this' to be an 'AbortController' object, but got "+(null===t?"null":typeof t));return e}Object.defineProperties(y.prototype,{signal:{enumerable:!0},abort:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag&&Object.defineProperty(y.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortController"}),_e.AbortController=y,_e.AbortSignal=b,_e.default=y,m.exports=y,m.exports.AbortController=m.exports.default=y,m.exports.AbortSignal=b},2,[3,4,5,9,11,12,14]);
__d(function(g,r,i,a,m,_e,d){m.exports=function(e){return e&&e.__esModule?e:{default:e}},m.exports.__esModule=!0,m.exports.default=m.exports},3,[]);
__d(function(g,r,i,_a,m,e,d){m.exports=function(o,n){if(!(o instanceof n))throw new TypeError("Cannot call a class as a function")},m.exports.__esModule=!0,m.exports.default=m.exports},4,[]);
__d(function(g,_r,i,a,m,_e,d){var e=_r(d[0]);function r(r,t){for(var o=0;o<t.length;o++){var n=t[o];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(r,e(n.key),n)}}m.exports=function(e,t,o){return t&&r(e.prototype,t),o&&r(e,o),Object.defineProperty(e,"prototype",{writable:!1}),e},m.exports.__esModule=!0,m.exports.default=m.exports},5,[6]);
__d(function(g,r,_i,a,m,e,d){var t=r(d[0]).default,o=r(d[1]);m.exports=function(s){var n=o(s,"string");return"symbol"==t(n)?n:n+""},m.exports.__esModule=!0,m.exports.default=m.exports},6,[7,8]);
__d(function(g,r,i,a,m,e,d){function o(t){return m.exports=o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},m.exports.__esModule=!0,m.exports.default=m.exports,o(t)}m.exports=o,m.exports.__esModule=!0,m.exports.default=m.exports},7,[]);
__d(function(g,_r,_i,a,m,_e,d){var r=_r(d[0]).default;m.exports=function(t,e){if("object"!=r(t)||!t)return t;var i=t[Symbol.toPrimitive];if(void 0!==i){var o=i.call(t,e||"default");if("object"!=r(o))return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)},m.exports.__esModule=!0,m.exports.default=m.exports},8,[7]);
__d(function(g,r,i,a,m,_e,d){var e=r(d[0]).default,o=r(d[1]);m.exports=function(t,n){if(n&&("object"==e(n)||"function"==typeof n))return n;if(void 0!==n)throw new TypeError("Derived constructors may only return object or undefined");return o(t)},m.exports.__esModule=!0,m.exports.default=m.exports},9,[7,10]);
__d(function(g,r,i,a,m,_e,d){m.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e},m.exports.__esModule=!0,m.exports.default=m.exports},10,[]);
__d(function(g,r,i,a,m,e,d){function t(o){return m.exports=t=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},m.exports.__esModule=!0,m.exports.default=m.exports,t(o)}m.exports=t,m.exports.__esModule=!0,m.exports.default=m.exports},11,[]);
__d(function(g,r,i,a,m,_e,d){var e=r(d[0]);m.exports=function(t,o){if("function"!=typeof o&&null!==o)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(o&&o.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),o&&e(t,o)},m.exports.__esModule=!0,m.exports.default=m.exports},12,[13]);
__d(function(g,r,i,a,m,_e,d){function t(e,o){return m.exports=t=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},m.exports.__esModule=!0,m.exports.default=m.exports,t(e,o)}m.exports=t,m.exports.__esModule=!0,m.exports.default=m.exports},13,[]);
__d(function(g,r,_i,a,m,e,d){
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
'use strict';Object.defineProperty(e,'__esModule',{value:!0});var t=new WeakMap,n=new WeakMap;function o(n){var o=t.get(n);return console.assert(null!=o,"'this' is expected an Event object, but got",n),o}function i(t){null==t.passiveListener?t.event.cancelable&&(t.canceled=!0,"function"==typeof t.event.preventDefault&&t.event.preventDefault()):"undefined"!=typeof console&&"function"==typeof console.error&&console.error("Unable to preventDefault inside passive event listener invocation.",t.passiveListener)}function l(n,o){t.set(this,{eventTarget:n,event:o,eventPhase:2,currentTarget:n,canceled:!1,stopped:!1,immediateStopped:!1,passiveListener:null,timeStamp:o.timeStamp||Date.now()}),Object.defineProperty(this,"isTrusted",{value:!1,enumerable:!0});for(var i=Object.keys(o),l=0;l<i.length;++l){var s=i[l];s in this||Object.defineProperty(this,s,u(s))}}function u(t){return{get(){return o(this).event[t]},set(n){o(this).event[t]=n},configurable:!0,enumerable:!0}}function s(t){return{value(){var n=o(this).event;return n[t].apply(n,arguments)},configurable:!0,enumerable:!0}}function p(t,n){var o=Object.keys(n);if(0===o.length)return t;function i(n,o){t.call(this,n,o)}i.prototype=Object.create(t.prototype,{constructor:{value:i,configurable:!0,writable:!0}});for(var l=0;l<o.length;++l){var p=o[l];if(!(p in t.prototype)){var c="function"==typeof Object.getOwnPropertyDescriptor(n,p).value;Object.defineProperty(i.prototype,p,c?s(p):u(p))}}return i}function c(t){if(null==t||t===Object.prototype)return l;var o=n.get(t);return null==o&&(o=p(c(Object.getPrototypeOf(t)),t),n.set(t,o)),o}function f(t,n){return new(c(Object.getPrototypeOf(n)))(t,n)}function v(t){return o(t).immediateStopped}function y(t,n){o(t).eventPhase=n}function b(t,n){o(t).currentTarget=n}function h(t,n){o(t).passiveListener=n}l.prototype={get type(){return o(this).event.type},get target(){return o(this).eventTarget},get currentTarget(){return o(this).currentTarget},composedPath(){var t=o(this).currentTarget;return null==t?[]:[t]},get NONE(){return 0},get CAPTURING_PHASE(){return 1},get AT_TARGET(){return 2},get BUBBLING_PHASE(){return 3},get eventPhase(){return o(this).eventPhase},stopPropagation(){var t=o(this);t.stopped=!0,"function"==typeof t.event.stopPropagation&&t.event.stopPropagation()},stopImmediatePropagation(){var t=o(this);t.stopped=!0,t.immediateStopped=!0,"function"==typeof t.event.stopImmediatePropagation&&t.event.stopImmediatePropagation()},get bubbles(){return Boolean(o(this).event.bubbles)},get cancelable(){return Boolean(o(this).event.cancelable)},preventDefault(){i(o(this))},get defaultPrevented(){return o(this).canceled},get composed(){return Boolean(o(this).event.composed)},get timeStamp(){return o(this).timeStamp},get srcElement(){return o(this).eventTarget},get cancelBubble(){return o(this).stopped},set cancelBubble(t){if(t){var n=o(this);n.stopped=!0,"boolean"==typeof n.event.cancelBubble&&(n.event.cancelBubble=!0)}},get returnValue(){return!o(this).canceled},set returnValue(t){t||i(o(this))},initEvent(){}},Object.defineProperty(l.prototype,"constructor",{value:l,configurable:!0,writable:!0}),"undefined"!=typeof window&&void 0!==window.Event&&(Object.setPrototypeOf(l.prototype,window.Event.prototype),n.set(window.Event.prototype,l));var w=new WeakMap;function T(t){return null!==t&&"object"==typeof t}function P(t){var n=w.get(t);if(null==n)throw new TypeError("'this' is expected an EventTarget object, but got another value.");return n}function x(t){return{get(){for(var n=P(this).get(t);null!=n;){if(3===n.listenerType)return n.listener;n=n.next}return null},set(n){"function"==typeof n||T(n)||(n=null);for(var o=P(this),i=null,l=o.get(t);null!=l;)3===l.listenerType?null!==i?i.next=l.next:null!==l.next?o.set(t,l.next):o.delete(t):i=l,l=l.next;if(null!==n){var u={listener:n,listenerType:3,passive:!1,once:!1,next:null};null===i?o.set(t,u):i.next=u}},configurable:!0,enumerable:!0}}function E(t,n){Object.defineProperty(t,`on${n}`,x(n))}function O(t){function n(){j.call(this)}n.prototype=Object.create(j.prototype,{constructor:{value:n,configurable:!0,writable:!0}});for(var o=0;o<t.length;++o)E(n.prototype,t[o]);return n}function j(){if(!(this instanceof j)){if(1===arguments.length&&Array.isArray(arguments[0]))return O(arguments[0]);if(arguments.length>0){for(var t=new Array(arguments.length),n=0;n<arguments.length;++n)t[n]=arguments[n];return O(t)}throw new TypeError("Cannot call a class as a function")}w.set(this,new Map)}j.prototype={addEventListener(t,n,o){if(null!=n){if("function"!=typeof n&&!T(n))throw new TypeError("'listener' should be a function or an object.");var i=P(this),l=T(o),u=(l?Boolean(o.capture):Boolean(o))?1:2,s={listener:n,listenerType:u,passive:l&&Boolean(o.passive),once:l&&Boolean(o.once),next:null},p=i.get(t);if(void 0!==p){for(var c=null;null!=p;){if(p.listener===n&&p.listenerType===u)return;c=p,p=p.next}c.next=s}else i.set(t,s)}},removeEventListener(t,n,o){if(null!=n)for(var i=P(this),l=(T(o)?Boolean(o.capture):Boolean(o))?1:2,u=null,s=i.get(t);null!=s;){if(s.listener===n&&s.listenerType===l)return void(null!==u?u.next=s.next:null!==s.next?i.set(t,s.next):i.delete(t));u=s,s=s.next}},dispatchEvent(t){if(null==t||"string"!=typeof t.type)throw new TypeError('"event.type" should be a string.');var n=P(this),o=t.type,i=n.get(o);if(null==i)return!0;for(var l=f(this,t),u=null;null!=i;){if(i.once?null!==u?u.next=i.next:null!==i.next?n.set(o,i.next):n.delete(o):u=i,h(l,i.passive?i.listener:null),"function"==typeof i.listener)try{i.listener.call(this,l)}catch(t){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(t)}else 3!==i.listenerType&&"function"==typeof i.listener.handleEvent&&i.listener.handleEvent(l);if(v(l))break;i=i.next}return h(l,null),y(l,0),b(l,null),!l.defaultPrevented}},Object.defineProperty(j.prototype,"constructor",{value:j,configurable:!0,writable:!0}),"undefined"!=typeof window&&void 0!==window.EventTarget&&Object.setPrototypeOf(j.prototype,window.EventTarget.prototype),e.defineEventAttribute=E,e.EventTarget=j,e.default=j,m.exports=j,m.exports.EventTarget=m.exports.default=j,m.exports.defineEventAttribute=E},14,[]);
__r(0);
)POLYFILL";

} // namespace webworker
7 changes: 7 additions & 0 deletions cpp/WebWorkerCore.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "WebWorkerCore.h"
#include "Polyfills.h"
#include "networking/ResponseHostObject.h"
#include <iostream>
#include <sstream>
Expand Down Expand Up @@ -304,6 +305,12 @@ void WorkerRuntime::setupGlobalScope() {
try {
Runtime& runtime = *hermesRuntime_;

// Execute polyfills first (TextEncoder, URL, AbortController, etc.)
runtime.evaluateJavaScript(
std::make_shared<StringBuffer>(kPolyfillScript),
"polyfills.js"
);

std::string initScript = R"(
var self = this;
var global = this;
Expand Down
42 changes: 42 additions & 0 deletions docs/POLYFILLS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Polyfills in web workers

The `react-native-webworker` library includes built-in polyfills to provide essential web APIs that are missing from Hermes. Every worker automatically gets these polyfills loaded before your code runs.

## What it does

Every worker comes with polyfills for APIs that Hermes doesn't implement but that web developers expect. Behind the scenes, it uses proven polyfill libraries to provide:

- **AbortController** - Cancel asynchronous operations
- *(Room for future polyfills)*

This ensures your worker code can use modern JavaScript APIs without worrying about Hermes compatibility.

## Adding new polyfills

You can easily extend the polyfill bundle by adding new APIs:

### Adding a polyfill

1. Install the polyfill package:

```bash
cd packages/polyfills
yarn add text-encoding-polyfill
```

2. Import it in the source file:

```javascript
// packages/polyfills/src/index.js
import 'abort-controller/polyfill';
import 'text-encoding-polyfill'; // New polyfill
```

3. Rebuild the polyfills:

```bash
cd packages/polyfills
yarn build
```

The new polyfill will be automatically available in all workers.
52 changes: 52 additions & 0 deletions example/src/__tests__/polyfills.harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { afterEach, describe, it, expect } from 'react-native-harness';
import { Worker } from 'react-native-webworker';

// Helper to prevent tests from hanging indefinitely
function withTimeout<T>(
promise: Promise<T>,
ms: number = 2000,
msg: string = 'Operation timed out'
): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) => setTimeout(() => reject(new Error(msg)), ms)),
]);
}

describe('Polyfills', () => {
let worker: Worker;

afterEach(() => {
worker.terminate();
});

it('should add AbortController to the global object', async () => {
worker = new Worker({
script: `
self.onmessage = function(event) {
self.postMessage(typeof AbortController);
};
`,
name: 'test-worker',
});

const responsePromise = new Promise<string>((resolve, reject) => {
worker.onmessage = (event) => {
resolve(event.data as string);
};
worker.onerror = (err) => {
reject(err);
};
});

await worker.postMessage('start');

const result = await withTimeout(
responsePromise,
2000,
'Worker did not respond with AbortController type'
);

expect(result).toBe('function');
});
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
"react-native": "*"
},
"workspaces": [
"example"
"example",
"packages/*"
],
"packageManager": "yarn@4.11.0",
"react-native-builder-bob": {
Expand Down
11 changes: 11 additions & 0 deletions packages/polyfills/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
presets: [
[
'@react-native/babel-preset',
{
// Use hermes-stable profile to ensure all incompatible constructs are transformed
unstable_transformProfile: 'hermes-stable',
},
],
],
};
13 changes: 13 additions & 0 deletions packages/polyfills/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const path = require('path');

module.exports = {
// Project configuration
projectRoot: __dirname,
watchFolders: [__dirname],

// Resolver configuration
resolver: {
sourceExts: ['js', 'mjs', 'cjs', 'json'],
nodeModulesPaths: [path.join(__dirname, 'node_modules')],
},
};
31 changes: 31 additions & 0 deletions packages/polyfills/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@react-native-webworker/polyfills",
"description": "Polyfills for React Native WebWorker",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "node scripts/build.js && node scripts/generate-header.js"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@react-native/babel-preset": "^0.83.0",
"metro": "^0.83.0",
"metro-babel-transformer": "^0.83.0",
"metro-minify-terser": "^0.83.0",
"metro-resolver": "^0.83.0",
"abort-controller": "3.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/V3RON/react-native-webworker.git"
},
"author": "Szymon Chmal <szymon@chmal.it> (https://github.com/V3RON)",
"license": "MIT",
"bugs": {
"url": "https://github.com/V3RON/react-native-webworker/issues"
},
"homepage": "https://github.com/V3RON/react-native-webworker#readme",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
}
31 changes: 31 additions & 0 deletions packages/polyfills/scripts/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const Metro = require('metro');
const path = require('path');
const fs = require('fs');

async function build() {
const projectRoot = path.join(__dirname, '..');
const distDir = path.join(projectRoot, 'dist');

if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}

const config = await Metro.loadConfig({
config: path.join(projectRoot, 'metro.config.js'),
});

await Metro.runBuild(config, {
entry: path.join(projectRoot, 'src/index.js'),
out: path.join(distDir, 'polyfills.bundle.js'),
platform: 'ios',
minify: true,
dev: false,
});

console.log('Bundle created: dist/polyfills.bundle.js');
}

build().catch((error) => {
console.error('Build failed:', error);
process.exit(1);
});
Loading
Loading