Sean Keane
02/27/2020, 2:05 PMcommonLib.myTestMap.get("key")
But the method available to me is commonLib.myTestMap.get_11rb$("key")
Is there any way of having a standard getter for the keys in the map?anton.bannykh
02/28/2020, 9:39 AM@JsName
to set the desired name manually.
For example:
class MapProxy<K, V>(private val map: Map<K, V>) {
@JsName("get")
operator fun get(key: K): V? = map[key]
}
fun main() {
val map = MapProxy(mutableMapOf<String, String>("a" to "b"))
println(map["a"]) // JS: println(map.get('a'));
}
Sean Keane
02/28/2020, 9:40 AManton.bannykh
02/28/2020, 9:42 AM11rb$
)? Is there something more to this?Sean Keane
02/28/2020, 9:45 AMSean Keane
02/28/2020, 9:46 AManton.bannykh
02/28/2020, 9:47 AMSean Keane
02/28/2020, 9:50 AManton.bannykh
02/28/2020, 10:15 AM.d.ts
declarations important for you?
- Which ES standard do your web teams use?
- How big do you think the library will get?
- What layout do you have in mind for the library? (e.g. a single library, a bunch of small libraries, etc.)
- How much effort do your web teams put into slimming down the resulting JS artifacts?
- Do web teams need to use different parts of this common library? Will they need to be able to cut away the parts they don't use?
- Is there something missing that's needed for your use case?
Thanks!Sean Keane
02/28/2020, 11:05 AM- Does your company already use Kotlin? On Android maybe?
We are a 100% kotlin codebase on Android. We also have some Spring Servers written in Kotlin, Iโm looking to introduce Ktor in the coming months as a Server Framework.
- Do you plan to use the resulting JS library from TypeScript? Is providing .d.ts declarations important for you?
All our codebases are in JS at the moment. I have been put in charge of the architecture of the system. With that said, we are moving everything to TS. I dont want to see JS next year, im looking to achieve TS and Kotlin everywhere. The web team share a similar mindset.
- Which ES standard do your web teams use?
We build with ES6 and webpack bundles it for ES5
- How big do you think the library will get?
Currently, its all business logic, we need to make it as small as possible. But we want webpack to clean it up for us on the client side. So lib size is important but not the end of the world if its a little larger than normal. This should also shrink with Kotlin 1.4.0
- What layout do you have in mind for the library? (e.g. a single library, a bunch of small libraries, etc.)
We will have one large core lib but can consider pulling this out into smaller libs for features such as Analytics and Feature Flagging, this would help other teams, we can use these libs to create a larger core SDK for clients.
- How much effort do your web teams put into slimming down the resulting JS artifacts?
We dont currently have any JS SDKโs in the company but we are a platform company so its definitely needed. We treeshake with webpack as far as I know.
- Do web teams need to use different parts of this common library? Will they need to be able to cut away the parts they don't use?
There is about 4 web teams working on different projects, so cutting the parts they dont need can be beneficial over all to allow other teams use the features they need.
- Is there something missing that's needed for your use case?
Yes, there is no clean way of creating a NPM module to be used with JS. What I have found is that the minified version of Kotlin.js cannot be used with web, the variables are unable to be accessed without object functions. Ill post a snipped before and after.Sean Keane
02/28/2020, 11:05 AM(function (root, factory) {
if (typeof define === 'function' && define.amd)
define(['exports', 'kotlin'], factory);
else if (typeof exports === 'object')
factory(module.exports, require('kotlin'));
else {
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'common'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'common'.");
}root['common'] = factory(typeof this['common'] === 'undefined' ? {} : this['common'], kotlin);
}
}(this, function (_, Kotlin) {
'use strict';
var hello;
hello = 'HELLO WORLD';
}));
But if I bundle them with the unzipped jar version they can be accessed. This is the unzipped jar output.
(function (root, factory) {
if (typeof define === 'function' && define.amd)
define(['exports', 'kotlin'], factory);
else if (typeof exports === 'object')
factory(module.exports, require('kotlin'));
else {
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'common'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'common'.");
}root['Kcommon'] = factory(typeof this['common'] === 'undefined' ? {} : this['common'], kotlin);
}
}(this, function (_, Kotlin) {
'use strict';
var name;
Object.defineProperty(_, 'hello', {
get: function () {
return hello;
},
set: function (value) {
hello = value;
}
});
hello = 'HELLO WORLD';
Kotlin.defineModule('common', _);
return _;
}));
We had another issue with require(kotlin)
and had to write a script to bundle all the deps correctly. It was still pulling in the full kotlin lib in the client so I had to iterate over all the files and replace any require(kotlin)
with require(./kotlin.js)
. There was a lot of moving dependacies to create a working output. It would be great if this just happened for us. Or even guidance on how we should do this would help a lot. Here is the script.
tasks {
register("buildNpm") {
doLast {
copy {
from(zipTree("$buildDir/libs/common-js-$version.jar"))
eachFile {
filter { line ->
line.replace("require('kotlin')", "require('./kotlin.js')")
}
}
into("$buildDir/npm")
}
copy {
from("${rootProject.buildDir}/js/packages/common/kotlin-dce/kotlin.js")
into("$buildDir/npm")
}
}
}
named("buildNpm") {
dependsOn("jsJar", "processDceJsKotlinJs")
}
named("build") {
dependsOn("buildNpm")
}
}
As you can see this is not ideal. But I am a huge Kotlin avocate. and I really want this to work for us so any help at all would be greatly appreciated. I was also chatting to @Sebastian Aigner on this issue and raised the same concerns.anton.bannykh
02/28/2020, 1:18 PManton.bannykh
02/28/2020, 1:20 PManton.bannykh
02/28/2020, 1:21 PMIlya Goncharov [JB]
02/28/2020, 1:24 PMkotlin {
js {
browser {
...
}
}
}
Sean Keane
02/28/2020, 1:34 PMimport org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.multiplatform")
id("org.jetbrains.kotlin.native.cocoapods")
id("kotlin-dce-js")
}
version = Versions.cocoapodVersion
kotlin {
cocoapods {
summary = "Shared Code for Android and iOS"
homepage = "Link to a Kotlin/Native module homepage"
}
android()
js() {
targets {
browser {
// @Suppress("EXPERIMENTAL_API_USAGE")
// dceTask { keep("kotlin.defineModule") }
}
}
}
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
::iosArm64
else
::iosX64
iOSTarget("ios") {
compilations {
val main by getting {
kotlinOptions.freeCompilerArgs = listOf("-Xobjc-generics")
}
}
}
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Versions.coroutinesCore}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:${Versions.serializationCommon}")
implementation("io.ktor:ktor-client-core:${Versions.ktor}")
implementation("io.ktor:ktor-client-json:${Versions.ktor}")
implementation("io.ktor:ktor-client-serialization:${Versions.ktor}")
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}
sourceSets["iosMain"].dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.coroutinesCore}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:${Versions.serializationCommon}")
implementation("io.ktor:ktor-client-ios:${Versions.ktor}")
implementation("io.ktor:ktor-client-json-native:${Versions.ktor}")
implementation("io.ktor:ktor-client-serialization-native:${Versions.ktor}")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
}
sourceSets["jsMain"].dependencies {
implementation(kotlin("stdlib-js"))
}
}
android {
compileSdkVersion(App.compileSdk)
sourceSets {
getByName("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
java.srcDirs("src/androidMain/kotlin")
res.srcDirs("src/androidMain/res")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesCore}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.serializationCommon}")
implementation("io.ktor:ktor-client-android:${Versions.ktor}")
implementation("io.ktor:ktor-client-json-jvm:${Versions.ktor}")
implementation("io.ktor:ktor-client-serialization-jvm:${Versions.ktor}")
implementation("androidx.lifecycle:lifecycle-extensions:${Versions.lifecycleVersion}")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycleVersion}")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("androidx.core:core-ktx:+")
}
tasks {
register("buildNpm") {
doLast {
copy {
from(zipTree("$buildDir/libs/common-js-$version.jar"))
eachFile {
filter { line ->
line.replace("require('kotlin')", "require('./kotlin.js')")
}
}
into("$buildDir/npm")
}
copy {
from("${buildDir}/kotlin-js-min/js/main/kotlin.js")
into("$buildDir/npm")
}
}
}
named("buildNpm") {
dependsOn("jsJar")
}
named("build") {
dependsOn("buildNpm")
}
}
I had used DCE with the EAP version of kotlin 1.3.70 but I had issues with autocomplete on the other targets, switching back to 1.3.61 sorted this, but now the DCE task is not available.
This means my gradle task doesnt work because the DCE kotlin.js is ripping out defineModule
Ilya Goncharov [JB]
02/28/2020, 1:38 PMhello
you need to add it to keep
keep(<module-name>.<package>.hello)
where <module-name>
is file which is produced in build/js/packages
, usually is <root-project>-<project-name>
Sean Keane
02/28/2020, 1:39 PMEivind Nilsbakken
02/28/2020, 1:40 PMplugins {
id("org.jetbrains.kotlin.multiplatform") version "1.3.70-eap-274"
id("nebula.release") version "13.0.0"
}
It's as if js.browser.dceTask { keep(...) }
is simply not there anymore.Sean Keane
02/28/2020, 1:42 PM// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:${Versions.gradle}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}")
//classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:${Web.frontEndPlugin}")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
Eivind Nilsbakken
02/28/2020, 1:42 PMkeep
work again. Dce works either way, but kotlin.defineModule
is not kept.Sean Keane
02/28/2020, 1:43 PM// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
mavenCentral()
maven(url = "<https://dl.bintray.com/kotlin/kotlin-eap>")
}
dependencies {
classpath("com.android.tools.build:gradle:${Versions.gradle}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}")
//classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:${Web.frontEndPlugin}")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven(url = "<https://dl.bintray.com/kotlin/kotlin-eap>")
}
}
This was my EAP setupSean Keane
03/02/2020, 12:29 PManton.bannykh
03/02/2020, 12:31 PMIlya Goncharov [JB]
03/02/2020, 12:33 PMSean Keane
03/02/2020, 12:33 PM1.3.61
. But once set to use the beta 1.3.70-eap
. Changing this actually sorted the autocomplete issue when I was using AS with iOS.
I agree upon build of the gradle file from term it didnt complain.Eivind Nilsbakken
03/02/2020, 1:09 PMKotlin.defineModule
(under certain circumstances, in spite of keep("kotlin.defineModule")
), I ended up just hacking this into the task that assembles my npm module with bits and pieces from the nodejs
and browser
tasks:
copy {
from("$buildDir/js/packages/${rootProject.name}/kotlin-dce/kotlin.js") {
filter { it.replace("var _ = Kotlin;", "var _ = Kotlin; Kotlin.defineModule = function (id, declaration) {};") }
}
into("$buildDir/npm")
}
Hopefully not a long-time solution, but it works for me for now. I haven't figured out why keep
breaks with the nebula release plugin.Sean Keane
03/02/2020, 1:20 PMkotlin.defineModule
from being removed?Eivind Nilsbakken
03/02/2020, 1:31 PMbuild.gradle.kts
. No, it doesn't prevent it from being removed, but it adds it back in into the DCE-ed kotlin.js
. It's just an empty function in the working DCE-ed kotlin.js
too, so it might be better to just remove it from the generated project js, but this is less code, since kotlin.js
needs to be copied anyway.anton.bannykh
03/02/2020, 1:44 PMKotlin.defineModule
being removed? Are you coupling a DCE-ed kotlin.js
with a non-DCE-ed module?Eivind Nilsbakken
03/02/2020, 1:47 PMkotlin.js
from browser
for creating a nodejs
module which is to be used outside of a kotlin multiplatform project, and the non-DCE-ed kotlin
dependency is a hefty 2MB.Eivind Nilsbakken
03/02/2020, 1:48 PManton.bannykh
03/02/2020, 1:53 PMkotlin.js
?Eivind Nilsbakken
03/02/2020, 2:00 PMkotlin
in the non-DCE-ed result of the nodejs
target. I'd expect to be able to make it work by adding other random API-calls to keep
, but as I mentioned that broke when I added a dependency on the nebula release plugin, for reasons that remain a mystery to me. I'm not sure how to explain it better, but I have a demo project on GitLab: https://gitlab.com/fleskesvor/tabletop-enumsSean Keane
03/02/2020, 2:02 PMcommon.jar
The reason for this is as explained above. The variables from the DCE'd common code cannot be accessed from JS. But when bundled with Kotlin it becomes over 2mb in size. So by using the DCE version of Kotlin and the unzipped Jarg version of common it results in a lib which is a fraction of the size. As Eivind has said above, if there is a better way of doing this or even a web-pack sample for treeshaking on the client side, please let us know.anton.bannykh
03/02/2020, 2:19 PMkeep
feature didn't work for both of you, so you coupled the non-DCE'd module output with the DCE'd kotlin.js
as a workaround, didn't you? Interesting!
@Sean Keane does updating the plugin to 1.3.70-... fix this issue for you (i.e. let you use the keep
feature to prevent DCE from removing the exported `var`s) ?
@Eivind Nilsbakken is this "nebula release plugin" breaks the keep
feature issue present in the tabletop-enums
project?Sean Keane
03/02/2020, 2:23 PM1.3.70-eap
correctly and it has sorted the issue with autocomplete in iOS and dceTask { keep "" }
That is exactly what we did. The JS variables where not accessible so by using the non-DCE'd module with the DCE'd kotlin we could keep all dependencies within the one module with a large reduced size.Eivind Nilsbakken
03/02/2020, 2:32 PMmaster
. If you comment it out from build.gradle.kts
, you will get a working build in build/npm
, which you can npm link
with other projects.anton.bannykh
03/02/2020, 2:35 PMIlya Goncharov [JB]
03/02/2020, 2:59 PMnpm
folder not from kotlin-dce
like kotlin.js
but from .jar
It seems, that you can copy from kotlin-dce
and everything should workanton.bannykh
03/02/2020, 3:08 PMkeep
doesn't work, and as a result all exports are removed.Eivind Nilsbakken
03/02/2020, 3:08 PMnodejs
to be made for my use-case, realized it wasn't, and ended up adding more and more hacks. I'll have to check, but I don't think kotlin-dce
includes any package.json
at least. Not even the one from my resource
directory.Sean Keane
03/02/2020, 3:15 PMCould not determine the dependencies of task ':kotlinNpmInstall'.
> org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager$ResolutionState$Installed cannot be cast to org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager$ResolutionState$Configuring
this is the gradle dependencies for JS.
sourceSets["jsMain"].dependencies {
implementation(kotlin("stdlib-js"))
implementation("io.ktor:ktor-client-js:${Versions.ktor}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.coroutinesCore}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:${Versions.serializationCommon}")
}
I've tried bundling them but there is a lot of issues here.Sean Keane
03/02/2020, 3:18 PMSean Keane
03/02/2020, 4:41 PMnpm update
consistently fails to resolve. Fixing the NPM issue is the only way I can see this moving forward, allowing webpack
to do all the treeshaking on the client side to reduce the size. But this means the libs need to be npm available? unless I'm missing something?Eivind Nilsbakken
03/02/2020, 8:13 PM.jar
with the contents of kotlin-dce
. This doesn't take me all the way, but as you suspected, at least I don't need to add keep('kotlin.defineModule')
to my dce config. I do however have to explicitly keep
my own package (keep("<http://tabletop-enums.com|tabletop-enums.com>.fleskesvor.tabletop")
. Otherwise, my package hierarchy is declared within the generated module, but never populated and never exported, ie. like this:
var Enum = Kotlin.kotlin.Enum;
var Kind_CLASS = Kotlin.Kind.CLASS;
var throwISE = Kotlin.throwISE;
var Kind_OBJECT = Kotlin.Kind.OBJECT;
var Type$BOARDGAME_instance;
var Type$CARDS_instance;
var Type$DICE_instance;
var Suit$CLUBS_instance;
var Suit$DIAMONDS_instance;
var Suit$HEARTS_instance;
var Suit$SPADES_instance;
var Platform_instance = null;
As before, enabling the nebula.release
plugin has the effect of breaking the configuration of keep
, which is an effect that is still present on my new HEAD
of master
. (So one must comment out that plugin, ./gradlew build
and set a valid version in build/npm/package.json
to be able to npm link
my test project.)
Additionally, kotlin-dce
does not contain a package.json
out of the box, nor is src/jsMain/resources/package.json
copied over, so I have to do that in a custom task. I also still need that copy task for src/jsMain/resources/index.js
since I don't want my consumers to have to "unwrap" my exported module.
Another interesting find is that even the generated module in kotlin-dce
doesn't actually use the DCE-ed kotlin.js
, so I still need Sean's hack of replacing occurences of require('kotlin')
with require('kotlin.js')
. This can easily be verified by testing the module with npm link
with that replacement commented out, in which case any use of it will fail with a MODULE_NOT_FOUND
, since no dependency on kotlin
is added to my package.json
. Adding that dependency will pull in the full kotlin
dependency, which will result in a full weight of 2.0M of the full module, vs. only 115K with the DCE-ed kotlin.js
(sizes of a bundle with @zeit/ncc
).
By the way, there seems to be an inconsistency/error in the DCE documentation on https://kotlinlang.org/docs/reference/javascript-dce.html:
The described syntax moduleName.dot.separated.package.name.declarationName
works (though declarationName
doesn't seem to be necessary, if you want to keep everything from a package), but the example kotlin-js-example_<http://main.org|main.org>.jetbrains.kotlin.examples.toKeep
has an underscore in place of the dot to separate module and package name, which doesn't seem to work for me, unless I'm misunderstanding something. ๐คIlya Goncharov [JB]
03/02/2020, 8:49 PMkeep
declarations
But personally I do not recommend provide public JavaScript API of your library with Kotlin-specific types like Enums, and etc, because it is not JavaScript way, better to use JavaScript native types like arrays, primitives objects and so on.
Because if user of your library will use Enum
from your library, and will use other Kotlin/JS based library with Enum too, these two enums will be different enums in JavaScript virtual machine, which can lead to problem in runtime (with instanceof
for example)Eivind Nilsbakken
03/02/2020, 9:15 PMindex.js
to recurse over the generated object to be able to export a similar object with native types instead, which is a solution I'm leaning towards. I'd prefer if I could somehow do this in Kotlin, but I haven't found a good way to do this, other than expect
/`actual` and a bunch of duplication + transformation throughout the specific platform sources.
The dce is from the browser
version, by the way, since there's no dce in nodejs
.
EDIT: This seems almost exactly like what I'm hoping to achieve with my test use-case: https://kotlinlang.slack.com/archives/C0B8L3U69/p1533092140000170 Though it doesn't seem like inline enum class
is supported yet, as of 1.3.70-eap-184.Sean Keane
03/03/2020, 11:46 AMIn one of my POCs I use myIf you succeed in exposing the JS object from a Kotlin codebase let me know ๐to recurse over the generated object to be able to export a similar object with native types instead, which is a solution I'm leaning towardsindex.js
Eivind Nilsbakken
03/03/2020, 12:27 PMvar te = require("tabletop-enums")
console.log(te.enums.type.BOARDGAME);
console.log(te.enums.card.suit.HEARTS);
I have the recursion in index.js
stashed on a different computer, but it's pretty basic stuff.