janvladimirmostert
07/09/2020, 7:11 PMmoduleB: 1.0.0
under dependencies instead of a relative / absolute path like the other Kotlin std lib dependencies
is there a way to automatically include the generated JS from moduleB into moduleA or at least have that package installed somewhere? In the maven world, you would run mvn install
, does the node ecosystem have something similar ?turansky
07/09/2020, 7:20 PMjanvladimirmostert
07/09/2020, 7:25 PMkotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
}
js(IR) {
browser {
binaries.executable()
webpackTask {
output.libraryTarget = UMD
}
}
}
janvladimirmostert
07/09/2020, 7:26 PMturansky
07/09/2020, 7:28 PMjs
with all dependencies inside in distribution
folderjanvladimirmostert
07/09/2020, 7:29 PMjs(IR) {
browser {
binaries.executable()
dceTask {
}
turansky
07/09/2020, 7:30 PMturansky
07/09/2020, 7:32 PMMain
from app
subprojectturansky
07/09/2020, 7:32 PMKT-TEST-app
- JS subproject idjanvladimirmostert
07/09/2020, 7:33 PM{
"main": "kotlin/rez-property-rez-account.js",
"devDependencies": {
"webpack": "4.42.1",
"webpack-cli": "3.3.11",
"source-map-loader": "1.0.0",
"dukat": "0.5.4"
},
"dependencies": {
"rez-property-rez-util": "1.0.0", <<<--
"kotlin": "file:/home/vlad/Code/rez-property/build/js/packages_imported/kotlin/1.4.0-M3",
"kotlin-test-js-runner": "file:/home/vlad/Code/rez-property/build/js/packages_imported/kotlin-test-js-runner/1.4.0-M3",
"kotlin-test": "file:/home/vlad/Code/rez-property/build/js/packages_imported/kotlin-test/1.4.0-M3"
},
janvladimirmostert
07/09/2020, 7:33 PMjanvladimirmostert
07/09/2020, 7:34 PMturansky
07/09/2020, 7:37 PMdistributions
you can receive independent js
(default behaiour)turansky
07/09/2020, 7:38 PMwill that get rid of this dependency as well?Default expectation - yes
turansky
07/09/2020, 7:40 PMjanvladimirmostert
07/09/2020, 7:43 PMjanvladimirmostert
07/09/2020, 7:46 PMjanvladimirmostert
07/09/2020, 7:55 PMjanvladimirmostert
07/09/2020, 8:00 PMturansky
07/09/2020, 8:02 PMi think the @JsExport i’m using automatically includes everything in the JS output for each moduleYes, DCE not required in this case
Ilya Goncharov [JB]
07/09/2020, 8:03 PMjanvladimirmostert
07/09/2020, 8:05 PMgradle publicPackageJson
, will it be installed in a central place somewhere that my external project can then access ?janvladimirmostert
07/09/2020, 8:20 PMjanvladimirmostert
07/09/2020, 8:43 PMIlya Goncharov [JB]
07/09/2020, 8:51 PMbuild/js/packages/module-name/kotlin/module-name.js
janvladimirmostert
07/09/2020, 9:02 PMpublicPackageJson
meant to be jsPublicPackageJson
?
if i run gradle clean build jsPublicPackageJson
i'm not seeing a jsPublicPackageJson in my root build directory
there is however one in the module's own build directory, but it's emptyIlya Goncharov [JB]
07/09/2020, 9:07 PMskipOnEmptyNpmDependencies
on false
on configuration of this taskIlya Goncharov [JB]
07/09/2020, 9:08 PMjanvladimirmostert
07/09/2020, 9:09 PMjanvladimirmostert
07/09/2020, 9:23 PMval jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
implementation(npm("lit-html", "^1.2.1"))
}
}
{
"main": "kotlin/rez-property-rez-account.js",
"devDependencies": {},
"dependencies": {
"lit-html": "^1.2.1"
},
"peerDependencies": {},
"optionalDependencies": {},
"bundledDependencies": [],
"name": "rez-property-rez-account",
"version": "1.0.0"
}
how do i access this dependency from the external project, which directory should i be npm install
ing for it to be accessible in the other project?Ilya Goncharov [JB]
07/09/2020, 10:13 PMbuild/js/packages/module-name/kotlin
into kotlin
subfolder of this “some” folder
And then you can add dependency on this library with version file:path-to-folder
But it requires to do reinstall (npm install
) after every recompilation of Kotlin
You can adopt your package for usage with Yarn workspaces for example
https://classic.yarnpkg.com/en/docs/workspaces/janvladimirmostert
07/10/2020, 6:49 AMjanvladimirmostert
07/10/2020, 10:32 PMIlya Goncharov [JB]
07/11/2020, 7:26 AMjanvladimirmostert
07/11/2020, 8:06 AMapplications
+-- jvm-web-via-ktor-application-1
+-- jvm-web-via-ktor-application-2
+-- android-application
+-- ios-application
+-- desktop-application
modules
+-- jvm-module-1
+-- multi-platform-module-1
+-- multi-platform-module-2
integrations
+-- jvm-integration-1
+-- jvm-integration-2
so one of the modules are named utils and contains extension functions, helper functions, and my change detection state, all of them are inside the common module
the state is effectively a Delegate, the built-in delegates didn't work for what i wanted to do, so i wrote my own
inline fun <T> Delegates.calculatable(
crossinline getValue: () -> T,
crossinline setValue: (value: T) -> Unit = {},
crossinline validate: (Change<T>) -> Boolean = { true },
crossinline onChange: (Change<T>) -> Unit = { },
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return getValue()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val oldValue = getValue()
if (validate(Change(name = property.name, oldValue = oldValue, newValue = value))) {
if (oldValue != value) {
setValue(value)
onChange(Change(name = property.name, oldValue = oldValue, newValue = value))
}
}
}
}
Another module for example would contain application states (for single page apps, it would be a gigantic single state, for applications that are using server-side rendering, each page would have its own state declared inside this module
So this would for example be state for a login page:janvladimirmostert
07/11/2020, 8:06 AM@JsExport
@ExperimentalJsExport
class LoginState {
var changed: ((field: String) -> Unit) = {}
var _email: String = ""
var email: String by Delegates.calculatable(
getValue = { _email },
setValue = { _email = it },
validate = {
// TODO: validate email here
return@calculatable true
},
onChange = {
// TODO: update internal state
changed(it.name)
}
)
var _password: String = ""
var password: String by Delegates.calculatable(
getValue = { _password },
setValue = { _password = it},
validate = {
// TODO: validate password here
return@calculatable true
},
onChange = {
// TODO: update internal state
changed(it.name)
}
)
}
this state can now be re-used for all applications that has a login page and the business logic around when to enable the login button is all captured into this state (this is a very simpliefied use-case, for a travel startup, i built a whole hotel and flight booking state using such delegates and i can actually unit test this state to make sure that the page is in the right state when values change)
So inside the ktor-jvm application, there's a web folder which contains a typescript project, web/src/login.html, web/src/login.ts, and all its images, css, etc. The package.json contains dependency, something like rez-util: ../../../../../../rez-property-rez-util
The web project is built by Parcel which generates login.12324.js, login.12324.css, etc
My gradle build script now copies these over from web/dist/ to src/main/resources but strips out the number.
My Ktor application now either embeds these packed files if they're small enough or links to them via a static route
On the TS side, i'm doing something like this, login.ts
let state = new account.state.LoginState()
let page = () => render(
// language=HTML
html`
pull in other lit-html components here using lit-html ${} syntax
`
, document.querySelector("<http://div.app|div.app>"))
state.changed = function (reason: String) {
console.log(`Change detected: ${reason}`)
page()
}
page()
so now every time the Kotlin delegate's state changes, that page function is called and lit-html is re-rendering only the values that have changed
it's lightning fast, the number of DOM changes are literally just the values that have changed and since i'm controlling the state, i can optimise it so that it only triggers a change when it needs to
Serializing that state is not currently supported, i logged a bug for it
https://youtrack.jetbrains.com/issue/KT-39740
, so for now i have a .json getter / setter inside the state which copies the state variables over to a LoginReq / LoginRes annotated with Serializable instead of just directly serializing to and from a map. This unfortunately bloats the generated JS since i now have LoginReq and LoginRes inside my generated JS instead of just the state itselfjanvladimirmostert
07/11/2020, 8:17 AMimport * as rezutil from "rez-property-rez-util/kotlin/rez-property-rez-util"
import util = rezutil.com.rezproperty.modules.util
import * as rezaccount from "rez-property-rez-account/kotlin/rez-property-rez-account"
import account = rezaccount.com.rezproperty.modules.account
would have been nice just just do import { account } from "rez-property-rez-util"
i'm not sure how the TypeScript for the generated module is supposed to look like, but the current generated TypeScript doesn't work at all, so i had to write a gradle.buildFinished process in order to post-process the generated TS header that's generated by the IR mode
See my comment on https://youtrack.jetbrains.com/issue/KT-37883Ilya Goncharov [JB]
07/11/2020, 10:27 AMIlya Goncharov [JB]
07/11/2020, 10:30 AMclass A
t can be used from js
import { A } from “module”