├── .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 | 
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 |
--------------------------------------------------------------------------------