Is it possible to configure KotlinJS to compile to...
# javascript
a
Is it possible to configure KotlinJS to compile to multiple js files instead of one big one?
👌 1
🚫 1
t
Webpack plugin can help with this 🙂
j
The argument has been you're either shipping to the browser in which case you want a single file that's been webpack minified to all hell or you're on the server where it shouldn't matter
So what's your use case? For browser extensions where you want multiple JS files to use in different contexts I've just used multiple modules.
f
@jw if I understand it correctly you guys are already working on it since the Spaces team has the same requirement. Its very common that when you target the browser that you don't compile everything into one big (minified file). That works well for hello world applications or a simple TODO app but as soon as you build a bigger web application you want your individual features/pages split into separate files so a user that never opens the about me website does not have to download all the JS code associated with that page. But at the same time you often have a lot of shared code like layout components etc that creating 50+ sub modules is not really a good way to manage a big web application ether.
j
Who is you guys? I'm a consumer! But anyway that is a JS ecosystem problem and not a Kotlin/JS problem. That should be solved by JS tooling, not Kotlin/JS tooling. Using multiple modules and webpack to DCE or webpack to produce multiple isolated DCE'd files is what i've been doing and has worked fine. If you want to pull out a shared application-specific common library then that's a webpack or webpack plugin feature request.
n
I think Kotlin JS needs to have its own Webpack replacement which is designed with Kotlin in mind. Although more of the Kotlin JS build process is controlled by the Kotlin side (in the more recent versions of Kotlin JS) there are still some things controlled by the JS side. Would make sense to have Kotlin control the entire build process.
j
Eh, maybe. Hopefully the forthcoming ES6 support will further aid in the ability for webpack to do its thing. In general, bespoke tooling rarely is a good idea. And while JS is an ecosystem that tolerates tool churn, there's also no shortage of existing tooling. The JetBrains team already seems to be overcommited 500% on Kotlin. Widening their scope is only going to slow everything down.
☝🏻 1
f
Oh sorry I thought the JW stand for some jetbrains team. What KotlinJS would need to provide is a good system how to define split points
so that the compiler can automatically try to put as much unique code as possible into splitted files and auto loads them when required. Webpack for example uses https://webpack.js.org/guides/code-splitting/ this. Which is very interesting and in some of my bigger projects I made heavy use of the dynamic imports which would be a great addition if kotlinjs can provide something like that (or at least similar to that)
j
(Yeah i'm just trolling the [JB] and [G] people 🤪)
🧌 7
i
You can use code splitting of webpack, because you can add any additional config to webpack. You can extract all third party to separate chunk for example.
f
@Ilya Goncharov [JB] Yeah but the second article is the code splitting I would like to see more. Specifying split points in the code itself and then the compiler figures out how to best create smaller JS files around that (moving common stuff into more common JS modules etc)
1
a
@fkrauthan could you tell me more about the split points? How do you see this implemented in Kotlin/JS? The way I see this is that declarations only referenced in suspend functions could easily be moved to a dynamically imported module. But in ordinary functions certain Kotlin expression require its declaration module to be loaded (e.g. top level function invocation, static class instantiation). Such reference means it is impossible to move the declaration to another module and load it lazily.
So in order to call a top level function from a dynamically loaded module in a non-suspend function the user basically has to specify a fallback action. Something like "Run this lambda only if all the necessary modules are loaded. Otherwise do this". That's because we cannot stop and wait until the module is loaded. Unlike suspend functions, where the compiler could just suspend the coroutine until the modules are loaded.
That's the essence of the proposal I've been talking before. Some concrete details are not ironed out yet - like in suspend functions should we load modules first, or do that on demand? In ordinary functions how should those special runners be called? Should they trigger module loading? etc. And yes, it is possible to do some optimizations automatically. But I reckon that unless certain code is marked as "should be loaded asynchronously", it is very easy to prevent that from happening by accident.
f
@anton.bannykh That sounds like a good approach. To be honest I have no idea how to elegant implement it in Kotlin. But an approach that loads async and allows me to run code until something is loaded vs when its finished loading and in case I use coroutines allow me to suspend till the module is loaded sounds good. Where it becomes harder is if the compiler can figure out that the async loaded chunk of code can be all moved into a separate module or if you still need to manual define the chunk’s itself. (obviously having the compiler figure out common code vs more unique code and move them automatic in chunks based on split points and potential other logic would be the best)
Maybe it can work with some file level annotation to mark a file to generate a new chunk and then somehow have some function construct to load that file and a definition from that file async (as you described above)
a
Great minds think alike - indeed making possible to mark a smaller chunk of code as a dynamically loaded entity is part of the proposal. Both for a file and for a package. I personally think that the file use case is more useful, because it should allow to load an existing JS library asynchrously without the need to create a separate module.
As for having to manually define the chunk itself - I don't see a way around right now, but would be glad to be proven wrong. We could make the compiler suggest inserting "split points" for certain API's. But that's certainly after the main "manual mode" is figured out.
t
This how Dart does with dart2js https://dart.dev/guides/language/language-tour#deferred-loading Maybe some annotation could work. A compiler plugin like for Jetpack Compose.
Copy code
@Deferred
import com.something
Then later in the code, when you use the deferred library, we can have a little icon on the left, like for suspend, which allow us to recognize a deferred library. And throw errors if it is not called in a suspend funtion. Kotlin compiler would automatically do the wrap.
Copy code
@Deferred
import com.something

fun badUsage() {
    externalFunction()
}

suspend fun goodUsage() {
    externalFunction()
}
To which the compiler plugin could add the missing function.
Copy code
suspend fun goodUsage() {
    loadLibrary()
    externalFunction()
}
Just throwing some ideas 😅
a
Yes, the core idea seems the same. Thanks for the link by the way, I've added it to the proposal.
Are you familiar with that Dart's deferred? If yes, do you have feedback?
t
@anton.bannykh
I don’t see a way around right now, but would be glad to be proven wrong.
It can be emulated right now via multiple outputs Assumtion: Chunk == separate class/package
t
A long time ago... I remember being really happy about it. I used it with AngularDart, which was more advanced than the AngularTS if I can call it this way.
🙏 1
t
For multiple outputs can be used webpack plugin
suspend
function - most flexible solution in my opinion
a
@turansky not sure we are on the same page. Yes, it is possible to split the output into smaller chunks. But what I meant was:
Copy code
// chunk 1 (loaded lazily / on demand / dynamically )
function foo() {....}

// chunk 2

// Non-suspend context
foo() // <- we cannot stop and wait until chunk 1 is loaded
         // So this should either be prohibited
         // Or the developer should explicitely expect that `foo()` might not be able to execute because the module was not loaded.
So it seems impossible to eliminate certain dependencies without changing the program semantics.
t
Example:
Copy code
// service in "main.js" 
class MyExporter {
    @Chunk // "svg-export.js"
    suspend toSvg():Promise<File> {
        // load big chunk 
        // create job  
    }

    @Chunk // "pdf-export.js"
    suspend toPdf():Promise<File> {
        // load big chunk 
        // create job  
    }

    @Chunk // "vsdx-export.js"
    suspend toVsdx():Promise<File> {
        // load big chunk 
        // create job  
    }
}
a
Yes, with suspend functions - no problem 👍
t
Now - can be realized manually (via multiple outputs) Future (I want to believe) - via
suspend
@anton.bannykh Is there ticket on YouTrack on this theme?
t
a
@turansky I don't think we disagree. But the original quote "I don’t see a way around right now, but would be glad to be proven wrong." was about the compiler not being able to just randomly put any declaration in a separate dynamically loaded library. Which is why I think it is a good idea to tell the compiler, which parts of you project you'd like to load on demand. Doesn't seem like anything you've said contradicts that, so my point still stands.
t
Your truth
was about the compiler
I miss this condition/context
a
No worries 🙂
t
GWT contains “chunks” logic via interface RunAsyncCallback And in all cases I created redundant interface implementation only with one goal - isolate single “big” method in separate chunk
b
I'm able to follow this well, which is great. However i'm unsure what to set the entry/main value as in my webconfig/package.json files. Before when it was a single file I could just point to the single file, but without an index.js file to unify the module exports I'm not sure what should go int those fields. Is it possible to define a kotlin index.js file? If not has anyone who successfully split up the output and is consuming it in a separate web client figure this out? Thanks!
⬆️ 1
m
There’s a compiler flag
-Xir-per-module
which helps with code splitting. It generates separate JS files per Gradle module and dependency. It definitely helps with splitting up a Kotlin React project and loading parts of it dynamically. I haven’t tried to use it for multiple entry points in Webpack but in theory it shouldn’t be a problem 🤔 https://github.com/fluidsonic/kjs-chunks