Skip to content

Commit decbc83

Browse files
committed
MPP: Example frontend JS application using nodejs and webpack
1 parent 4747f3a commit decbc83

File tree

14 files changed

+410
-18
lines changed

14 files changed

+410
-18
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
build
66
out
77
target
8-
node_modules
8+
node_modules
9+
package-lock.json

build.gradle

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) {
5252

5353
kotlin.experimental.coroutines "enable"
5454

55+
if (platform == "js") {
56+
tasks.withType(compileKotlin2Js.getClass()) {
57+
kotlinOptions {
58+
moduleKind = "umd"
59+
sourceMap = true
60+
metaInfo = true
61+
}
62+
}
63+
}
64+
5565
tasks.withType(Test) {
5666
testLogging {
5767
showStandardStreams = true
@@ -87,10 +97,6 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) {
8797
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
8898
}
8999
}
90-
91-
if (platform == "js") {
92-
apply from: rootProject.file('gradle/test-js.gradle')
93-
}
94100
}
95101

96102
// --------------- Configure sub-projects that are part of the library ---------------
@@ -143,7 +149,7 @@ configure(subprojects.findAll { !internal.contains(it.name) && it.name != 'kotli
143149

144150
// --------------- Configure sub-projects that are published ---------------
145151

146-
def unpublished = internal + 'kotlinx-coroutines-rx-example'
152+
def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js']
147153

148154
def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/"
149155
def core_docs_file = "$projectDir/core/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list"

gradle.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ version = 0.20-SNAPSHOT
33
kotlin_version = 1.2.10
44
junit_version = 4.12
55
atomicFU_version = 0.9.2
6+
html_version = 0.6.8
67
lincheck_version=1.9
78
dokka_version = 0.9.16-dev-mpp-hacks-1
89
bintray_version = 1.7.3
9-
gradle_node_version = 1.2.0
1010

11+
gradle_node_version = 1.2.0
12+
node_version = 8.9.3
13+
npm_version = 5.5.1

gradle/test-js.gradle renamed to gradle/test-mocha-js.gradle

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22

33
apply plugin: 'com.moowork.node'
44

5-
tasks.withType(compileKotlin2Js.getClass()) {
6-
kotlinOptions {
7-
moduleKind = "umd"
8-
sourceMap = true
9-
metaInfo = true
10-
}
11-
}
12-
135
task populateNodeModules(type: Copy, dependsOn: compileKotlin2Js) {
146
from compileKotlin2Js.destinationDir
157
into "${buildDir}/node_modules"
@@ -25,6 +17,8 @@ task populateNodeModules(type: Copy, dependsOn: compileKotlin2Js) {
2517
}
2618

2719
node {
20+
version = "$node_version"
21+
npmVersion = "$npm_version"
2822
download = true
2923
}
3024

js/example-frontend-js/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Example JS frontend application with coroutines
2+
3+
Build application with
4+
5+
```
6+
gradlew :example-frontend-js:build
7+
```
8+
9+
The resulting application can be found in `build/dist` subdirectory.
10+
11+
You can start application with webpack-dev-server using:
12+
13+
```
14+
gradlew :example-frontend-js:start
15+
```

js/example-frontend-js/build.gradle

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
apply plugin: 'kotlin-dce-js'
18+
apply plugin: 'com.moowork.node'
19+
20+
dependencies {
21+
compile "org.jetbrains.kotlinx:kotlinx-html-js:$html_version"
22+
}
23+
24+
compileKotlin2Js {
25+
kotlinOptions {
26+
main = "call"
27+
}
28+
}
29+
30+
node {
31+
version = "$node_version"
32+
npmVersion = "$npm_version"
33+
download = true
34+
}
35+
36+
task bundle(type: NpmTask, dependsOn: [npmInstall, runDceKotlinJs]) {
37+
args = ["run", "bundle"]
38+
}
39+
40+
task start(type: NpmTask, dependsOn: bundle) {
41+
args = ["run", "start"]
42+
}

js/example-frontend-js/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "example-frontend-js",
3+
"dependencies": {
4+
"webpack": "^3.10.0"
5+
},
6+
"devDependencies": {
7+
"html-webpack-plugin": "^2.30.1",
8+
"webpack-dev-server": "^2.9.7",
9+
"style-loader": "^0.19.1",
10+
"css-loader": "^0.28.7"
11+
},
12+
"scripts": {
13+
"bundle": "webpack",
14+
"start": "webpack-dev-server --open --no-inline"
15+
}
16+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import kotlinx.coroutines.experimental.*
18+
import kotlinx.html.*
19+
import kotlinx.html.div
20+
import kotlinx.html.dom.*
21+
import kotlinx.html.js.onClickFunction
22+
import org.w3c.dom.*
23+
import kotlin.browser.*
24+
import kotlin.math.*
25+
26+
fun main(args: Array<String>) {
27+
println("Starting example application...")
28+
document.addEventListener("DOMContentLoaded", {
29+
Application().start()
30+
})
31+
}
32+
33+
val Double.px get() = "${this}px"
34+
35+
private fun HTMLElement.setSize(w: Double, h: Double) {
36+
with(style) {
37+
width = w.px
38+
height = h.px
39+
}
40+
}
41+
42+
private fun HTMLElement.setPosition(x: Double, y: Double) {
43+
with(style) {
44+
left = x.px
45+
top = y.px
46+
}
47+
}
48+
49+
@Suppress("DEPRECATION")
50+
fun random() = kotlin.js.Math.random()
51+
52+
class Application {
53+
private val body get() = document.body!!
54+
private val scene get() = document.getElementById("scene") as HTMLElement
55+
private val sw = 800.0
56+
private val sh = 600.0
57+
private var animationIndex = 0
58+
private val animations = mutableSetOf<Job>()
59+
60+
fun start() {
61+
body.append.div("content") {
62+
h1 {
63+
+"Kotlin Coroutines JS Example"
64+
}
65+
div {
66+
button {
67+
+"Rect"
68+
onClickFunction = { onRect() }
69+
}
70+
button {
71+
+"Circle"
72+
onClickFunction = { onCircle() }
73+
}
74+
button {
75+
+"Clear"
76+
onClickFunction = { onClear() }
77+
}
78+
}
79+
div {
80+
id = "scene"
81+
}
82+
}
83+
scene.setSize(sw, sh)
84+
}
85+
86+
private fun animation(cls: String, size: Double, block: suspend CoroutineScope.(HTMLElement) -> Unit) {
87+
val elem = scene.append.div(cls)
88+
elem.setSize(size, size)
89+
val job = launch {
90+
block(elem)
91+
}
92+
animations += job
93+
job.invokeOnCompletion {
94+
animations -= job
95+
scene.removeChild(elem)
96+
}
97+
}
98+
99+
private fun onRect() {
100+
val index = ++animationIndex
101+
val speed = 0.3
102+
val rs = 20.0
103+
val turnAfter = 5000.0 // seconds
104+
animation("rect", rs) { rect ->
105+
println("Started new 'rect' coroutine #$index")
106+
val timer = AnimationTimer()
107+
var turnAt = timer.time + turnAfter
108+
var vx = speed
109+
var vy = speed
110+
var x = 0.0
111+
var y = 0.0
112+
while (true) {
113+
val dt = timer.await()
114+
x += vx * dt
115+
y += vy * dt
116+
val xRange = 0.0 .. sw - rs
117+
val yRange = 0.0 .. sh - rs
118+
if (x !in xRange ) {
119+
x = x.coerceIn(xRange)
120+
vx = -vx
121+
}
122+
if (y !in yRange) {
123+
y = y.coerceIn(yRange)
124+
vy = -vy
125+
}
126+
rect.setPosition(x, y)
127+
if (timer.time >= turnAt) {
128+
delay(1000) // pause a bit
129+
// flip direction
130+
val t = vx
131+
if (random() > 0.5) {
132+
vx = vy
133+
vy = -t
134+
} else {
135+
vx = -vy
136+
vy = t
137+
}
138+
// reset time
139+
turnAt = timer.reset() + turnAfter
140+
println("Delayed #$index for a while at ${timer.time}, resumed and turned")
141+
}
142+
}
143+
144+
}
145+
}
146+
147+
private fun onCircle() {
148+
val index = ++animationIndex
149+
val acceleration = 5e-4
150+
val initialRange = 0.7
151+
val maxSpeed = 0.4
152+
val initialSpeed = 0.1
153+
val radius = 20.0
154+
animation("circle", radius) { circle ->
155+
println("Started new 'circle' coroutine #$index")
156+
val timer = AnimationTimer()
157+
val initialAngle = random() * 2 * PI
158+
var vx = sin(initialAngle) * initialSpeed
159+
var vy = cos(initialAngle) * initialSpeed
160+
var x = (random() * initialRange + (1 - initialRange) / 2) * sw
161+
var y = (random() * initialRange + (1 - initialRange) / 2) * sh
162+
while (true) {
163+
val dt = timer.await()
164+
val dx = sw / 2 - x
165+
val dy = sh / 2 - y
166+
val dn = sqrt(dx * dx + dy * dy)
167+
vx += dx / dn * acceleration * dt
168+
vy += dy / dn * acceleration * dt
169+
val vn = sqrt(vx * vx + vy * vy)
170+
val trim = vn.coerceAtMost(maxSpeed)
171+
vx = vx / vn * trim
172+
vy = vy / vn * trim
173+
x += vx * dt
174+
y += vy * dt
175+
circle.setPosition(x, y)
176+
}
177+
}
178+
179+
}
180+
181+
private fun onClear() {
182+
animations.forEach { it.cancel() }
183+
}
184+
}
185+
186+
class AnimationTimer {
187+
var time = window.performance.now()
188+
189+
suspend fun await(): Double {
190+
val newTime = window.awaitAnimationFrame()
191+
val dt = newTime - time
192+
time = newTime
193+
return dt
194+
}
195+
196+
fun reset(): Double {
197+
time = window.performance.now()
198+
return time
199+
}
200+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// ------ Main bundle for example application ------
18+
19+
require("example-frontend-js");
20+
require("style.css");

0 commit comments

Comments
 (0)