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.jsjanvladimirmostert
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”