Setting environment variable with KotlinJS and Webpack 5

Hello, newcomer here. I’m learning KotlinJS, and I have a problem setting environment variables.

1. My requirement
Basically, I want to set different variables such as the backend url depending on the environment (PROD, DEV, but also TEST and so on.)

2. The advice I found
I tried following the advice here :
https://discuss.kotlinlang.org/t/kotlin-js-react-accessing-configuring-environment-variables/16906/9
and here :
https://gist.github.com/CameronProbert/85b7d60fa9572d93566f5c5ee62441e0
However, this does not seem to work anymore with the latest versions of Kotlin JS and webpack.

I’ll start with a summary of the recommended method from those topics, so you do not have to read them.

I have to use Webpack. I can send the environment as a command line parameter to webpack (adapted to Webpack 5) :

    val envTargetWebpackArgs = listOf("--env", "envTarget=$env")
    webpackTask { args.plusAssign(envTargetWebpackArgs) }
    runTask { args.plusAssign(envTargetWebpackArgs) }

Then I can read it in webpack.config.js. As mentionned in the documentation here : https://webpack.js.org/guides/environment-variables/ this is done by replacing the module.exports = config line to export a function instead :

module.exports = (env) => {
    // ...
    return config;
}

Finally, with KotlinJS I do not make a webpack.config.js myself beacuse KotlinJS will generate it. Instead I have to create a directory called webpack.config.d at the root of the project, and any js file I put there will be added to webpack.config.js .

3. What I did
I created a file, webpack.config.d/env.js (sorry about the length, I abbreviated it but removing more might make you think the problem is simpler than it is) :

module.exports = env => {
    const webpack = require("webpack");
    let appConfig;
    switch(env.envTarget) {
        case 'PROD':
            appConfig = {
                'appConfig': {
                    envName: JSON.stringify(env.envTarget),
                    serverRootUrl: JSON.stringify("backend.url.com")
                }
            };
            break;
        // Many more cases.
        default:
            appConfig = {
                'appConfig': {
                    envName: JSON.stringify(env.envTarget),
                    serverRootUrl: JSON.stringify("localhost:8080")
                }
            };
            break;
    }
    config.plugins.push(new webpack.DefinePlugin(appConfig));
    return config;
}

4. The problem
When I tried it it did not work. I looked around, and found the problem in the generated webpack.config.js file. Here is the relevant part :

module.exports = env => {
    const webpack = require("webpack");
    let appConfig;
    switch(env.envTarget) {
        case 'PROD':
            appConfig = {
                'appConfig': {
                    envName: JSON.stringify(env.envTarget),
                    serverRootUrl: JSON.stringify("backend.url.com")
                }
            };
            break;
        // Many more cases.
        default:
            appConfig = {
                'appConfig': {
                    envName: JSON.stringify(env.envTarget),
                    serverRootUrl: JSON.stringify("localhost:8080")
                }
            };
            break;
    }
    config.plugins.push(new webpack.DefinePlugin(appConfig));
    return config;
}


module.exports = config

As you can see, KotlinJS properly copied my code from env.js, but since it adds the line module.exports = config at the end, all my changes are ignored ! I tried to find a workaround, to no avail. Exporting a function instead of the config is the only way to access the variables from the webpack config file, and the way KotlinJs generates it, it’s not an option.

5. My questions
Do you know of a workaround that would let me use environment variables in Webpack ? Or is there another way entirely to set environment variables such as the backend url ?

I found a workaround. I’m not proud of it, but it works.

Basically, I have a set of json config files in src, all named after an environment :
src/dev.json
src/prod.json
and so on.

I can choose which one to use by writing -Penvironment=[environment] on the gradle command line.

In the code, I can read a variable by calling Config.[variable name] and I will get the corresponding value from the appropriate config file.

Here is the code :
buid.gradle.kts :

val environment = project.properties["environment"] ?: "dev"
println("environment = $environment")
tasks.register<Copy>("envConfig") {
    from("src/$environment.json")
    into("$buildDir/generated")
    rename { "config.json" }
}
tasks.getByName("processResources").dependsOn("envConfig")
kotlin.sourceSets.getByName("main").resources.srcDir("$buildDir/generated")

Config.kt :

external val require: dynamic

object Config {
    private val configMap = require("./config.json")

    val environment = configMap["environment"] as String

    val serverRootUrl = configMap["serverRootUrl"] as String

    // ...
}

When the Gradle build is launched, the envConfig task will pick up the config file, copy it to build/generated and rename it config.json regardless of what it original name was. Since I added that folder to the sourceSet, it gets picked up by webpack. Then I can access it from the code using require(“./config.json”). The Config object is only there so I do not have to cast to String all the time.

It works, but it feels very hacky. If anyone has an idea for improvement, I’m interested. Also, I’m curious if anyone can make the first method (with Webpack) works ?

1 Like