i have two modules, moduleA and moduleB, moduleA d...
# javascript
j
i have two modules, moduleA and moduleB, moduleA depends on moduleB the resulting output's package.json now has
moduleB: 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 ?
t
Do you use DCE?
j
i've not set it up explicitly
Copy code
kotlin {

	jvm {
		compilations.all {
			kotlinOptions.jvmTarget = "11"
		}
	}

	js(IR) {
		browser {
			binaries.executable()
			webpackTask {
				output.libraryTarget =  UMD
			}
		}
	}
should i set it up ?
t
After DCE configuration you will receive
js
with all dependencies inside in
distribution
folder
j
how do i set that up, just a dceTask?
Copy code
js(IR) {
		browser {
			binaries.executable()
			dceTask {
				
			}
t
In example I keep
Main
from
app
subproject
KT-TEST-app
- JS subproject id
j
i'm using the generated JS in an external node project, will that get rid of this dependency as well?
Copy code
{
  "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"
  },
rez-util is being pulled in via Gradle
the external node project obviously can't find that dependency
t
In
distributions
you can receive independent
js
(default behaiour)
will that get rid of this dependency as well?
Default expectation - yes
This is default DCE configuration. Default DCE configuration = empty dist
j
my directory structure is as follow: applications modules +- rez-util +- rez-account package is com.blah.modules.util / account for example do i need to include every single class from rez-util in rez-account's dceTask's keep in order to get the whole thing included ?
i'm also using the new IR mode from Kotlin 1.4 M3, which only outputs the typescript headers and the js file, not sure if it's different to 1.3.72
i think the @JsExport i'm using automatically includes everything in the JS output for each module, the problem is not that the JS is missing, it's just the generated package.json is looking for a dependency rez-util: 1.0.0 instead of rez-util: file://...... so now the external node project is looking for it on npm.org or wherever node packages are typically pulled from
if i npm install the rez-account package which depends on rez-util, npm can't find rez-util even though it's already added to package.json in the external project
t
i think the @JsExport i’m using automatically includes everything in the JS output for each module
Yes, DCE not required in this case
i
Hi, external gradle dependencies presented as file dependencies, because they are not changed often, so you can install them, and they can live without changes With local project dependencies, situation is different, you can change modules often, and it should be presented, so for local project we use yarn workspaces, in package.json it is just a semver versions, but in root package.json there is field workspaces As for using of your project inside external js/ts application, we have task publicPackageJson for such purposes (it will be more common in future release), and you can check this https://github.com/ilgonmic/kotlin-ts/tree/master/library
🎉 2
j
so if i run,
gradle publicPackageJson
, will it be installed in a central place somewhere that my external project can then access ?
i had to read that several times before it made sense 😄 so there is a root/build/package.json which includes those two "workspaces", how do i get that inside my external TypeScript project? do i just point my package.json from the external TypeScript project to that build directory? let me try that
ok, so i think i get what's happening, running that command generates a root package.json which uses yarn workspaces, it generates a node_modules directory which has both my dependencies in it, albeit symlinked i guess now i need to figure out how to make my TypeScript project see that build/js/node_modules as a npm registry let me go Google that, my Node knowledge is limited
i
Hmm, information about workspaces was about initial question, why local projects have not file dependency Public package json is located in build/tml/publicPackageJson, and this package json is ready to use in external projects, you can copy it into necessary place But if you have monorepository with js project and Kotlin/JS, you can import by path from
build/js/packages/module-name/kotlin/module-name.js
j
is
publicPackageJson
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 empty
i
It likely means that tou have no npm dependencies, and in m3 it does not produce public package json (it will be changed in RC version) You can set
skipOnEmptyNpmDependencies
on
false
on configuration of this task
But pay attention, that this option will be removed in RC version, so don’t forget remove it when will migrate on RC version
j
yes, this is 1.4 M3 let me add my npm dependencies from the external project in my Kotlin project and see if it generates it would actually prefer managing them via build.gradle.kts than tinkering in an external package.json
i've added a dependency, now it's generating that jsPublicPackageJson directory with a package.json
Copy code
val jsMain by getting {
			dependencies {
				implementation(kotlin("stdlib-js"))
				implementation(npm("lit-html", "^1.2.1"))
			}
		}
Copy code
{
  "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?
i
You need to put this package.json in some folder, and javascript file from
build/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/
j
i'll need to experiment with this a bit, thanks for the help so far! Copying of those files i can probably automate in Gradle's build.finished
I've played around with this tonight, wrote some Gradle to copy package.json, js and ts files into root/build/library/subproject automatically and it works! Will there be a flag going forward to switch this on and automatically do the copying? This kinda setup i'm going to use going forward for all new web projects - effectively Kotlin Common code containing page state, models, business logic, validation, serialization, etc while TypeScript glues a lightweight framework like lit-html / polymer / etc into this Kotlin common code ... until i can port one of them to Kotlin
1
i
We will improving experience in this area, yes Especially based on use cases. We want to collect as much as possible cases how users want to mix js/ts projects with Kotlin to design best experience in most cases. Could you please share, do you have monorepo? And how do you organise layout of your project? Where is js project and where is Kotlin project? Is Kotlin project is subfolder in monorepo? @Sebastian Aigner jfyi about js/ts + Kotlin
K 1
j
So preferably, i would have preferred writing the whole thing in Kotlin, but porting something like lit-html turned out to be a huge pain, unless i rewrite it in Kotlin which is not in the scope for this project and React is overengineered for my use-case Generated JS size is also a concern since this is a consumer facing applications(s) and not a business app. Modern JS/TS frameworks uses backticks next to a function name which effectively splits it up into a list and search replaces ${} automatically sending those params into a vararg JS function (this is part of the ES6 spec i believe), in lit-html's case it's html`<div>${name}</div>` which will internally be split up into 3 parts, two of which are static and the middle part is dynamic, so when you call render, only the ${name} portion is updated in the DOM and not the whole DOM like React does. Dukat breaks this up into a list of strings and then interacting with it via Kotlin just doesn't work and even if could get it working, i'd had to jump through crazy hoops to write a wrapper around lit-html and search replace something like #{} to ${} since Kotlin is already interpreting those ${} Anyways, so to cut a long story short, it seemed simpler to just glue the lit-html and the KotlinJS together with TS. I've decided to go for a mono gradle repo to make my life easier, having multiple repos was a huge pain with Gradle, with a mono repo, i can just declare all the subprojects in my settings.gradle.kt and it just works. Reusing these modules between companies, i copy paste them for now since they might require different business logic for each state (business1's requirements for a valid email might be limited to their own domain whereas business2's requirements might be differnt, so copying and pasting these modules are not the end of the world) Project Structure for each startup/business i'm building
Copy code
applications
  +-- 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
Copy code
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:
Copy code
@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
Copy code
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 itself
The other pain point is the generated package names, so i would like to use the full com.company.module..... in JVM projects, maybe Android projects, but on the JS side, it's cumbersome writing these out by hand
Copy code
import * 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-37883
i
Thanks a lot! It is very useful! As for export names with package, it can be useful https://youtrack.jetbrains.com/issue/KT-37710 Seems that there is trick right now, you can to not use packages in Kotlin/JS in layer with exported declarations. And additional hierarchy with packages should not be generated in JS (but for d.ts files it should be fixed in issue with your comment)
👍 1
So for example if you write (Without package statement)
Copy code
class A
t can be used from js
Copy code
import { A } from “module”