├── .gitignore ├── LICENSE ├── README.md ├── android ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── reactnativewasm │ ├── WasmModule.kt │ └── WasmPackage.kt ├── ios ├── Wasm-Bridging-Header.h ├── Wasm.m ├── Wasm.swift └── Wasm.xcodeproj │ └── project.pbxproj ├── package-lock.json ├── package.json ├── react-native-wasm.podspec └── src ├── WebAssembly.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 inokawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-wasm 2 | 3 | ![npm](https://img.shields.io/npm/v/react-native-wasm) 4 | 5 | A polyfill to use [WebAssembly](https://webassembly.org/) in [React Native](https://github.com/facebook/react-native). 6 | 7 | This package instantiates WebAssembly in a native WebView environment and makes the communication with React Native to simulate original behavior. 8 | Native module of React Native has limited argument types ([iOS](https://reactnative.dev/docs/native-modules-ios#argument-types)/[Android](https://reactnative.dev/docs/native-modules-android#argument-types)) so we need to serialize/deserialize the exchanged data, which may have some overhead but will work as in a web app. 9 | 10 | > [!WARNING] 11 | > 12 | > I recommend using [react-native-react-bridge](https://github.com/inokawa/react-native-react-bridge) rather than this to run WebAssembly. Although its aim is a bit different, it's built on WebView like this and it's working much more stably. 13 | > 14 | > And check the current progress of wasm support in React Native: 15 | > 16 | > - https://github.com/react-native-community/jsc-android-buildscripts/issues/113 17 | > - https://github.com/facebook/hermes/issues/429 18 | > 19 | > And also check https://github.com/cawfree/react-native-webassembly 20 | 21 | ## Install 22 | 23 | ```sh 24 | npm install react-native-wasm 25 | 26 | # <=0.59 you have to link manually. 27 | react-native link react-native-wasm 28 | 29 | # In iOS 30 | cd ios && pod install 31 | ``` 32 | 33 | And currently you have to create bridging header manually in iOS. 34 | 35 | https://reactnative.dev/docs/native-modules-ios#exporting-swift 36 | 37 | > Important when making third party modules: Static libraries with Swift are only supported in Xcode 9 and later. In order for the Xcode project to build when you use Swift in the iOS static library you include in the module, your main app project must contain Swift code and a bridging header itself. If your app project does not contain any Swift code, a workaround can be a single empty .swift file and an empty bridging header. 38 | 39 | ### Requirements 40 | 41 | - react-native 0.59+ (because of [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) support in React Native) 42 | 43 | ## Usage 44 | 45 | ```javascript 46 | // index.js 47 | import { AppRegistry } from "react-native"; 48 | import "react-native-wasm"; 49 | ... 50 | 51 | AppRegistry.registerComponent(appName, () => App); 52 | 53 | // Foo.js 54 | const buffer = Uint8Array.from([ 55 | 0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00 56 | ,0x01,0x87,0x80,0x80,0x80,0x00,0x01,0x60 57 | ,0x02,0x7F,0x7F,0x01,0x7F,0x03,0x82,0x80 58 | ,0x80,0x80,0x00,0x01,0x00,0x07,0x87,0x80 59 | ,0x80,0x80,0x00,0x01,0x03,0x61,0x64,0x64 60 | ,0x00,0x00,0x0A,0x8D,0x80,0x80,0x80,0x00 61 | ,0x01,0x87,0x80,0x80,0x80,0x00,0x00,0x20 62 | ,0x00,0x20,0x01,0x6A,0x0B]); 63 | 64 | WebAssembly.instantiate(buffer).then((res) => { 65 | console.log(res.instance.exports.add(3, 5)); // 8 66 | }); 67 | ``` 68 | 69 | ## TODOs 70 | 71 | - [x] instantiate 72 | - [x] Support iOS 73 | - [x] Support Android 74 | - [ ] Support importObject 75 | - [ ] compile 76 | - [ ] validate 77 | - [ ] WebAssembly.Instance 78 | - [ ] WebAssembly.Module 79 | - [ ] WebAssembly.Memory 80 | - [ ] WebAssembly.Table 81 | - [ ] Support bundling .wasm file 82 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android_ 4 | Project android_ created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1690193890710 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments=--init-script /var/folders/qh/98hrq5p567v42ld8j_kzgpn80000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/qh/98hrq5p567v42ld8j_kzgpn80000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.4.2)) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault 3 | def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['Wasm_kotlinVersion'] 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.1' 12 | // noinspection DifferentKotlinGradleVersion 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | def getExtOrDefault(name) { 21 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['Wasm_' + name] 22 | } 23 | 24 | def getExtOrIntegerDefault(name) { 25 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['Wasm_' + name]).toInteger() 26 | } 27 | 28 | android { 29 | compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') 30 | buildToolsVersion getExtOrDefault('buildToolsVersion') 31 | defaultConfig { 32 | minSdkVersion 16 33 | targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') 34 | versionCode 1 35 | versionName "1.0" 36 | 37 | } 38 | 39 | buildTypes { 40 | release { 41 | minifyEnabled false 42 | } 43 | } 44 | lintOptions { 45 | disable 'GradleCompatible' 46 | } 47 | compileOptions { 48 | sourceCompatibility JavaVersion.VERSION_1_8 49 | targetCompatibility JavaVersion.VERSION_1_8 50 | } 51 | } 52 | 53 | repositories { 54 | mavenCentral() 55 | jcenter() 56 | google() 57 | 58 | def found = false 59 | def defaultDir = null 60 | def androidSourcesName = 'React Native sources' 61 | 62 | if (rootProject.ext.has('reactNativeAndroidRoot')) { 63 | defaultDir = rootProject.ext.get('reactNativeAndroidRoot') 64 | } else { 65 | defaultDir = new File( 66 | projectDir, 67 | '/../../../node_modules/react-native/android' 68 | ) 69 | } 70 | 71 | if (defaultDir.exists()) { 72 | maven { 73 | url defaultDir.toString() 74 | name androidSourcesName 75 | } 76 | 77 | logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}") 78 | found = true 79 | } else { 80 | def parentDir = rootProject.projectDir 81 | 82 | 1.upto(5, { 83 | if (found) return true 84 | parentDir = parentDir.parentFile 85 | 86 | def androidSourcesDir = new File( 87 | parentDir, 88 | 'node_modules/react-native' 89 | ) 90 | 91 | def androidPrebuiltBinaryDir = new File( 92 | parentDir, 93 | 'node_modules/react-native/android' 94 | ) 95 | 96 | if (androidPrebuiltBinaryDir.exists()) { 97 | maven { 98 | url androidPrebuiltBinaryDir.toString() 99 | name androidSourcesName 100 | } 101 | 102 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}") 103 | found = true 104 | } else if (androidSourcesDir.exists()) { 105 | maven { 106 | url androidSourcesDir.toString() 107 | name androidSourcesName 108 | } 109 | 110 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}") 111 | found = true 112 | } 113 | }) 114 | } 115 | 116 | if (!found) { 117 | throw new GradleException( 118 | "${project.name}: unable to locate React Native android sources. " + 119 | "Ensure you have you installed React Native as a dependency in your project and try again." 120 | ) 121 | } 122 | } 123 | 124 | def kotlin_version = getExtOrDefault('kotlinVersion') 125 | 126 | dependencies { 127 | // noinspection GradleDynamicVersion 128 | api 'com.facebook.react:react-native:+' 129 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 130 | } 131 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | Wasm_kotlinVersion=1.5.20 2 | Wasm_compileSdkVersion=28 3 | Wasm_buildToolsVersion=28.0.3 4 | Wasm_targetSdkVersion=28 5 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactnativewasm/WasmModule.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativewasm 2 | 3 | import android.os.Build 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.webkit.JavascriptInterface 7 | import android.webkit.ValueCallback 8 | import android.webkit.WebView 9 | import androidx.annotation.RequiresApi 10 | import com.facebook.react.bridge.* 11 | import java.util.concurrent.CountDownLatch 12 | import kotlin.collections.HashMap 13 | 14 | 15 | const val js: String = """ 16 | var wasm = {}; 17 | var promise = {}; 18 | function instantiate(id, bytes){ 19 | promise[id] = WebAssembly.instantiate(Uint8Array.from(bytes)) 20 | .then(function(res){ 21 | delete promise[id]; 22 | wasm[id] = res; 23 | android.resolve(id, JSON.stringify(Object.keys(res.instance.exports))); 24 | }).catch(function(e){ 25 | delete promise[id]; 26 | android.reject(id, e.toString()); 27 | }); 28 | return true; 29 | } 30 | """ 31 | 32 | class WasmModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { 33 | 34 | private val context: ReactContext = reactContext 35 | lateinit var webView: WebView; 36 | val asyncPool = HashMap() 37 | val syncPool = HashMap() 38 | val syncResults = HashMap() 39 | 40 | init { 41 | val self = this; 42 | Handler(Looper.getMainLooper()).post(object : Runnable { 43 | @RequiresApi(Build.VERSION_CODES.KITKAT) 44 | override fun run() { 45 | webView = WebView(context); 46 | webView.settings.javaScriptEnabled = true 47 | webView.addJavascriptInterface(JSHandler(self, asyncPool, syncPool, syncResults), "android") 48 | webView.evaluateJavascript("javascript:" + js, ValueCallback { reply -> // NOP 49 | }) 50 | } 51 | }); 52 | } 53 | 54 | override fun getName(): String { 55 | return "Wasm" 56 | } 57 | 58 | @ReactMethod 59 | fun instantiate(id: String, bytes: String, promise: Promise) { 60 | asyncPool[id] = promise 61 | 62 | Handler(Looper.getMainLooper()).post(object : Runnable { 63 | @RequiresApi(Build.VERSION_CODES.KITKAT) 64 | override fun run() { 65 | webView.evaluateJavascript(""" 66 | javascript:instantiate("$id", [$bytes]); 67 | """, ValueCallback { value -> 68 | { 69 | if (value == null) { 70 | asyncPool.remove(id) 71 | promise.reject("failed to instantiate") 72 | } 73 | } 74 | }) 75 | } 76 | }); 77 | } 78 | 79 | @ReactMethod(isBlockingSynchronousMethod = true) 80 | fun callSync(id: String, name: String, args: String): Double { 81 | val latch = CountDownLatch(1) 82 | syncPool[id] = latch 83 | 84 | Handler(context.getMainLooper()).post(object : Runnable { 85 | @RequiresApi(Build.VERSION_CODES.KITKAT) 86 | override fun run() { 87 | webView.evaluateJavascript(""" 88 | javascript:android.returnSync("$id", wasm["$id"].instance.exports.$name(...$args)); 89 | """, ValueCallback { value -> 90 | { 91 | // NOP 92 | } 93 | }) 94 | } 95 | }); 96 | 97 | latch.await() 98 | val result = syncResults[id] 99 | syncResults.remove(id) 100 | return result ?: 0.0 101 | } 102 | 103 | protected class JSHandler internal constructor(ctx: WasmModule, asyncPool: HashMap, syncPool: HashMap, syncResults: HashMap) { 104 | val ctx: WasmModule = ctx 105 | val asyncPool: HashMap = asyncPool 106 | val syncPool: HashMap = syncPool 107 | val syncResults: HashMap = syncResults 108 | 109 | @JavascriptInterface 110 | fun resolve(id: String, data: String) { 111 | val p = asyncPool[id] 112 | if (p != null) { 113 | asyncPool.remove(id) 114 | p.resolve(data) 115 | } 116 | } 117 | 118 | @JavascriptInterface 119 | fun reject(id: String, data: String) { 120 | val p = asyncPool[id] 121 | if (p != null) { 122 | asyncPool.remove(id) 123 | p.reject(data) 124 | } 125 | } 126 | 127 | @JavascriptInterface 128 | fun returnSync(id: String, data: Double) { 129 | val l = syncPool[id] 130 | if (l != null) { 131 | syncPool.remove(id) 132 | syncResults[id] = data 133 | l.countDown() 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactnativewasm/WasmPackage.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativewasm 2 | 3 | import java.util.Arrays 4 | import java.util.Collections 5 | 6 | import com.facebook.react.ReactPackage 7 | import com.facebook.react.bridge.NativeModule 8 | import com.facebook.react.bridge.ReactApplicationContext 9 | import com.facebook.react.uimanager.ViewManager 10 | import com.facebook.react.bridge.JavaScriptModule 11 | 12 | class WasmPackage : ReactPackage { 13 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 14 | return Arrays.asList(WasmModule(reactContext)) 15 | } 16 | 17 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 18 | return emptyList>() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ios/Wasm-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | -------------------------------------------------------------------------------- /ios/Wasm.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(Wasm, NSObject) 4 | 5 | RCT_EXTERN_METHOD(instantiate:(NSString *)modId bytesStr:(NSString *)bytes resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 6 | 7 | RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(callSync:(NSString *)modId funcName:(NSString *)name arguments:(NSString *)args) 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /ios/Wasm.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import WebKit 3 | 4 | let js: String = """ 5 | var wasm = {}; 6 | var promise = {}; 7 | function instantiate(id, bytes){ 8 | promise[id] = WebAssembly.instantiate(Uint8Array.from(bytes)) 9 | .then(function(res){ 10 | delete promise[id]; 11 | wasm[id] = res; 12 | window.webkit.messageHandlers.resolve.postMessage(JSON.stringify({id:id,data:JSON.stringify(Object.keys(res.instance.exports))})); 13 | }).catch(function(e){ 14 | delete promise[id]; 15 | window.webkit.messageHandlers.reject.postMessage(JSON.stringify({id:id,data:e.toString()})); 16 | }); 17 | return true; 18 | } 19 | """ 20 | 21 | struct Promise { 22 | let resolve: RCTPromiseResolveBlock 23 | let reject: RCTPromiseRejectBlock 24 | } 25 | 26 | struct JsResult: Codable { 27 | let id: String 28 | let data: String 29 | } 30 | 31 | @objc(Wasm) 32 | class Wasm: NSObject, WKScriptMessageHandler { 33 | 34 | var webView: WKWebView! 35 | var asyncPool: Dictionary = [:] 36 | 37 | static func requiresMainQueueSetup() -> Bool { 38 | return true 39 | } 40 | 41 | override init() { 42 | super.init() 43 | let webCfg: WKWebViewConfiguration = WKWebViewConfiguration() 44 | 45 | let userController: WKUserContentController = WKUserContentController() 46 | userController.add(self, name: "resolve") 47 | userController.add(self, name: "reject") 48 | webCfg.userContentController = userController 49 | 50 | webView = WKWebView(frame: .zero, configuration: webCfg) 51 | 52 | DispatchQueue.main.async { 53 | self.webView.evaluateJavaScript(js) { (value, error) in 54 | // NOP 55 | } 56 | } 57 | } 58 | 59 | @objc 60 | func instantiate(_ modId: NSString, bytesStr bytes: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 61 | asyncPool.updateValue(Promise(resolve: resolve, reject: reject), forKey: modId as String) 62 | 63 | DispatchQueue.main.async { 64 | self.webView.evaluateJavaScript(""" 65 | instantiate("\(modId)", [\(bytes)]); 66 | """ 67 | ) { (value, error) in 68 | if error != nil { 69 | self.asyncPool.removeValue(forKey: modId as String) 70 | reject("error", "\(error)", nil) 71 | } 72 | } 73 | } 74 | } 75 | 76 | @objc @discardableResult 77 | func callSync(_ modId: NSString, funcName name: NSString, arguments args: NSString) -> NSNumber { 78 | var result: NSNumber = 0 79 | let semaphore = DispatchSemaphore(value: 0) 80 | DispatchQueue.main.async { 81 | self.webView.evaluateJavaScript(""" 82 | wasm["\(modId)"].instance.exports.\(name)(...\(args)); 83 | """ 84 | ) { (value, error) in 85 | // TODO handle error 86 | if value == nil { 87 | result = 0 88 | } else { 89 | result = value as! NSNumber 90 | } 91 | semaphore.signal() 92 | } 93 | } 94 | 95 | semaphore.wait() 96 | return result 97 | } 98 | 99 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 100 | if message.name == "resolve" { 101 | let json = try! JSONDecoder().decode(JsResult.self, from: (message.body as! String).data(using: .utf8)!) 102 | guard let promise = asyncPool[json.id] else { 103 | return 104 | } 105 | asyncPool.removeValue(forKey: json.id) 106 | promise.resolve(json.data) 107 | } else if message.name == "reject" { 108 | let json = try! JSONDecoder().decode(JsResult.self, from: (message.body as! String).data(using: .utf8)!) 109 | guard let promise = asyncPool[json.id] else { 110 | return 111 | } 112 | asyncPool.removeValue(forKey: json.id) 113 | promise.reject("error", json.data, nil) 114 | } 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /ios/Wasm.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 11 | 12 | F4FF95D7245B92E800C19C63 /* Wasm.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* Wasm.swift */; }; 13 | 14 | 5E555C0D2413F4C50049A1A2 /* Wasm.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* Wasm.mm */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = "include/$(PRODUCT_NAME)"; 22 | dstSubfolderSpec = 16; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 134814201AA4EA6300B7C361 /* libWasm.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libWasm.a; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 32 | 33 | B3E7B5891CC2AC0600A0062D /* Wasm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Wasm.m; sourceTree = ""; }; 34 | F4FF95D5245B92E700C19C63 /* Wasm-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Wasm-Bridging-Header.h"; sourceTree = ""; }; 35 | F4FF95D6245B92E800C19C63 /* Wasm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wasm.swift; sourceTree = ""; }; 36 | 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 134814211AA4EA7D00B7C361 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 134814201AA4EA6300B7C361 /* libWasm.a */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 58B511D21A9E6C8500147676 = { 59 | isa = PBXGroup; 60 | children = ( 61 | 62 | 63 | F4FF95D6245B92E800C19C63 /* Wasm.swift */, 64 | B3E7B5891CC2AC0600A0062D /* Wasm.m */, 65 | F4FF95D5245B92E700C19C63 /* Wasm-Bridging-Header.h */, 66 | 67 | 134814211AA4EA7D00B7C361 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXNativeTarget section */ 74 | 58B511DA1A9E6C8500147676 /* Wasm */ = { 75 | isa = PBXNativeTarget; 76 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Wasm" */; 77 | buildPhases = ( 78 | 58B511D71A9E6C8500147676 /* Sources */, 79 | 58B511D81A9E6C8500147676 /* Frameworks */, 80 | 58B511D91A9E6C8500147676 /* CopyFiles */, 81 | ); 82 | buildRules = ( 83 | ); 84 | dependencies = ( 85 | ); 86 | name = Wasm; 87 | productName = RCTDataManager; 88 | productReference = 134814201AA4EA6300B7C361 /* libWasm.a */; 89 | productType = "com.apple.product-type.library.static"; 90 | }; 91 | /* End PBXNativeTarget section */ 92 | 93 | /* Begin PBXProject section */ 94 | 58B511D31A9E6C8500147676 /* Project object */ = { 95 | isa = PBXProject; 96 | attributes = { 97 | LastUpgradeCheck = 0920; 98 | ORGANIZATIONNAME = Facebook; 99 | TargetAttributes = { 100 | 58B511DA1A9E6C8500147676 = { 101 | CreatedOnToolsVersion = 6.1.1; 102 | }; 103 | }; 104 | }; 105 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Wasm" */; 106 | compatibilityVersion = "Xcode 3.2"; 107 | developmentRegion = English; 108 | hasScannedForEncodings = 0; 109 | knownRegions = ( 110 | English, 111 | en, 112 | ); 113 | mainGroup = 58B511D21A9E6C8500147676; 114 | productRefGroup = 58B511D21A9E6C8500147676; 115 | projectDirPath = ""; 116 | projectRoot = ""; 117 | targets = ( 118 | 58B511DA1A9E6C8500147676 /* Wasm */, 119 | ); 120 | }; 121 | /* End PBXProject section */ 122 | 123 | /* Begin PBXSourcesBuildPhase section */ 124 | 58B511D71A9E6C8500147676 /* Sources */ = { 125 | isa = PBXSourcesBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | 129 | 130 | F4FF95D7245B92E800C19C63 /* Wasm.swift in Sources */, 131 | B3E7B58A1CC2AC0600A0062D /* Wasm.m in Sources */, 132 | 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXSourcesBuildPhase section */ 137 | 138 | /* Begin XCBuildConfiguration section */ 139 | 58B511ED1A9E6C8500147676 /* Debug */ = { 140 | isa = XCBuildConfiguration; 141 | buildSettings = { 142 | ALWAYS_SEARCH_USER_PATHS = NO; 143 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 144 | CLANG_CXX_LIBRARY = "libc++"; 145 | CLANG_ENABLE_MODULES = YES; 146 | CLANG_ENABLE_OBJC_ARC = YES; 147 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 148 | CLANG_WARN_BOOL_CONVERSION = YES; 149 | CLANG_WARN_COMMA = YES; 150 | CLANG_WARN_CONSTANT_CONVERSION = YES; 151 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 152 | CLANG_WARN_EMPTY_BODY = YES; 153 | CLANG_WARN_ENUM_CONVERSION = YES; 154 | CLANG_WARN_INFINITE_RECURSION = YES; 155 | CLANG_WARN_INT_CONVERSION = YES; 156 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 157 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 158 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 159 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 160 | CLANG_WARN_STRICT_PROTOTYPES = YES; 161 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 162 | CLANG_WARN_UNREACHABLE_CODE = YES; 163 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 164 | COPY_PHASE_STRIP = NO; 165 | ENABLE_STRICT_OBJC_MSGSEND = YES; 166 | ENABLE_TESTABILITY = YES; 167 | GCC_C_LANGUAGE_STANDARD = gnu99; 168 | GCC_DYNAMIC_NO_PIC = NO; 169 | GCC_NO_COMMON_BLOCKS = YES; 170 | GCC_OPTIMIZATION_LEVEL = 0; 171 | GCC_PREPROCESSOR_DEFINITIONS = ( 172 | "DEBUG=1", 173 | "$(inherited)", 174 | ); 175 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 176 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 177 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 178 | GCC_WARN_UNDECLARED_SELECTOR = YES; 179 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 180 | GCC_WARN_UNUSED_FUNCTION = YES; 181 | GCC_WARN_UNUSED_VARIABLE = YES; 182 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 183 | MTL_ENABLE_DEBUG_INFO = YES; 184 | ONLY_ACTIVE_ARCH = YES; 185 | SDKROOT = iphoneos; 186 | }; 187 | name = Debug; 188 | }; 189 | 58B511EE1A9E6C8500147676 /* Release */ = { 190 | isa = XCBuildConfiguration; 191 | buildSettings = { 192 | ALWAYS_SEARCH_USER_PATHS = NO; 193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 194 | CLANG_CXX_LIBRARY = "libc++"; 195 | CLANG_ENABLE_MODULES = YES; 196 | CLANG_ENABLE_OBJC_ARC = YES; 197 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 198 | CLANG_WARN_BOOL_CONVERSION = YES; 199 | CLANG_WARN_COMMA = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INFINITE_RECURSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 209 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 210 | CLANG_WARN_STRICT_PROTOTYPES = YES; 211 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 212 | CLANG_WARN_UNREACHABLE_CODE = YES; 213 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 214 | COPY_PHASE_STRIP = YES; 215 | ENABLE_NS_ASSERTIONS = NO; 216 | ENABLE_STRICT_OBJC_MSGSEND = YES; 217 | GCC_C_LANGUAGE_STANDARD = gnu99; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 220 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 221 | GCC_WARN_UNDECLARED_SELECTOR = YES; 222 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 223 | GCC_WARN_UNUSED_FUNCTION = YES; 224 | GCC_WARN_UNUSED_VARIABLE = YES; 225 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 226 | MTL_ENABLE_DEBUG_INFO = NO; 227 | SDKROOT = iphoneos; 228 | VALIDATE_PRODUCT = YES; 229 | }; 230 | name = Release; 231 | }; 232 | 58B511F01A9E6C8500147676 /* Debug */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | HEADER_SEARCH_PATHS = ( 236 | "$(inherited)", 237 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 238 | "$(SRCROOT)/../../../React/**", 239 | "$(SRCROOT)/../../react-native/React/**", 240 | ); 241 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 242 | OTHER_LDFLAGS = "-ObjC"; 243 | PRODUCT_NAME = Wasm; 244 | SKIP_INSTALL = YES; 245 | 246 | SWIFT_OBJC_BRIDGING_HEADER = "Wasm-Bridging-Header.h"; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | SWIFT_VERSION = 5.0; 249 | 250 | }; 251 | name = Debug; 252 | }; 253 | 58B511F11A9E6C8500147676 /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | HEADER_SEARCH_PATHS = ( 257 | "$(inherited)", 258 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 259 | "$(SRCROOT)/../../../React/**", 260 | "$(SRCROOT)/../../react-native/React/**", 261 | ); 262 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 263 | OTHER_LDFLAGS = "-ObjC"; 264 | PRODUCT_NAME = Wasm; 265 | SKIP_INSTALL = YES; 266 | 267 | SWIFT_OBJC_BRIDGING_HEADER = "Wasm-Bridging-Header.h"; 268 | SWIFT_VERSION = 5.0; 269 | 270 | }; 271 | name = Release; 272 | }; 273 | /* End XCBuildConfiguration section */ 274 | 275 | /* Begin XCConfigurationList section */ 276 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Wasm" */ = { 277 | isa = XCConfigurationList; 278 | buildConfigurations = ( 279 | 58B511ED1A9E6C8500147676 /* Debug */, 280 | 58B511EE1A9E6C8500147676 /* Release */, 281 | ); 282 | defaultConfigurationIsVisible = 0; 283 | defaultConfigurationName = Release; 284 | }; 285 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Wasm" */ = { 286 | isa = XCConfigurationList; 287 | buildConfigurations = ( 288 | 58B511F01A9E6C8500147676 /* Debug */, 289 | 58B511F11A9E6C8500147676 /* Release */, 290 | ); 291 | defaultConfigurationIsVisible = 0; 292 | defaultConfigurationName = Release; 293 | }; 294 | /* End XCConfigurationList section */ 295 | }; 296 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 297 | } 298 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-wasm", 3 | "title": "React Native Wasm", 4 | "version": "0.1.3", 5 | "description": "A polyfill to use WebAssembly in React Native.", 6 | "main": "src/index.js", 7 | "files": [ 8 | "README.md", 9 | "android", 10 | "ios", 11 | "src", 12 | "react-native-wasm.podspec" 13 | ], 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "prepublishOnly": "git clean -d -x -f ./android && git clean -d -x -f ./ios" 17 | }, 18 | "devDependencies": { 19 | "react": "^16.9.0", 20 | "react-native": "0.61.5" 21 | }, 22 | "peerDependencies": { 23 | "react-native": ">=0.59.0" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/inokawa/react-native-wasm.git" 28 | }, 29 | "keywords": [ 30 | "react", 31 | "react-native", 32 | "webassembly", 33 | "wasm", 34 | "polyfill", 35 | "ios", 36 | "android" 37 | ], 38 | "author": "inokawa (https://github.com/inokawa/)", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/inokawa/react-native-wasm/issues" 42 | }, 43 | "homepage": "https://github.com/inokawa/react-native-wasm#readme" 44 | } 45 | -------------------------------------------------------------------------------- /react-native-wasm.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "react-native-wasm" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.homepage = package["homepage"] 10 | s.license = package["license"] 11 | s.authors = package["author"] 12 | 13 | s.platforms = { :ios => "9.0" } 14 | s.source = { :git => "https://github.com/inokawa/react-native-wasm.git", :tag => "#{s.version}" } 15 | 16 | 17 | s.source_files = "ios/**/*.{h,m,mm,swift}" 18 | 19 | 20 | s.dependency "React" 21 | end 22 | -------------------------------------------------------------------------------- /src/WebAssembly.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from "react-native"; 2 | 3 | const { Wasm } = NativeModules; 4 | 5 | class WasmInstance { 6 | _exports; 7 | constructor(id, keys) { 8 | this._exports = JSON.parse(keys).reduce((acc, k) => { 9 | acc[k] = (...args) => Wasm.callSync(id, k, JSON.stringify(args)); 10 | return acc; 11 | }, {}); 12 | } 13 | get exports() { 14 | return this._exports; 15 | } 16 | } 17 | 18 | const generateId = () => { 19 | return ( 20 | new Date().getTime().toString(16) + 21 | Math.floor(1000 * Math.random()).toString(16) 22 | ); 23 | }; 24 | 25 | const instantiate = (buffer) => 26 | new Promise((resolve, reject) => { 27 | const id = generateId(); 28 | 29 | Wasm.instantiate(id, buffer.toString()) 30 | .then((keys) => { 31 | if (!keys) { 32 | reject("failed to get exports"); 33 | } else { 34 | resolve({ 35 | instance: new WasmInstance(id, keys), 36 | module: { 37 | // TODO 38 | }, 39 | }); 40 | } 41 | }) 42 | .catch((e) => { 43 | reject(e); 44 | }); 45 | }); 46 | 47 | export const WebAssembly = { 48 | instantiate: (buffer, importObject) => { 49 | return instantiate(buffer); 50 | }, 51 | // Do not support because `FileReader.readAsArrayBuffer` is not supported by React Native currently. 52 | // instantiateStreaming: (response, importObject) => 53 | // Promise.resolve(response.arrayBuffer()).then((bytes) => 54 | // instantiate(bytes) 55 | // ), 56 | compile: (bytes) => {}, 57 | // Do not support because `FileReader.readAsArrayBuffer` is not supported by React Native currently. 58 | // compileStreaming: () => {}, 59 | validate: () => true, 60 | Instance: () => {}, 61 | Module: () => {}, 62 | Memory: () => {}, 63 | Table: () => {}, 64 | }; 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Platform } from "react-native"; 2 | import { WebAssembly as WasmPolyfill } from "./WebAssembly"; 3 | 4 | if (Platform.OS === "ios" || Platform.OS === "android") { 5 | window.WebAssembly = window.WebAssembly || WasmPolyfill; 6 | } 7 | --------------------------------------------------------------------------------