Skip to content

shirakaba/react-native-nativescript-runtime

Repository files navigation

react-native-nativescript-runtime

Use the NativeScript runtime in a React Native app!

Project status

Still a work-in-progress. See the TODO section at the bottom of the Readme for current progress.

In short: We are not yet running NativeScript code in React Native. However, we do have a script to automatically integrate the NativeScript library set up in a React Native iOS project. I've not paid much attention to Android at all, yet.

Installation

npm install react-native-nativescript-runtime

Usage

import NativescriptRuntime from "react-native-nativescript-runtime";

// ...

const result = await NativescriptRuntime.multiply(3, 7);

Contributing

I'll explain the repo structure.

Repo architecture

This repo was initialised using the standard react-native-builder-bob tool, which bootstraps a best-practice boilerplate for making a React Native plugin (whilst also testing it on an example app).

To get an impression of what changes I've made beyond initialising the repo, see the next section where I provide a diff.

To understand what the purpose of this labyrinth of folders is, see my notes below.

Things to keep in mind:

  • This React Native plugin, like most React Native plugins, will be distributed as an npm package that itself vends a locally-built CocoaPod and a locally-built Gradle file. Those locally-built native modules can make use of all the files that the npm package includes.
  • The example app lives at /example
  • The iOS native sources live at /ios
  • The Android native sources live at /android
  • Any directory can be pulled into the npm package, e.g. /scripts, for use by the iOS and/or Android native modules.
  • Conversely, the iOS and/or Android native modules can choose to exclude some of the files in their directories (this is appropriate when we have e.g. an iOS-specific resource like XCFrameworks.zip that we want to simply unzip into the root of the app, rather than bundle into it as-is).
.
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── android # The React Native Android native module.
│   ├── build.gradle
│   └── src
│   └── main
│       ├── AndroidManifest.xml
│       └── java
│           └── com
│               └── reactnativenativescriptruntime
│                   ├── NativescriptRuntimeModule.java
│                   └── NativescriptRuntimePackage.java
├── babel.config.js # The Babel config for all TSX?/JSX? sources excluding `/example`, which has its own Babel config.
├── example # The folder for the example app. Unlike the convention in NativeScript, it is source-controlled.
│   ├── android # The Android example app. I haven't done much work on it yet.
│   │   ├── app
│   │   ├── build.gradle
│   │   ├── gradle
│   │   ├── gradle.properties
│   │   ├── gradlew
│   │   ├── gradlew.bat
│   │   ├── nativescript.build.gradle
│   │   ├── nativescript.buildscript.gradle
│   │   └── settings.gradle
│   ├── app.json # The React Native example app's metadata (display name, etc). I think this is an Expo concept.
│   ├── babel.config.js # The React Native example app's Babel config.
│   ├── index.tsx # The React Native example app's entrypoint.
│   ├── ios # The iOS example app.
│   │   ├── File.swift
│   │   ├── NativeScript # This is copied from the `ios` folder vended by the npm package.
│   │   ├── NativeScript.xcframework # This is unzipped from the `ios/XCFrameworks.zip` folder vended by the npm package.
│   │   ├── NativescriptRuntimeExample
│   │   ├── NativescriptRuntimeExample-Bridging-Header.h
│   │   ├── NativescriptRuntimeExample.xcodeproj
│   │   ├── NativescriptRuntimeExample.xcworkspace
│   │   ├── Podfile # The Podfile for the iOS app. Calls upon use_nativescript.rb.
│   │   ├── Podfile.lock
│   │   ├── Pods
│   │   ├── TNSWidgets.xcframework # This is unzipped from the `ios/XCFrameworks.zip` folder vended by the npm package.
│   │   ├── __MACOSX
│   │   ├── internal # This is copied from @nativescript/ios.
│   │   └── use_nativescript.rb # The script to install NativeScript. Once finalised, I plan to move this logic into:
|                               # /react-native-nativescript-runtime.podspec, or;
|                               # /scripts/use_nativescript.rb.
│   ├── metro.config.js # The bundler config for the React Native example app.
│   ├── nativescript
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json 
│   └── src # The sources for the React Native example app.
|           # I'm debating whether or not to nest the NativeScript sources in src/nativescript.
|           # It's not clear what would be more intuitive in a React Native project.
├── ios # The React Native iOS native module.
│   ├── NativeScript # Copied from @nativescript/capacitor. Originally from @nativescript/ui-mobile-base.
│   │   ├── App-Bridging-Header.h
│   │   ├── Runtime.h
│   │   └── Runtime.m
│   ├── NativescriptRuntime.h # React Native iOS native module header. Not yet filled in.
│   ├── NativescriptRuntime.m # React Native iOS native module implementation. Not yet filled in.
│   ├── NativescriptRuntime.xcodeproj # React Native iOS native module xcodeproj.
│   └── XCFrameworks.zip # The JSC runtime, copied from @nativescript/capacitor.
                         # Unlike @nativescript/ios/framework/internal/XCFrameworks.zip found in @nativescript/ios@8.x.x,
                         # it lacks the TNSRuntime.h header (perhaps that's a JSC-only thing?).
                         # Oddly, in @nativescript/ios@6.5.4, there is no XCFrameworks.zip file at all!
                         # So it seems that this zip really was made bespoke for the Capacitor integration.
                         # It sounds like  main reason we're using JSC rather than V8 is because this project is based on
                         # a camera project that nStudio did for a client in Ecuador.
                         # The good news is, this .zip is potentially stable indefinitely, as JSC is a slow-moving project.
