is a way to remove the 2mb size from the Kotlin Re...
# javascript
s
is a way to remove the 2mb size from the Kotlin Requirement? Iโ€™m publishing a NPM lib and it seems to be dependant on
require("kotlin")
s
You could use dead code elimination and package the stripped down kotlin library.
I have a super simple project where I tried this last night. Itโ€™s basically hello world in firebase, and I got the kotlinx.coroutines and the kotlin stdlib dependencies down to ~230 kb each. (this was using the 1.3.70 eap. In Kotlin 1.4 they promise that this is MUCH better. My expectation is that in 1.4 with my current example, the stdlib would be only a few kb.
s
Is there any chance you could share the gradle script? I have enabled DCE but not the
require(kotlin)
in the gradle dependency. If I add that I might strip everything and only bundle what is needed. This is a MPP project BTW.
a
Sharing whole project would be nice if it is not for production :)
s
+1
The error I get when I publish an NPM module and then run it in a project is this
Copy code
Module not found: Can't resolve 'kotlin' in '/Users/seankeane/Development/test-dce-js/test-dce-js/node_modules/dce-test'
e
Is DCE available at all for the nodejs target though? I'm using
1.3.70-eap-184
, and it seems to only be available for the browser target. Granted, I'm not sure how it would work, since building a nodejs target doesn't actually seem to include the kotlin js library in the produced build, minified or not. ๐Ÿค”
I do get a dce-d
kotlin.js
in
build/js/packages/my-package/kotlin-dce/kotlin.js
, if I add a browser target with dce (eg.
browser { dceTask { ... } }
), which is significantly lighter. I could probably just bundle that with my npm module to make this work.
s
This sounds like what I need to do. Iโ€™ll try and get it setup on my side and get it working. Ill report back either way. Thank you ๐Ÿ™‚
๐Ÿ‘ 1
So I am in a weird situation. I don't get the same compiled
Kotlin.js
. But your approach sounds exactly like what I need. Could you look over this and see if I am missing anything?
Copy code
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() {
        browser() {
            @Suppress("EXPERIMENTAL_API_USAGE")
            dceTask {}
        }
    }

    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("org.jetbrains.kotlin:kotlin-stdlib-js:${Versions.kotlin}")
        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")
}
e
This is my test project: https://gitlab.com/fleskesvor/tabletop-enums I pushed my dce additions on master, so a
clean build
should produce (what looks like) a dce-d
kotlin.js
in
build/js/packages/tabletop-enums/kotlin-dce/kotlin.js
(at 109k without adding anything to
dceTask { keep() } }
). I don't actually utilize it yet (and I also removed the kotlin dependency in my
src/jsMain/resources/package.json
, so the generated code doesn't actually work at the moment.
I'm thinking that to make this work until it's supported properly, I will make a gradle task that depends on browser
processDceJsKotlinJs
and nodejs
jsJar
, which unzips the jar made by the
nodejs
task into
$buildDir
and copies the dce-d
kotlin.js
into it. That way, it's easy to
clean
, and easy to test locally with
npm link
. Something like this perhaps:
Copy code
tasks {
    register("buildNpm") {
        doLast {
            copy {
                from(zipTree("$buildDir/libs/${rootProject.name}-js-$version.jar"))
                into("$buildDir/npm")
            }
            copy {
                from("$buildDir/js/packages/${rootProject.name}/kotlin-dce/kotlin.js")
                into("$buildDir/npm")
            }
        }
    }
    named("buildNpm") {
        dependsOn("jsJar", "processDceJsKotlinJs")
    }
    named("build") {
        dependsOn("buildNpm")
    }
}
s
Thank you for this. I do have the DCE'd code working. But I spent all day yesterday dealing with a "requires kotlin" issue. I will try today to get this to work. I was generating the package.json file but it just wasn't able to access the kotlin DCE code. Even when I created the package.json in the DCE folder with the files. So you think bundleDependencies would help in the package.json?
e
That might help, but I'm not familiar with it. I tested with
@zeit/ncc
a couple of days ago, and that seemed to work with kotlin as a dependency (but of course still 2MB large). I guess I'd probably just have to include
const kotlin = require("./kotlin.js");
in
index.js
for it to work with generated kotlin DCE.
I was also thinking of solving this with https://rollupjs.org, but I'm not terribly familiar with that either, and it seemed to require a bit more configuration than kotlin DCE, which kind of just works, except it's not intended for this use-case yet.
s
I'll see if I can get the DCE code bundled. I wonder is there a way to import the kotlin.js module within the kotlin code so it will be generated with that dependant. At the moment I only have a single hello world variable. Still requires kotlin to work. Also, the DCE code for the application doesn't work. It doesn't have the object setters and getters included. This means it can't see the var from the lib. Might be one that needs to be optimised. I've tried few browser projects but they just work. Can't see what the difference is, I was looking to just bundle the browser with dependents. But no luck their either. I did see one article where they edit the generated js manually to include kotlin.
e
Hmm. I don't know what might be causing that. Maybe it's because nothing in your code accesses those getters/setters? You could try to add your objects to
dceTask { keep(...) }
to see if that makes any difference. I'm just guessing though. It would be nice to hear from someone with greater knowledge of how DCE works. I think there's syntax to import external libraries into kotlin code, but even if you use that on kotlin.js, I guess that would be dead code, and would probably be removed? EDIT: I was thinking of `@JsModule`: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-js-module/index.html
s
This is what I have experienced. Without DCE this var is accessible through this code.
Copy code
(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;
  Object.defineProperty(_, 'hello', {
    get: function () {
      return hello;
    },
    set: function (value) {
      hello = value;
    }
  });
  hello = 'HELLO WORLD';
  Kotlin.defineModule('common', _);
  return _;
}));
When DCE is applied it then becomes this, the variable hello is no longer able to be accessed through JS.
Copy code
(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';
}));
I think there's syntax to import external libraries into kotlin code, but even if you use that on kotlin.js, I guess that would be dead code, and would probably be removed?
EDIT: I was thinking of `@JsModule`: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-js-module/index.htmlย (edited)
I was thinking of this also. Even if it just pulled the require(kotlin.js). Then when it comes to bundling that it should use the DCE'd version that we have but it would naturally pull the remote version. My JS understanding is sketchy but im making the break from the mobile world. So bear with me. ๐Ÿ™‚ I'm looking to create a shared lib across all 3 platforms, this is the last hurdle for me to build a POC
e
Oh, but that's the [project-name].js from build/.../kotlin-dce/, right? I think you should still use [project-name].js from `build/libs/project-name-js-*.jar, but with only
kotlin.js
from build/.../kotlin-dce.
s
That part I can now do (Thanks to your script ๐Ÿ˜‰ ) but it still has the require(kotlin.js) dep issue when I run it. I feel like I am very close with this but its not giving in either.
e
No worries. This is all a bit new to me too. I'm looking to use this to replace some shared libraries for work, which are kind of awkward to maintain and publish at the moment.
s
You and me both. Im going to try your task now and see. Ill do the build after and publish it. Hopefully it gets us closer. I feel like solving this will be a game changer for both of us ๐Ÿ˜‚
e
Ah, so it evaluates
typeof exports === 'object'
to
true
, and then tries to run
require('kotlin')
, which is not your local kotlin DCE, but if that evaluation ran through to that
else
, it would try to use the
kotlin
variable that you already declared in your
index.js
, I think.
Yeah, definitely, so I appreciate you working with me on this, even though we're both newbies at this. ๐Ÿ™‚
๐Ÿ’ช 1
s
I can try removing it from the code and building it and check if that works
I'm quite new to the JS world and seem to have ended up in one of its hardest challenges ๐Ÿ˜‚ We will get to the end of this at some stage. Kind of tempted to pull the 1.4.0 dev branch and build with that
e
So, I think what that evaluation is doing is to check whether it's being called from a es6, commonJS or amd context, so you have to kind of stage your testing environment accordingly. If you looked in my repo, I have another branch with some test scripts to test the built code, which uses a standalone
.mjs
script and
node --experimental-modules
to test imports.
Haha, yeah. ๐Ÿ™‚ I'm really hoping this will bear fruits after all the effort invested into creating workarounds while waiting for
1.4.x
, which will hopefully solve this use-case properly.
s
Looking through your code I think I see what needs to be done on my side. I will create an index.js and put the require(kotlin) there. Then I might be able to build with that dependant without issue. I will also see if I can get the dependant that it uses to be the dce'd kotlin version.
What really got me thinking about building out this project for all dependants was the talk that was made by @Sebastian Aigner at Kotlin Conf. That was the one talk that gave me faith in making this work. I think it should be possible but Sebastian, If you see this thread. Please save us ๐Ÿ˜‚

