React Native
React native support works fine but this section has not been updated with the most recent API. Also, some sbt plugins have been published to help copy and manage the scala.js artifacts without defining your plugins like shown below.
You can easily create react native applications using this library. react native
uses a "runner" application (unlike flutter) to host the javascript execution
engine. You will need the runner skeleton to build a react native
application. The best way to do that is to follow the instructions for
installing
react-native-cli
and then overlaying the scalajs library on top of the generated project.
Setup
To overlay the scala.js parts (until I create a nice g8 template with this already in it):
- Create build.sbt as you normally would.
- Create the project folder as you normally would.
- Create source folder
mkdir -p src/main/scala
content at the top-level.
Include the core libraries:
in your library dependencies. Create your build.sbt build file as you would for any scala project including scala flags, etc. You can copy portions of the build.sbt file at scala.js react template.
Alter the index.js in the top level directory to either contain the "exported to
js" component or you can call AppRegistry.registerComponent
inside of
scalajs. The scala.js function that you would call in index.js must be top level
exported (@JSExportTopLevel
). In either case, the index.js should either use
your exported JS "app" component or call a function that calls
registerComponent
.
You may want to include "org.scala-js" %%% "scala-js-dom" % "latest.version"
to include scala support for some of the available polyfills such as
fetch
. There is no DOM on the mobile devices, so the DOM parts of this library
are not available and you should not use them.
That's all you need for setup to get started.
Enhanced Setup
If you want to copy your scalajs output to a well known location so that you
only need to change your index.js (located in the toplevel directory) once, you
can set up a file copy task that copies the output of fullOptJS or
fastOptJS. You would run this task after the scala.js full/fast
processing. index.js is the default starting point for javascript bundling when
using the react-native cli such as react-native run-android
.
The metro bundler looks for index.js to create the javascript resource graph. Instead of modifying the output of sbt, you can also use build vars in javascript to switch between targets--a typical approach in javascript e.g. include min if you are in a dev build. Below is the copy approach. If your final scala.js project that creates the linked javascript artifact is always the same, it is probably easier to hardcode the path with conditionals in index.js.
However, let's just assume that you want to keep index.js unchanged and want to map full or fast builds to the same output file.
You will want to scope the task to the respective scala.js linkage tasks, fastOptJS or fullOptJS so that the correct artifactPath is picked up.
There are many ways to do this in sbt including using artifactPath (scoped to the fast or full task inside Compile) to copy the file and standardize its output name for react-native.
If you need to copy per task you could also create a plugin or do something small like:
Or as a plugin:
Managing task running in sbt is covered in the manual as there are a few different ways to set the above copy operation up some of which are more simple then defining a task. A really great blog on sbt tasks is here, you should read it just to be smarter about sbt.
Here's a stackoverflow article on the same topic.
Calling AppRegistry.registerComponent in scala.js
If your index.js file imports a function that performs the registration you need to write the registration function in scala first:
Regardless of whether you call registerComponent
in scala or JS, you need to
create the application component. That's easy. Don't forget to "wrap" it for JS
use.
In the code below, the component itself is exported so you can call
registerComponent
in index.js. If you call registerComponent
in scala as
show above, you do not need to export the anything related to the component but
it still need to be wrapped.
The top level is usually a stateless component as it is called by the react-native framework and does not take any arguments.
If you have your exports setup using either approach, your index.js should include the scala.js output:
If you want to switch on the build type in javascript and skip the sbt configuration above, use an ES6 feature with dynamic imports (make sure its enabled in your environment if you need to):
If you don't have ES6 support you could use the legacy "require(...)" which
should be supported for some time to come with most bundlers. The default
react-native metro should allow the dynamic import using import
. If you use
this approach, make sure any imported resources such as images are available
based on a path relative to the js file as your scala or js imports inside of
app-opt.js/app-fastopt.js will now be relative to the target/scala-2.12
directory.
Build
Run sbt as you normally would and during dev and use ~fastOptJS
if you used
the triggered approach to perform the scala.js output copy. That allows you to
recompile as needed. react native uses its own JS packager, called metro, that
restructures your JS similar to webpack.
metro is not as feature-rich as webpack. When you run react-native run-android
it first runs gradle to build the java part of the project and it starts up a JS
server similar to the way that webpack-dev-server works. It is suppose to detect
changes in js files and do a hot reload. You may need to turn on hot reloading
using Ctrl+M (linux/windows hosted emulators).
Helpers
While you should install android studio because you will probably need to write some interop at some point, you can start the emulator without starting android studio:
You will want to add:
to a shell script you can import into your current terminal.
Other Libraries
There are some ports of common libraries, all WIP and some have no code yet :-):
- react-navigation (working)
- sideswipe (working)
- nativebase (no code yet)
- react-native-elements (no code yet)
Creating facades is easy. It only took 3 hours to create the entire facade for react-native from scratch when I did not even know react-native. I just look at the typescript definitions and develop a scala.js friendly API from there. I believe that a good combination is to use scala.js and typescript together.
Resources
Widget/other libraries:
Result
Here's a simple screen using Text and Button.
{:width="300px"}
app.scala
main.scala:
index.js: