Kotlin Javacript component from another kotlin Javascript project?


#1

I have been writing a Kotlin wrapper for a react Material UI component set which once in a reasonable condition I plan to release to (or on :slight_smile:) the public… it seems to work reasonably well so far and almost ready to be previewed.

However, I have been using create-react-kotlin-app and yarn start for development. It now takes about 20 seconds to recompile after any changes.

I now want to move on and try using my wrapper in a real app (the component project has a demo app and that works fine - just slow to compile).

To separate things, and to avoid having to compile my wrapper every time, I am trying to get my Kotlin react component project compiling and then using it in my second Kotlin javascript project.

I am planning on using Gradle (kts). I have played with the kotlin-frontend-plugin and had some success for getting a single project running.

However, I have abandoned that in favour of using node (yarn) and webpack directly.

I have spent many hours trying to make this work… I am at my closest now where my project and the component project seem to get compiled into the one bundle, however this is now throwing up errors.

I am no Gradle or javascript (and therefore neither node nor webpack) expert, so that has been half of the struggle.

I have attached a zip file of the project (see end of post) for anyone that would like to take a look. As mentioned, once I get this working there will be a few thousand lines of Kotlin react material UI wrapper code to be released :-)… the attached zip is a smaller test application with just a dummy react component to get started. I am also happy to release the wrapper earlier if anyone would like to help make the component side of things work.

I have also posted the build.gradle.kts files and package.json files, etc. below, but not sure how useful they would be for solving my problem in isolation (as mentioned, full zip attachment at the end of the post).

Right now I get the following error:

Uncaught TypeError: throwCCE is not a function
    at get_js (kotlin.js:32305)
    at RDOMBuilder.RBuilder.child_bzgiuu$ (kotlin-react.js:119)
    at testComponent1 (index.js:47)
    at includeComponent$lambda (app.js:92)
    at buildElements (kotlin-react.js:163)
    at render_0 (kotlin-react-dom.js:165)
    at includeComponent (app.js:101)
    at main (app.js:21)
    at eval (app.js:111)
    at eval (app.js:114)
get_js	@	kotlin.js:32305
RBuilder.child_bzgiuu$	@	kotlin-react.js:119
testComponent1	@	index.js:47
includeComponent$lambda	@	app.js:92
buildElements	@	kotlin-react.js:163
render_0	@	kotlin-react-dom.js:165
includeComponent	@	app.js:101
main	@	app.js:21
(anonymous)	@	app.js:111
(anonymous)	@	app.js:114
./build/js/app.js	@	bundle.js:807
__webpack_require__	@	bundle.js:20
(anonymous)	@	bundle.js:69
(anonymous)	@	bundle.js:72

As mentioned, I have spent quite a few hours on this over the last couple of weeks (spare time project), but if anyone could help… I would be so grateful.

I would also greatfully recieve feedback on how to do any of this is a better or more standard way :slight_smile:

Thanks!

Component project build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile

val production: Boolean = (parent!!.properties["production"] as String ).toBoolean()

buildscript {
    var kotlinVersion: String by extra
    kotlinVersion = "1.2.41"

    repositories {
        jcenter()
        maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
    }

    dependencies {
        classpath(kotlin("gradle-plugin", kotlinVersion))
    }
}

apply {
    plugin("kotlin2js")
}

// Not sure why this is needed, but it makes "compile(kotlinModule("stdlib-js", kotlinVersion))" line down below work
plugins {
    java
    id("com.moowork.node") version "1.2.0"
}

val kotlinVersion: String by extra

repositories {
    jcenter()
    maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
    maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
    maven { setUrl("http://dl.bintray.com/kotlin/kotlin-js-wrappers") }
}

dependencies {
    compile(kotlin("stdlib-js", kotlinVersion))
    compile("org.jetbrains", "kotlin-react", "16.3.1-pre.28-kotlin-1.2.30")
    compile("org.jetbrains", "kotlin-react-dom", "16.3.1-pre.28-kotlin-1.2.30")
}


val compileKotlin2Js: Kotlin2JsCompile by tasks

