Skip to content

Commit b136294

Browse files
authored
Merge pull request #7 from icerockdev/develop
Release 0.1.0
2 parents fba25aa + 2bb35db commit b136294

File tree

20 files changed

+742
-48
lines changed

20 files changed

+742
-48
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://api.bintray.com/packages/icerockdev/moko/moko-javascript/images/download.svg) ](https://bintray.com/icerockdev/moko/moko-javascript/_latestVersion) ![kotlin-version](https://img.shields.io/badge/kotlin-1.4.32-orange)
1+
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/dev.icerock.moko/javascript) ](https://repo1.maven.org/maven2/dev/icerock/moko/javascript) ![kotlin-version](https://img.shields.io/badge/kotlin-1.4.32-orange)
22

33
# Mobile Kotlin javascript
4-
This is a Kotlin MultiPlatform library that ...
4+
This is a Kotlin MultiPlatform library that allows you to run JavaScript code from common Kotlin code
55

66
## Table of Contents
77
- [Features](#features)
@@ -14,7 +14,8 @@ This is a Kotlin MultiPlatform library that ...
1414
- [License](#license)
1515

1616
## Features
17-
...
17+
- Evaluate JavaScript code from Kotlin common code
18+
- Pass objects to JavaScript as global vars
1819

1920
## Requirements
2021
- Gradle version 6.0+
@@ -43,7 +44,16 @@ dependencies {
4344
```
4445

4546
## Usage
46-
...
47+
```kotlin
48+
val javaScriptEngine = JavaScriptEngine()
49+
val result: JsType = javaScriptEngine.evaluate(
50+
context = emptyMap(),
51+
script = """ "Hello" + "World" """.trimIndent()
52+
)
53+
if (result is JsType.Str) {
54+
println(result.value)
55+
}
56+
```
4757

4858
## Samples
4959
More examples can be found in the [sample directory](sample).

buildSrc/src/main/kotlin/Deps.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,50 @@ object Deps {
88
private const val androidAppCompatVersion = "1.1.0"
99
private const val materialDesignVersion = "1.0.0"
1010
private const val androidLifecycleVersion = "2.1.0"
11-
private const val androidCoreTestingVersion = "2.1.0"
11+
private const val androidCoreTestingVersion = "1.3.0"
12+
private const val testJUnitExtVersion = "1.1.2"
13+
private const val quickjsVersion = "0.9.0"
1214

1315
private const val coroutinesVersion = "1.4.2"
14-
private const val mokoTestVersion = "0.2.0"
16+
private const val kotlinxSerializationVersion = "1.1.0"
17+
private const val mokoTestVersion = "0.3.0"
18+
1519
const val mokoJavascriptVersion = "0.1.0"
1620

1721
object Android {
1822
const val compileSdk = 30
1923
const val targetSdk = 30
20-
const val minSdk = 16
24+
const val minSdk = 18
2125
}
2226

2327
object Libs {
2428
object Android {
2529
const val appCompat = "androidx.appcompat:appcompat:$androidAppCompatVersion"
2630
const val material = "com.google.android.material:material:$materialDesignVersion"
2731
const val lifecycle = "androidx.lifecycle:lifecycle-extensions:$androidLifecycleVersion"
32+
33+
const val kotlinTestJUnit = "org.jetbrains.kotlin:kotlin-test-junit:$kotlinTestVersion"
34+
const val testRunner = "androidx.test:runner:$androidCoreTestingVersion"
35+
const val testRules = "androidx.test:rules:$androidCoreTestingVersion"
36+
const val testJUnitExt = "androidx.test.ext:junit:$testJUnitExtVersion"
37+
const val testJUnitExtKtx = "androidx.test.ext:junit-ktx:$testJUnitExtVersion"
38+
39+
const val quickjs = "app.cash.quickjs:quickjs-android:$quickjsVersion"
2840
}
2941

3042
object MultiPlatform {
3143
const val coroutines =
3244
"org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
45+
const val kotlinSerialization =
46+
"org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion"
47+
48+
const val kotlinTest =
49+
"org.jetbrains.kotlin:kotlin-test-common:$kotlinTestVersion"
50+
const val kotlinTestAnnotations =
51+
"org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinTestVersion"
52+
const val mokoTest = "dev.icerock.moko:test-core:$mokoTestVersion"
53+
const val mokoTestRobolectric = "dev.icerock.moko:test-roboelectric:$mokoTestVersion"
3354

34-
const val mokoTest = "dev.icerock.moko:test:$mokoTestVersion"
3555
const val mokoJavascript = "dev.icerock.moko:javascript:$mokoJavascriptVersion"
3656
}
3757
}
Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,79 @@
1+
import java.util.Base64
2+
13
plugins {
24
id("org.gradle.maven-publish")
5+
id("signing")
36
}
47

5-
publishing {
6-
group = "dev.icerock.moko"
7-
version = Deps.mokoJavascriptVersion
8+
group = "dev.icerock.moko"
9+
version = Deps.mokoJavascriptVersion
10+
11+
val javadocJar by tasks.registering(Jar::class) {
12+
archiveClassifier.set("javadoc")
13+
}
814

9-
repositories.maven("https://api.bintray.com/maven/icerockdev/moko/moko-javascript/;publish=1") {
10-
name = "bintray"
15+
publishing {
16+
repositories.maven("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") {
17+
name = "OSSRH"
1118

1219
credentials {
13-
username = System.getenv("BINTRAY_USER")
14-
password = System.getenv("BINTRAY_KEY")
20+
username = System.getenv("OSSRH_USER")
21+
password = System.getenv("OSSRH_KEY")
22+
}
23+
}
24+
25+
publications.withType<MavenPublication> {
26+
// Stub javadoc.jar artifact
27+
artifact(javadocJar.get())
28+
29+
// Provide artifacts information requited by Maven Central
30+
pom {
31+
name.set("MOKO JavaScript")
32+
description.set("JavaScript code evaluation from common code for Kotlin Multiplatform Mobile")
33+
url.set("https://github.com/icerockdev/moko-javascript")
34+
licenses {
35+
license {
36+
name.set("Apache-2.0")
37+
distribution.set("repo")
38+
url.set("https://github.com/icerockdev/moko-javascript/blob/master/LICENSE.md")
39+
}
40+
}
41+
42+
developers {
43+
developer {
44+
id.set("Tetraquark")
45+
name.set("Vladislav Areshkin")
46+
email.set("vareshkin@icerockdev.com")
47+
}
48+
developer {
49+
id.set("Dorofeev")
50+
name.set("Andrey Dorofeev")
51+
email.set("adorofeev@icerockdev.com")
52+
}
53+
developer {
54+
id.set("Alex009")
55+
name.set("Aleksey Mikhailov")
56+
email.set("aleksey.mikhailov@icerockdev.com")
57+
}
58+
}
59+
60+
scm {
61+
connection.set("scm:git:ssh://github.com/icerockdev/moko-javascript.git")
62+
developerConnection.set("scm:git:ssh://github.com/icerockdev/moko-javascript.git")
63+
url.set("https://github.com/icerockdev/moko-javascript")
64+
}
65+
}
66+
}
67+
68+
signing {
69+
val signingKeyId: String? = System.getenv("SIGNING_KEY_ID")
70+
val signingPassword: String? = System.getenv("SIGNING_PASSWORD")
71+
val signingKey: String? = System.getenv("SIGNING_KEY")?.let { base64Key ->
72+
String(Base64.getDecoder().decode(base64Key))
73+
}
74+
if (signingKeyId != null) {
75+
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
76+
sign(publishing.publications)
1577
}
1678
}
1779
}

javascript/build.gradle.kts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,53 @@ plugins {
77
id("publication-convention")
88
}
99

10+
android {
11+
testOptions.unitTests.isIncludeAndroidResources = true
12+
defaultConfig {
13+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14+
}
15+
16+
packagingOptions {
17+
exclude("META-INF/*.kotlin_module")
18+
exclude("META-INF/*.kotlin_module")
19+
exclude("META-INF/AL2.0")
20+
exclude("META-INF/LGPL2.1")
21+
}
22+
23+
sourceSets {
24+
getByName("androidTest").java.srcDirs(
25+
file("src/androidAndroidTest/kotlin"),
26+
file("src/mobileDeviceTest/kotlin")
27+
)
28+
}
29+
}
30+
31+
kotlin {
32+
sourceSets {
33+
val mobileDeviceTest by creating
34+
35+
val commonTest by getting
36+
val iosTest by getting
37+
val androidAndroidTest by getting
38+
39+
mobileDeviceTest.dependsOn(commonTest)
40+
iosTest.dependsOn(mobileDeviceTest)
41+
androidAndroidTest.dependsOn(mobileDeviceTest)
42+
}
43+
}
44+
1045
dependencies {
46+
androidMainImplementation(Deps.Libs.Android.quickjs)
47+
48+
commonMainImplementation(Deps.Libs.MultiPlatform.kotlinSerialization)
49+
50+
commonTestImplementation(Deps.Libs.MultiPlatform.kotlinTest)
51+
commonTestImplementation(Deps.Libs.MultiPlatform.kotlinTestAnnotations)
1152
commonTestImplementation(Deps.Libs.MultiPlatform.mokoTest)
53+
54+
androidTestImplementation(Deps.Libs.Android.kotlinTestJUnit)
55+
androidTestImplementation(Deps.Libs.Android.testRunner)
56+
androidTestImplementation(Deps.Libs.Android.testRules)
57+
androidTestImplementation(Deps.Libs.Android.testJUnitExt)
58+
androidTestImplementation(Deps.Libs.Android.testJUnitExtKtx)
1259
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest package="dev.icerock.moko.javascript" />
2+
<manifest package="dev.icerock.moko.javascript" />
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.javascript
6+
7+
import app.cash.quickjs.QuickJs
8+
import app.cash.quickjs.QuickJsException
9+
import kotlinx.serialization.SerializationException
10+
import kotlinx.serialization.builtins.serializer
11+
import kotlinx.serialization.json.Json
12+
import kotlinx.serialization.json.JsonElement
13+
import kotlinx.serialization.json.JsonObject
14+
15+
actual class JavaScriptEngine actual constructor() {
16+
private val quickJs: QuickJs = QuickJs.create()
17+
private val json: Json = Json.Default
18+
19+
@Volatile
20+
var isClosed = false
21+
private set
22+
23+
actual fun evaluate(
24+
context: Map<String, JsType>,
25+
script: String
26+
): JsType {
27+
if (isClosed) throw JavaScriptEvaluationException(message = "Engine already closed")
28+
29+
return try {
30+
internalEvaluate(context, script)
31+
} catch (exception: QuickJsException) {
32+
throw JavaScriptEvaluationException(exception, exception.message)
33+
}
34+
}
35+
36+
actual fun close() {
37+
if (isClosed) return
38+
quickJs.close()
39+
isClosed = true
40+
}
41+
42+
private fun internalEvaluate(
43+
context: Map<String, JsType>,
44+
script: String
45+
): JsType {
46+
val scriptWithContext = convertContextMapToJsScript(context) + script + "\n"
47+
val result = quickJs.evaluate(scriptWithContext)
48+
return handleQuickJsResult(result)
49+
}
50+
51+
// TODO fix pass of arguments - now wrapping of string and json invalid and will be broken on multilined strings
52+
private fun convertContextMapToJsScript(context: Map<String, JsType>): String {
53+
if (context.isEmpty()) return ""
54+
55+
return context.mapNotNull { pair ->
56+
prepareValueForJs(pair.value)?.let { "var ${pair.key} = $it;" }
57+
}.joinToString(separator = "")
58+
}
59+
60+
private fun prepareValueForJs(valueWrapper: JsType): String? {
61+
return when (valueWrapper) {
62+
is JsType.Bool -> valueWrapper.value.toString()
63+
is JsType.DoubleNum -> valueWrapper.value.toString()
64+
is JsType.Json -> valueWrapper.value.let {
65+
Json.encodeToString(JsonElement.serializer(), it)
66+
}.let {
67+
it.replace("\"", "\\\"")
68+
}.let {
69+
"JSON.parse(\"$it\")"
70+
}
71+
is JsType.Str -> valueWrapper.value.let {
72+
it.replace("\"", "\\\"")
73+
}.let {
74+
"\"$it\""
75+
}
76+
JsType.Null -> null
77+
}
78+
}
79+
80+
private fun handleQuickJsResult(result: Any?): JsType {
81+
return when (result) {
82+
null -> JsType.Null
83+
is Boolean -> JsType.Bool(result)
84+
is Int -> JsType.DoubleNum(result.toDouble())
85+
is Double -> JsType.DoubleNum(result)
86+
is Float -> JsType.DoubleNum(result.toDouble())
87+
is String -> try {
88+
val serializeResult = json.parseToJsonElement(result)
89+
if (serializeResult is JsonObject) {
90+
JsType.Json(serializeResult)
91+
} else {
92+
JsType.Str(result)
93+
}
94+
} catch (ex: SerializationException) {
95+
JsType.Str(result)
96+
} catch (ex: IllegalStateException) {
97+
JsType.Str(result)
98+
}
99+
else -> throw JavaScriptEvaluationException(
100+
message = "Impossible JavaScriptEngine handler state with result [$result]"
101+
)
102+
}
103+
}
104+
}

javascript/src/commonMain/kotlin/dev/icerock/moko/javascript/JavaScriptEngine.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,16 @@
55
package dev.icerock.moko.javascript
66

77
expect class JavaScriptEngine() {
8+
/**
9+
* Evaluate some [script] with external [context].
10+
*
11+
* @throws JavaScriptEvaluationException in case of an error in the engine evaluation or if the
12+
* engine has already been closed.
13+
*/
814
fun evaluate(context: Map<String, JsType>, script: String): JsType
15+
16+
/**
17+
* Closes the engine and releases the allocated memory.
18+
*/
19+
fun close()
920
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.javascript
6+
7+
class JavaScriptEvaluationException(
8+
cause: Throwable? = null,
9+
message: String? = null
10+
) : Exception(message, cause)

0 commit comments

Comments
 (0)