https://www.youtube.com/watch?v=L4DRD9eWKXoโ–พ

in case you havent seen it.
๐Ÿ‘ 1
e
Yeah, I mean it already kind of works if you just add
kotlin
to the dependencies in
package.json
, but it kind of sucks that it makes it a 2MB dependency in a Javascript project that otherwise isn't using kotlin.
s
Do you know if its possible to point the dependency to the bundled kotlin version?
e
I also tried to rewrite
require('kotlin')
to `require('./kotlin.js')`in the generated code, but that throws a new error
TypeError: Kotlin.defineModule is not a function
, so that is apparently removed by DCE. Might work to add it to
keep
.
โ˜๏ธ That's a way, but I'm not sure how to configure/automate that. I just edited my
build/npm/tabletop-enums.js
manually.
s
what do I need to add to keep? If I can figure out the manual steps I dont mind going through the hardship of automating it for us
e
Just:
Copy code
dceTask {
    keep("kotlin.defineModule")
}
That actually works for me in my test branch now, except that I have to manually change
kotlin
to
./kotlin.js
on line 5 in
build/npm/tabletop-enums.js
. I can push my changes, so that you can take a look, if you want.
s
Please!! That would be excellent. So this uses the kotlin DCE lib to build? what is your sizing like?
e
It's 109K.
s
Wow. I think I would be delighted if I can get it to that. Ill look into it now
e
https://gitlab.com/fleskesvor/tabletop-enums/-/tree/test-scripts - You just run
./link.sh
to build and npm link the project, then edit the aforementioned file, then run
./test.sh
to test it.
s
Ill report back in a few
e
I think you need node v12+ to run the test script, by the way.
๐Ÿ‘ 1
I think this might solve the issue of linking to a local `kotlin.js`: https://docs.npmjs.com/files/package.json#local-paths Might have to put another
package.json
in a sub-directory of
src/jsMain/resources
for that to work though, as I don't think you can point directly at the js-file.
s
ok, feel like we are getting closer to something working. I am just building out your repo now with the test and going to apply that to my own and see if the same applies
I managed to make mine work! 53kb. Thats a bit better. Will be interesting to see the size after all the other libs get bundled. Thank you for this. I need to now get it to work properly without all the manual changes
So I wanted to automate it more. This might help you. I created a filter that goes over the files off the back of your script. It replaces all the require(kotlin) with .js
Copy code
tasks {
    register("buildNpm") {
        doLast {
            copy {
                from(zipTree("$buildDir/libs/YOUR_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")
    }
}
๐Ÿ‘ 1
e
Oh, nice! I hadn't thought of that. ๐Ÿ™‚ 53K sounds pretty good. I suspect most of the size on mine comes from pulling in all the enum stuff. Haven't had time to look more at this stuff, but I will tonight.
s
Im going to check out how it will size with a network lib. Ill let you know how I get on
e
Thanks for keeping me posted. I just tested with your line replacement fix, and I can confirm that it works nicely, without any manual steps. ๐Ÿ™‚
I updated my branches with a new
publishToNpmLocal
, which is sort of an equivalent to the `publishToMavenLocal`task available from the
maven-publish
plugin. Might be a good idea to add a
publishToLocal
task to trigger both.
s
I was looking for an equivalent to publishToMavenLocal. This is perfect. I'll have a look later on. I will update the script to create the package and publish it automatically on the build step ๐Ÿ™‚
e
Yeah, it's not quite as persistent as
publishToMavenLocal
though, since it will be invalidated on a
gradle clean
, but maybe that's not a big deal. ๐Ÿค”
s
I don't see that as being a huge issue. But having it published on each build is a massive time saver. Does it overwrite on consecutive builds?
e
Well, it's only a symbolic link, so as long as it's not unlinked with
npm unlink
or by
rm
-ing the link, you only really need to execute
buildNpm
on consecutive builds to see your changes immediately in projects that use that link.
Maybe it would be a good idea to have it actually published to your local global npm cache though, so it can be used locally without having to use
npm link your-module
from other projects. More akin to the maven publish task. ๐Ÿค” I would have to read up on that though.
I think that's what this command does, but I haven't tried it before: https://docs.npmjs.com/cli/cache
s
I'll check it out later also. I'm out at the moment but I'll be back ๐Ÿ™‚
e
No problem. I'll be gone most of the day anyway. ๐Ÿ‘
s
Didnt get a chance yet to look at the cache but its on my list
e
I only looked at it briefly, but it didn't seem to do what I thought it would do.