├── package.json # When publishing this module to npm, it'll be from the root of the repo, using this.
├── react-native-nativescript-runtime.podspec # Specifies any native iOS files to be included in the CocoaPod.
├── scripts # Scripts to be used by both the npm package and (if necessary) the native modules.
│   ├── bootstrap.js
│   ├── build-nativescript.js
│   └── extractFrameworks.js
├── src # This is where you write the TypeScript API corresponding to your native module.
│   ├── __tests__
│   └── index.tsx
├── tsconfig.build.json # This examines all TypeScript sources excluding the example app, `/example`.
└── tsconfig.json

Changes relative to a freshly initialised React Native plugin

Here is a tree of changes I've made, relative to a freshly initialised react-native-builder-bob plugin. Note that this is very approximate, as things are changing rapidly from commit to commmit and I may simply miss some things. But it should at least give a general orientation as to what's specific to this plugin rather than just being typical React Native plugin boilerplate.

  • In green are the files that I either committed to the repo as sources, or will be auto-provisioned upon building the project (e.g. due to the use_nativescript.rb iOS installer script, which is equivalent to the postinstall.ts script in the NativeScript Capacitor repo).
  • Marked with an asterisk are any files that I've modified (again, either committed to source or post-build).

⚠️ Beware that I've not actually committed the Android aspects yet, but eventually they'll be shaped like this.

.
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── android
│   ├── build.gradle *
│   └── src
│       └── main
├── babel.config.js
├── example
│   ├── android
│   │   ├── app
│   │   │   ├── build.gradle *
│   │   │   ├── debug.keystore
│   │   │   ├── proguard-rules.pro
│   │   │   └── src
│   │   │       └── main
│   │   │           └── AndroidManifest.xml *
│   │   ├── build.gradle *
│   │   ├── gradle
│   │   ├── gradle.properties
│   │   ├── gradlew
│   │   ├── gradlew.bat
+ │   │   ├── nativescript.build.gradle
+ │   │   ├── nativescript.buildscript.gradle
│   │   └── settings.gradle
│   ├── app.json
│   ├── babel.config.js
│   ├── index.tsx
│   ├── ios
│   │   ├── File.swift
+ │   │   ├── NativeScript
+ │   │   ├── NativeScript.xcframework
│   │   ├── NativescriptRuntimeExample
│   │   ├── NativescriptRuntimeExample-Bridging-Header.h
│   │   ├── NativescriptRuntimeExample.xcodeproj *
│   │   ├── NativescriptRuntimeExample.xcworkspace
│   │   ├── Podfile *
│   │   ├── Podfile.lock *
│   │   ├── Pods
+ │   │   ├── TNSWidgets.xcframework
+ │   │   ├── __MACOSX
+ │   │   ├── internal
+ │   │   └── use_nativescript.rb
│   ├── metro.config.js
+ │   ├── nativescript
+ │   │   ├── build
+ │   │   ├── package.json
+ │   │   ├── references.d.ts
+ │   │   ├── src
+ │   │   └── tsconfig.json
│   ├── node_modules
│   ├── package-lock.json *
│   ├── package.json *
│   └── src
│       └── App.tsx
├── ios
+ │   ├── NativeScript
+ │   │   ├── App-Bridging-Header.h
+ │   │   ├── Runtime.h
+ │   │   └── Runtime.m
│   ├── NativescriptRuntime.h
│   ├── NativescriptRuntime.m
│   ├── NativescriptRuntime.xcodeproj
│   │   ├── project.pbxproj
│   │   └── project.xcworkspace
+ │   └── XCFrameworks.zip
├── package.json
├── react-native-nativescript-runtime.podspec
├── scripts
│   ├── bootstrap.js
+ │   └── build-nativescript.js
├── src
│   ├── __tests__
│   │   └── index.test.tsx
│   └── index.tsx
├── tsconfig.build.json
└── tsconfig.json

