├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── app.json ├── assets ├── icon.png └── splash.png ├── babel.config.js ├── build.sbt ├── fastopt-noparse.js ├── metro.config.js ├── package.json ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala └── hello └── world ├── App.scala └── Main.scala /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | *.orig.* 9 | web-build/ 10 | web-report/ 11 | 12 | project/project/ 13 | project/target/ 14 | target/ 15 | .metals/ 16 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, View} from 'react-native'; 3 | 4 | let app; 5 | if (__DEV__) { 6 | app = require("./target/scala-2.13/app-fastopt.js").app; 7 | } else { 8 | module.exports = () => { 9 | return 10 | 11 | Scala.js opt mode has not been enabled in App.js, please uncomment the code for it. 12 | 13 | ; 14 | } 15 | 16 | // uncomment the following line to enable opt building 17 | // app = require("./target/scala-2.13/app-opt.js").app; 18 | } 19 | 20 | export default app; 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scala.js + Slinky Expo Template 2 | This template sets up a simple React Native application written in Scala through [Scala.js](https://www.scala-js.org), [Slinky](https://slinky.dev), and [Expo](https://expo.io). 3 | 4 | ## Installation 5 | 6 | On macOS will need to install XCode for this if you haven't already. 7 | 8 | Install the Expo CLI: 9 | ``` 10 | $ npm install -g expo-cli 11 | ``` 12 | 13 | Use the template 14 | ``` 15 | $ expo init --template expo-template-scala 16 | ``` 17 | 18 | Prepare react-native 19 | ``` 20 | $ npm install react-native 21 | ``` 22 | 23 | ## Building Apps 24 | Once you've created your project, first compile your Scala code to JavaScript by running 25 | ``` 26 | $ sbt fastOptJS 27 | ``` 28 | 29 | Then, launch the app with Expo 30 | ``` 31 | $ npm start 32 | ``` 33 | 34 | ## Production Bundles 35 | To create a minified app, first build an optimized Scala.js bundle 36 | ``` 37 | $ sbt fullOptJS 38 | ``` 39 | 40 | Then, go to `App.js` and uncomment `app = require("./target/scala-2.13/app-opt.js").app;` in order to enable the production bundle. Finally, switch Expo to production mode to load your production app. 41 | 42 | ## Ejecting 43 | If you need to develop your app without depending on Expo, you can eject at any time by running `npm run eject`. This gives you the option to either eject to a "bare" project that uses React Native's build tooling or to an unmanaged ExpoKit project that still uses Expo but lets you add in custom native dependencies. 44 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "new-template", 4 | "description": "A template setting up Expo with Slinky and Scala.js", 5 | "slug": "new-template", 6 | "privacy": "public", 7 | "sdkVersion": "37.0.0", 8 | "platforms": [ 9 | "ios", 10 | "android" 11 | ], 12 | "packagerOpts": { 13 | "config": "metro.config.js" 14 | }, 15 | "version": "1.0.0", 16 | "orientation": "portrait", 17 | "icon": "./assets/icon.png", 18 | "splash": { 19 | "image": "./assets/splash.png", 20 | "resizeMode": "contain", 21 | "backgroundColor": "#ffffff" 22 | }, 23 | "updates": { 24 | "fallbackToCacheTimeout": 0 25 | }, 26 | "assetBundlePatterns": [ 27 | "**/*" 28 | ], 29 | "ios": { 30 | "supportsTablet": true 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadaj/expo-template-scala/34839f5276a9477f6c1c1b8e6db5071210ee5afa/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadaj/expo-template-scala/34839f5276a9477f6c1c1b8e6db5071210ee5afa/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(ScalaJSPlugin) 2 | 3 | name := "app" 4 | 5 | scalaVersion := "2.13.2" 6 | 7 | libraryDependencies += "me.shadaj" %%% "slinky-native" % "0.6.5" 8 | libraryDependencies += "me.shadaj" %%% "slinky-hot" % "0.6.5" 9 | 10 | scalacOptions += "-Ymacro-annotations" 11 | 12 | scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) -------------------------------------------------------------------------------- /fastopt-noparse.js: -------------------------------------------------------------------------------- 1 | var transformer = require('metro-react-native-babel-transformer') 2 | 3 | module.exports.transform = function ({ src, filename, options }) { 4 | options = options || {}; 5 | if (filename.indexOf('fastopt') > -1) { 6 | return { 7 | code: src, 8 | filename 9 | } 10 | } 11 | else { 12 | return transformer.transform({ src, filename, options }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformer: { 3 | babelTransformerPath: require("path").resolve("./fastopt-noparse.js"), 4 | getTransformOptions: async () => ({ 5 | transform: { 6 | experimentalImportSupport: false, 7 | inlineRequires: false, 8 | }, 9 | }), 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-template-scala", 3 | "version": "37.0.1", 4 | "repository": "https://github.com/shadaj/expo-template-scala", 5 | "license": "MIT", 6 | "main": "node_modules/expo/AppEntry.js", 7 | "scripts": { 8 | "start": "expo start", 9 | "android": "expo start --android", 10 | "ios": "expo start --ios", 11 | "web": "expo start --web", 12 | "eject": "expo eject" 13 | }, 14 | "dependencies": { 15 | "expo": "^37.0.7", 16 | "react": "16.9.0", 17 | "react-dom": "16.9.0", 18 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", 19 | "react-native-web": "^0.13.14", 20 | "react-proxy": "1.1.8" 21 | }, 22 | "devDependencies": { 23 | "babel-preset-expo": "^8.1.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.10 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.0") 2 | -------------------------------------------------------------------------------- /src/main/scala/hello/world/App.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import slinky.core._ 4 | import slinky.core.annotations.react 5 | import slinky.native._ 6 | 7 | import scala.scalajs.js.Dynamic.literal 8 | 9 | @react class App extends Component { 10 | type Props = Unit 11 | type State = Int 12 | 13 | def initialState = 0 14 | 15 | def render() = { 16 | View( 17 | style = literal( 18 | backgroundColor = "white", 19 | padding = 50, 20 | flex = 1, 21 | flexDirection = "column", 22 | justifyContent = "center", 23 | alignItems = "center" 24 | ) 25 | )( 26 | Image( 27 | source = ImageURISource( 28 | uri = "https://raw.githubusercontent.com/shadaj/slinky/master/logo.png" 29 | ), 30 | style = literal( 31 | width = 250, 32 | height = 250 33 | ), 34 | resizeMode = "cover" 35 | ), 36 | Text( 37 | style = literal(fontSize = 30, color = "red") 38 | )("Hello, Slinky Native!"), 39 | Text("Count: " + state), 40 | Button(title = "Tap Me!", onPress = () => { 41 | setState(_ + 1) 42 | }) 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/hello/world/Main.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import slinky.hot 4 | 5 | import scala.scalajs.LinkingInfo 6 | import scala.scalajs.js.annotation.JSExportTopLevel 7 | 8 | object Main { 9 | if (LinkingInfo.developmentMode) { 10 | hot.initialize() 11 | } 12 | 13 | @JSExportTopLevel("app") 14 | val app = App.componentConstructor 15 | } 16 | --------------------------------------------------------------------------------