Ignoring methods on certain platforms

I’ve got a multiplatform project. I have a big, complex class hierarchy in shared code.

I know, statically, that certain methods will only ever be used on the server.

class MuchMoreComplexInRealLife : AnotherComplexClass {
  override fun usedEverywhere() {...}
  override fun usedOnServer() {...}
}

However, the Javascript DCE can’t figure this out, and so all the server code ends up being compiled into Javascript and shipped with the client.

This isn’t a big problem, but it is a waste. Is there a way to persuade the Javascript compiler to simply ignore these methods? The code’s not amenable to refactoring into server-only and shared because I want to retain the single class hierarchy.

I was expecting to find a set of annotations for doing this, to allow this sort of thing:

class MuchMoreComplexInRealLife : AnotherComplexClass {
  override fun usedEverywhere() {...}
  @JvmOnly override fun usedOnServer() {...}
}

Naturally I’d need to annotate usedOnServer() all the way through the class hierarchy, but that’s not a bad thing. But I haven’t been able to find any way of doing this.

You can try using another tree-shaking utility. RollupJS might be able to do it.

I’m already using uglifyjs for creating my bundle, which should already be dropping dead code (although I now note that the docs don’t show a default for the dead_code option, so it may not actually be enabled.

But can DCE/tree shaking actually solve this? Javascript methods can be referred to dynamically, so there’s no way for a pure-Javascript tool to be able to tell whether a given method is in use or not. Doesn’t this need a Kotlin-level tool?

At one point in the future you will probably be able to declare only a few functions of a class as expect and give them different implementations of different platforms. So you could then for example implement the jvm-only function as an empty stub on js.
Also this would probably allow you to maybe even remove them from your common module completely and only have them in the jvm/js module.
The issue about this is here: https://youtrack.jetbrains.com/issue/KT-20427

Oh, yay. I’ve just discovered that kotlin-dce is incorrectly eliminating a class that my client was wanting, causing errors on startup, despite the logging saying that it’s seen the class and marked it as reachable. I’ve had to set -dev-mode to make my application work. My Javascript size has gone up by two megabytes.

Do I need to explicitly tell kotlin-dce that my main() is to be kept, or does it figure it out automatically?

On the down side, uglifyjs’s dead_code option was apparently enabled, so that won’t help…

With multi-platform projects, you can have the same hierarchy with platform-specific methods. See this class for an example:

That’s not really an option for me, though, as I end up having to duplicate my class hierarchy three times. (As well as spreading logic through multiple different directory hierarchies.)

I’m looking for a solution which allows me to put the client code and the server code for each logical unit next to each other in the same file, where they belong.