TODO

Android

Nothing working end-to-end yet; very much under construction.

  • Assemble everything from the Capacitor embed folder. Reasonably confident that I've integrated the build scripts correctly, but not so sure about all the other stuff.
  • Do a test-run and see if both the app and native module compile at all.
  • Check whether all the file paths and assets used by the native module resolve correctly. I see many usages of $rootDir, $userDir/nsconfig.json, etc., which may all make incorrect assumptions.
  • See whether any of the Android files can be derived from @nativescript/android instead.
  • Write the bridge code.
  • Convert to a TurboModule.

iOS

Auto-provisioning of the Xcode project is complete. Some steps remain manual for now.

  • [Swift support] Allow (or simply instruct) users how to use their own bridging header
  • [Swift support] Support AppDelegate.swift
  • Support auto-injection of runtime support into AppDelegate.{m,swift} (see the code blocks surrounded by // START NativeScript runtime and // END NativeScript runtime), or just give instructions.
  • Support uninstallation of NativeScript runtime support.
  • Move use_nativescript.rb up from the app to the iOS native module. It's only really there at the moment so that I have a shorter path to type out when running it on the CLI.
  • Take TNSWidgets from @nativescript/core rather than committing it to this repo.
  • Check whether NativeScript.xcframework can be taken from anywhere. I checked @nativescript/ios, both v6.x.x (JSC) and v8.x.x (V8), but it wasn't there. Seems it's bespoke to the Capacitor effort.
  • Convert to a TurboModule.

Despite bundling XCFrameworks.zip, we do still need to ensure that the root project's node_modules holds @nativescript/ios (whether as a direct dependency or as a subdependency of our npm package), because that's where our Cocoapod pulls the whole @nativescript/ios/framework/internal folder from (which provides things like the nativescript-pre-build script).

There's an argument that maybe we should just bundle the internal folder into the native module as we're already doing so for XCFrameworks.zip, but I prefer to re-use published packages wherever possible. Ideally at some point we'd be able to just cleanly source everything from @nativescript/ios.

It's probably best for it to be the responsibility of the app itself to install @nativescript/ios and/or @nativescript/android, to save disk space for anyone working on a single-platform project.

TS/JS

Figure out how to make NativeScript bundle (see scripts/build-nativescript.js).

Calling NativeScript from React Native

Build an API for sending messages to the NativeScript runtime from the React Native JS context. i.e. make a React Native native module under /ios and /android, with reference to the Capacitor one.

Approach

  1. I initialised a React Native plugin using react-native-builder-bob, selecting "Java & Objective-C" as the library type:

    npx create-react-native-library react-native-nativescript-runtime
  2. I ported the NativeScript Capacitor postinstall script to Ruby (see example/ios/use_nativescript.rb), adjusting it for the structure of a React Native app: https://github.com/NativeScript/capacitor/blob/main/src/postinstall.ts

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT

About

Use the NativeScript runtime from React Native

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published