Does anyone have a good setup/architecture for ena...
# javascript
m
Does anyone have a good setup/architecture for enabling different environments? Let’s say I have three environments
live
,
test
and
dev
. In each environment I’d like to use different configuration values, for example a different API URL. So basically I’d have three
.kt
files with different values each and depending on what I’d like to build/deploy I need a different one of these to be used 🤔 Ideally the values are provided when starting the server (e.g. webpack dev server) so that they can depend on environment variables (e.g. on Heroku).
t
I have similar open question. Current plan - use DefinePlugin Dynamic webpack configuration by webpack plugin
m
I don’t want to use that because I’d like to resolve the configuration after the JS project was built.
b
Have a look at this
I also have application.env on npmjs for js/ts projects specifically.
m
@Big Chungus thanks, that’s along the lines I’m thinking 🙂
I’ll work on a simpler solution though. No env vars in actual web code and and no JS lib dependencies.
b
Well the idea is .env file, not necessarily your actual env variables
m
More like this:
Copy code
import kotlinx.browser.*


public external interface Configuration {

	public val publicApiUrl: String
}


public val configuration: Configuration =
	window.asDynamic().appConfiguration.unsafeCast<Configuration>()
The defining side is more tricky 🙂
b
Is this too "bloated" still for you? (example from kamp)
Copy code
external interface AppEnv {
  val API_URL: String
}

inline val Window.env: AppEnv
  get() = asDynamic().env.unsafeCast<AppEnv>()

suspend fun loadEnv(): AppEnv {
  val env: AppEnv = fetch("/application.env").await().text().await()
    .split("\n")
    .filter(String::isNotBlank)
    .joinToString(",", "{", "}") {
      val (key, value) = it.split("=", limit = 2).let { c ->
        c[0] to c[1]
      }
      "\"$key\": \"$value\""
    }.let(JSON::parse)
  window.asDynamic().env = env
  requireNotNull(window.env.API_URL)
  return env
}
m
Yes,
fetch
request is unacceptable.
It needs to be there without network request
b
But then you're forced to code env values at buildtime
m
Not really
b
You basically cannot have an env ambiguous bundle
m
Someone serves the
index
HTML file. It can be injected there.
The
index
file needs to be dynamic anyway for security.
b
What like via?
Copy code
<script>
window.env={}
</script>
m
Yes
b
But then you still need to have multiple index.html files built at compile-time
m
No, the
index.html
is a template and built on-the-fly for each request.
I deploy to Cloudflare Workers. The worker simply injects dynamic parts on the fly.
Like security nonce and - to be done - configuration.
b
Ah, got it. Didn't realise we're talking about SSR app
m
Yeah there doesn’t seem to be a good static solution if you want to make use of security nonce 🤷‍♂️
And the code generating the
index.html
on the fly is also Kotlin/JS, running on Cloudflare 😄
So I can re-use the
Configuration
definition on the service side to make it type-safe.
Just not sure yet how.
b
Copy code
class Configuration(
  val prop1: String,
  val prop2:Int
) {
  override fun toString() = """{
    prop1:"$prop1",
    prop2:$prop2,
  }"""
}
val html = """
  <script>
    window.env=$configuration
  </script>
"""
?
m
That seems very unsafe 😅 And I have an unnecessary class definition and JSON generating code in the web project. But I only need that for deployment/running.
b
Well this is part of your "backend" that's serving index.html
m
But then it’s no longer typesafe
b
it is, since that class is in commonMain
m
I need the
external interface Configuration
in the web project.
And only the JSON generating part in the deployment.
I could use
kotlinx-serialization
and just “assume” that the generated JSON is perfectly valid for the
external interface
which doesn’t use
kotlinx-serialization
.