compileKotlin2Js.kotlinOptions {
    sourceMap = true
    metaInfo = true
    outputFile = "${project.buildDir.path}/js/index.js"
    main = "call"
    moduleKind = "commonjs"
}

val webpack by tasks.creating(Exec::class) {
    inputs.file("yarn.lock")
    // NOTE: Add inputs.file("webpack.config.js") for projects that have it
    inputs.dir("$buildDir/js")
//    inputs.dir("node_modules")

    outputs.dir("$projectDir/dist")
//    commandLine("$projectDir/node_modules/.bin/webpack", "app/index.js", "$buildDir/js/bundle.js")
//    commandLine("$projectDir/node_modules/.bin/webpack-cli", "--verbose", "--mode=development", "$buildDir/js/index.js", "$projectDir/dist/main.js")
    commandLine("$projectDir/node_modules/.bin/webpack-cli", "--verbose", "--mode=development", "$buildDir/js/index.js")
//    commandLine("$projectDir/node_modules/.bin/webpack-cli", "$projectDir/webpack.config.js")
}

Component project package.json: (Note it has hard coded paths to @jetbrains packages… not sure how it is supposed to work, but the @jetbrains node modules are not found otherwise… I saw that the kotlin-frontend-plugin does something sort of similar… if you know a better way, I would love to know :slight_smile:)

{
    "name": "test-comonents-1",
    "version": "0.1.0-SNAPSHOT",
    "description": "A couple of test components",
    "main": "build/js/index.js",
    "private": true,
    "dependencies": {
        "@jetbrains/kotlin-extensions": "^1.0.1-pre.28",
        "@jetbrains/kotlin-react": "^16.3.1-pre.28",
        "@jetbrains/kotlin-react-dom": "^16.3.1-pre.28",
        "core-js": "^2.5.3",
        "kotlin": "^1.2.41",
        "kotlin-extensions": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-extensions/",
        "kotlin-react": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-react/",
        "kotlin-react-dom": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-react-dom/",
        "kotlinx-html-js": "^0.6.4",
        "react": "^16.3.1",
        "react-dom": "^16.3.1"
    },
    "devDependencies": {
        "webpack": "^4.8.3",
        "webpack-cli": "^2.1.3"
    }
}

Test App build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile

buildscript {
    var kotlin_version: String by extra
    kotlin_version = "1.2.41"

    repositories {
        jcenter()
        maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
    }
    dependencies {
        classpath(kotlin("gradle-plugin", kotlin_version))
    }
}

group = "com.ccfraser.testing"
version = "1.0-SNAPSHOT"

apply {
    plugin("kotlin2js")
}

// Not sure why this is needed, but it makes "compile(...)" lines down below work
plugins {
    java
    id("com.moowork.node") version "1.2.0"
}

val kotlin_version: String by extra

repositories {
    repositories {
        jcenter()
        maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
        maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
        maven { setUrl("http://dl.bintray.com/kotlin/kotlin-js-wrappers") }
    }
}

dependencies {
    compile(kotlin("stdlib-js", kotlin_version))
    compile("org.jetbrains", "kotlin-react", "16.3.1-pre.28-kotlin-1.2.30")
    compile("org.jetbrains", "kotlin-react-dom", "16.3.1-pre.28-kotlin-1.2.30")
    compile(project(":components"))
}

val compileKotlin2Js: Kotlin2JsCompile by tasks

compileKotlin2Js.kotlinOptions {
    sourceMap = true
    metaInfo = true
//    outputFile = "${project.buildDir.path}/js/index.js"
    outputFile = "${project.buildDir.path}/js/app.js"
    main = "call"
    moduleKind = "commonjs"
    freeCompilerArgs = listOf("-Xcoroutines=enable")

}

