Search code examples
typescriptkotlinkotlin-multiplatformkotlin-js

KotlinJS + Typescript: Atomicfu TraceBase reference not found in d.ts file


With either coroutine or ktor dependency, when KotlinJS npm library is published, in the resulting generated typescript d.ts file there are atomicfu references

export namespace kotlinx.atomicfu {
    function atomic$ref$<T>(initial: T, trace: kotlinx.atomicfu.TraceBase): kotlinx.atomicfu.AtomicRef<T>;
    function atomic$boolean$(initial: boolean, trace: kotlinx.atomicfu.TraceBase): kotlinx.atomicfu.AtomicBoolean;
    function atomic$int$(initial: number, trace: kotlinx.atomicfu.TraceBase): kotlinx.atomicfu.AtomicInt;
    function atomic$long$(initial: kotlin.Long, trace: kotlinx.atomicfu.TraceBase): kotlinx.atomicfu.AtomicLong;
}

but reference for TraceBase is missing from the file and when compiling a Typescript app that depends on above kotlinJS library, it complains about missing TraceBase.

The only way to move forward is to add skipLibCheck = true in the tsconfig file. That's not an ideal solution for user of the library.

Is there a way to solve this issue?


Solution

  • It's a known compiler bug. There is an issue created to fix it: https://youtrack.jetbrains.com/issue/KT-50464

    Temporarily you can solve it using Gradle by removing all atomicfu references from the generated d.ts file.

    Solution below assumes that you use this Gradle plugin NPM package publishing. It provides a packJsNpmPublication task that builds the js library publication code with specific naming for generated files.

    You should add below two gradle tasks

    // This task copies content of "js" build folder into a "temp" folder
    
    tasks.register<Copy>("copyJsBuildContentToTemp") {
        this.from("$buildDir/publications/npm/js/")
        this.into("$buildDir/publications/npm/js/temp/")
    }
    
    // This task goes through your `d.ts` file from `temp` folder,
    // Removes all the lines containing `atomicfu` word and then last line with '}'
    // Then copies the modified file back into `js` folder that replaces the original file
    // At the end, it deletes the `temp` folder
    
    tasks.register<Copy>("removeAtomicFu") {
        var atomicfu = false
        duplicatesStrategy = DuplicatesStrategy.INCLUDE
        from(buildDir.resolve("$buildDir/publications/npm/js/temp/${rootProject.name}-${project.name}.d.ts")) {
            filter {
                when {
                    it.contains("atomicfu") -> {
                        atomicfu = true
                        ""
                    }
                    atomicfu && it == "}" -> {
                        atomicfu = false
                        ""
                    }
                    else -> it
                }
            }
        }
        this.into("$buildDir/publications/npm/js/")
        dependsOn("copyJsBuildContentToTemp")
        doLast {
            delete("$buildDir/publications/npm/js/temp")
        }
    }
    
    // This makes sure that `removeAtomicFu` tasks runs at the end of task 
    // `assembleJsNpmPublication` so when `pack` or `publish` runs and creates 
    // the `tarball` file, it would put update content in the `tgz` file
    project.afterEvaluate {
        tasks.findByName("assembleJsNpmPublication")?.finalizedBy("removeAtomicFu")
    }
    

    Now if you run ./gradlew packJsNpmPublication you'll have build content without atomicfu references.