Bundle Kotlin/JS project in single file for NodeJS execution

When building NodeJS applications you may import stuff from Maven. When doing so and using the browser target, webpack helps you by packing all in a single file and no more issues.

Would that be possible for NodeJS as well? If not, why and how to overcome the issue?

2 Likes

I found out that webpack does support packing NodeJs applications and the configuration is pretty simple:

module.exports = [{
    name: 'server',
    entry: './packages/beristo-pull-request-notifier/kotlin/beristo-pull-request-notifier.js',
    target: 'node', // <-- importat part!
    output: {
        path: __dirname + '/dist/server',
        filename: 'bundle.js',
    }
}];

I believe I will pipe some Gradle task using the Node Gradle Plugin.

I’d also like to point out that documentation around tasks that builds node modules packages out of a Kotlin/JS project is very lacking and having to scout each and every Gradle tasks of the Kotlin/JS plugin is really frustrating!

2 Likes

I managed with a custom tasks to generate the configuration:

open class GenerateWebpackConfig : DefaultTask() {

    enum class Target {
        NODE, BROWSER
    }

    private val template = """
        module.exports = [{
            name: 'server',
            entry: '%%%ENTRY%%%',
            target: '%%%MODE%%%', // <-- importat part!
            output: {
                path: '%%%OUTPUT_PATH%%%',
                filename: '%%%OUTPUT_NAME%%%',
            },
            resolve: {
                modules: [%%%MODULES_FOLDER%%%]
            }
        }];
    """.trimIndent()

    @get:InputFile
    var entryFile by project.objects.property<File>()

    @get:Input
    var target: Target = Target.NODE

    @get:Input
    var outputBundleFolder by project.objects.property<String>()

    @get:Input
    var outputBundleName by project.objects.property<String>()

    @get:InputFiles
    var modulesFolder = project.objects.listProperty(File::class)

    @get:OutputFile
    var outputConfig by project.objects.property<File>()

    init {
        with(project) {
            outputBundleFolder = file("$buildDir\\bundle").absolutePath
            outputBundleName = "bundle.js"
            modulesFolder.set(listOf(file("node_modules")))
            outputConfig = file("$buildDir/config/webpack.config.js")
        }
    }

    @TaskAction
    fun buildFile() {
        outputConfig.writeText(
            template.replace("%%%ENTRY%%%", entryFile.absolutePath.replace("\\", "\\\\"))
                .replace("%%%MODE%%%", target.toString().toLowerCase())
                .replace("%%%OUTPUT_PATH%%%", outputBundleFolder.replace("\\", "\\\\"))
                .replace("%%%OUTPUT_NAME%%%", outputBundleName)
                .replace(
                    "%%%MODULES_FOLDER%%%",
                    modulesFolder.get().joinToString(",") { "'${it.absolutePath.replace("\\", "\\\\")}'" }
                )
        )
    }
}

And the buildscript:

import com.github.gradle.node.task.NodeTask
import it.belabs.gradle.tasks.GenerateWebpackConfig
import it.belabs.gradle.tasks.GenerateWebpackConfig.Target.NODE
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile

plugins {
    kotlin("js")
    id("com.github.node-gradle.node")
}

kotlin {
    js {
        useCommonJs()
        nodejs()
        binaries.executable()
    }
}

val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class)
val packageJson by tasks.getting(KotlinPackageJsonTask::class)
val rootPackageJson by rootProject.tasks.getting(RootPackageJsonTask::class)

val generateWebpackConfig by tasks.creating(GenerateWebpackConfig::class) {
    target = NODE
    entryFile = compileKotlinJs.outputFile
    modulesFolder.set(listOf(File(rootPackageJson.rootPackageJson.parentFile, "node_modules")))
}

val packAction by tasks.creating(NodeTask::class) {
    group = "distribution"
    dependsOn(generateWebpackConfig, compileKotlinJs, rootPackageJson, tasks.npmInstall)
    script.set(file("node_modules/webpack-cli/bin/cli.js"))
    args.set(listOf("-c", generateWebpackConfig.outputConfig.absolutePath))
}

the tasks that generated the configurations are compileKotlinJs, packageJson and the rootProject task rootPackageJson. I was able to install webpack and webpack-cli locally with a local package.json using the Node Gradle Plugin and invoking it via a task. Gosh the JS tooling ecosystem is messed up!

3 Likes