val webpack by tasks.creating(Exec::class) {
    inputs.file("yarn.lock")
    // NOTE: Add inputs.file("webpack.config.js") for projects that have it
    inputs.file("webpack.config.js")
    inputs.dir("$buildDir/js")
//    inputs.dir("node_modules")

    outputs.dir("$buildDir/dist")
//    commandLine("$projectDir/node_modules/.bin/webpack", "app/index.js", "$buildDir/js/bundle.js")
//    commandLine("$projectDir/node_modules/.bin/webpack", "--verbose", "$buildDir/js/index.js", "$buildDir/dist/bundle.js")
//    commandLine("$projectDir/node_modules/.bin/webpack-cli", "--verbose", "--mode=development", "$buildDir/js/index.js", "/home/colin/Documents/IdeaProjects/test-react/components/build/js/index.js")
    commandLine("$projectDir/node_modules/.bin/webpack-cli", "--config", "$projectDir/webpack.config.js")

}

Test App package.json: (again with hard coded paths)

{
    "name": "test-new-kotlin-react-app",
    "version": "0.1.0",
    "main": "build/js/index.js",
    "private": true,
    "dependencies": {
        "@jetbrains/kotlin-extensions": "^1.0.1-pre.28",
        "@jetbrains/kotlin-react": "^16.3.1-pre.28",
        "@jetbrains/kotlin-react-dom": "^16.3.1-pre.28",
        "core-js": "^2.5.3",
        "kotlin": "^1.2.41",
        "kotlin-extensions": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-extensions/",
        "kotlin-react": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-react/",
        "kotlin-react-dom": "file:///home/colin/Documents/IdeaProjects/test-react/components/node_modules_imported/kotlin-react-dom/",
        "kotlinx-html-js": "^0.6.4",
        "react": "^16.3.1",
        "react-dom": "^16.3.1"
    },
    "devDependencies": {
        "webpack": "^4.8.3",
        "webpack-cli": "^2.1.3"
    }
}

Test app webpack.config.js:

'use strict';

const path = require('path');

module.exports = {
};

const config = {
    mode: 'development',
    entry: './build/js/app.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    // module: {
    //     rules: [
    //         { test: /\.txt$/, use: 'raw-loader' }
    //     ]
    // },
    resolve: {
        modules: [
            "js",
            "build/js",
            "resources",
            "node_modules",
            "../../test-react/components/build/js"
        ]
    }
};

module.exports = config;

test-react.zip (190.9 KB)


#2

Hi there,
The original problem is about using multiple kotlin.js modules (https://github.com/Kotlin/kotlinx.coroutines/issues/340).
It affects your case because webpack uses different paths to that module (components/node_modules/kotlin/kotlin.js and test-app/node_modules/kotlin/kotlin.js). The possible solution might be make sure that both your modules (components and test-app) depend on the same kotlin.js.


#3

Thanks for this…

I played around with peer dependencies for a bit, but couldn’t get it working until I found this https://github.com/webpack/webpack/issues/2134 (and a few other posts)… looks like webpack has its own version of “dependency hell” so not all is roses on the javascript side in this regard :slight_smile:

In particular, as well as using peerDependencies rather than dependencies in the component project, I also had to use aliases as referenced from the previous link… (not exactly sure why yet, but it works…) e.g. in webpack.config.js in the using project:

//...    
resolve: {
    //...
    alias: {
            'kotlin': path.resolve(path.join(__dirname, 'node_modules', 'kotlin')),
            'kotlin-react': path.resolve(path.join(__dirname, 'node_modules', 'kotlin-react')),
            'kotlin-react-dom': path.resolve(path.join(__dirname, 'node_modules', 'kotlin-react-dom')),
            'kotlinx-html-js': path.resolve(path.join(__dirname, 'node_modules', 'kotlinx-html-js')),
        } 
    //etc...

As mentioned prior, this has taken me rather a long time to figure out, so your help is greatly appreciated :smile:… I shall continue playing and see if I can merge in all my components.


#4

It has been a while, but at the beginning of this question I mentioned

I have been writing a Kotlin wrapper for a react Material UI component set which once in a reasonable condition I plan to release to (or on :slight_smile:) the public…

Well, as I mentioned over on the Slack channel, it has now been released “on” the public… for those interested in kotlin/javascript/react/Material UI, the project is called Muirwik for Material UI React Wrapper in Kotlin… The github page has a couple of screenshots of the demo project for those that feel like having a look.

Feedback and contributions welcome :slight_smile: